请选择 进入手机版 | 继续访问电脑版

技术控

    今日:22| 主题:57481
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Use JUnit's Expected Exceptions Sparingly

[复制链接]
披着蚊帐当婚纱 投递于 2016-10-3 14:28:38
160 1
Sometimes, when we get pull requests for    jOOQ or our other libraries, people change the code in our unit tests to be more “idiomatic JUnit.” In particular, this means that they tend to change this (admittedly not so pretty code):  
  1. @Test
  2. public void testValueOfIntInvalid() {
  3.     try {
  4.         ubyte((UByte.MIN_VALUE) - 1);
  5.         fail();
  6.     }
  7.     catch (NumberFormatException e) {}
  8.     try {
  9.         ubyte((UByte.MAX_VALUE) + 1);
  10.         fail();
  11.     }
  12.     catch (NumberFormatException e) {}
  13. }
复制代码
… into this, “better” and “cleaner” version:
  1. @Test(expected = NumberFormatException.class)
  2. public void testValueOfShortInvalidCase1() {
  3.     ubyte((short) ((UByte.MIN_VALUE) - 1));
  4. }
  5. @Test(expected = NumberFormatException.class)
  6. public void testValueOfShortInvalidCase2() {
  7.     ubyte((short) ((UByte.MAX_VALUE) + 1));
  8. }
复制代码
What Have We Gained?

  Nothing!
  Sure, we already have to use the    @Testannotation, so we might as well use its attribute    expectedright? I claim that this is completely wrong. For two reasons. And when I say “two”, I mean “four”:  
  1. We’re Not Really Haining Anything in Terms of Number of Lines of Code

  Compare the semantically interesting bits:
  1. // This:
  2. try {
  3.     ubyte((UByte.MIN_VALUE) - 1);
  4.     fail("Reason for failing");
  5. }
  6. catch (NumberFormatException e) {}
  7. // Vs this:
  8. @Test(expected = NumberFormatException.class)
  9. public void reasonForFailing() {
  10.     ubyte((short) ((UByte.MAX_VALUE) + 1));
  11. }
复制代码
Give or take whitespace formatting, there are exactly the same amount of essential semantic pieces of information:
  
       
  • The method calls on      ubyte(), which is under test. This doesn’t change.   
  • The message we want to pass to the failure report (in a string vs. in a method name).   
  • The exception type and the fact that it is expected.  
  So, even from a stylistic point of view, this isn’t really a meaningful change.
  2. We’ll Have to Refactor it Back Anyway  

  In the annotation-driven approach, all I can do is test for the exception    type. I cannot make any assumptions about the exception message for instance, in case I do want to add further tests, later on. Consider this:  
  1. // This:
  2. try {
  3.     ubyte((UByte.MIN_VALUE) - 1);
  4.     fail("Reason for failing");
  5. }
  6. catch (NumberFormatException e) {
  7.     assertEquals("some message", e.getMessage());
  8.     assertNull(e.getCause());
  9.     ...
  10. }
复制代码
3. The Single Method Call Is not the Unit  

  The    unittest was called    testValueOfIntInvalid(). So, the semantic “unit” being tested is that of the    UBytetype’s    valueOf()behavior in the event of invalid input    in general. Not for a single value, such as    UByte.MIN_VALUE - 1.  
  It shouldn’t be split into further smaller units, just because that’s the only way we can shoehorn the    @Testannotation into its limited scope of what it can do.  
  Hear this, TDD folks. I    NEVERwant to shoehorn my API design or my logic into some weird restrictions imposed by your “backwards” test framework (nothing personal, JUnit).    NEVER! “My” API is 100x more important than “your” tests. This includes me not wanting to:  
  
       
  • Make everything public.   
  • Make everything non-final.   
  • Make everything injectable.   
  • Make everything non-static.   
  • Use annotations.      I hate annotations.  
  Nope. You’re wrong. Java is already a not-so-sophisticated language, but let me at least use the few features it offers in any way I want.
  Don’t impose your design or semantic disfigurement on my code because of testing.
  OK. I’m overreacting.    I always am, in the presence of annotations. Because…  
  4. Annotations Are Always a Bad Choice for Control Flow Structuring  

  Time and again, I’m surprised by the amount of abuse of annotations in the Java ecosystem. Annotations are good for three things:
  
       
  • Processable documentation (e.g.      @Deprecated).   
  • Custom “modifiers” on methods, members, types, etc. (e.g.      @Override).   
  • Aspect-oriented programming (e.g.      @Transactional).  
  And beware that    @Transactionalis the    one of the very fewreally generally useful aspect that ever made it to mainstream (logging hooks being another one, or dependency injection if you absolutely must). In most cases, AOP is a niche technique to solve problems, and you generally don’t want that in ordinary programs.  
  It is decidedly    NOTa good idea to model control flow structures, let alone test behavior, with annotations.  
  Yes. Java has come a long (slow) way to embrace more sophisticated programming idioms. But if you really get upset with the verbosity of the occasional    try { .. } catch { .. }statement in your unit tests, then there’s a solution for you. It’s Java 8.  
  How to do it better with Java 8

      JUnit lambda is in the works:
    http://junit.org/junit-lambda.html        And they have added new functional API to the new      Assertionsclass:   
    https://github.com/junit-team/junit-lambda/blob/master/junit5-api/src/main/java/org/junit/gen5/api/Assertions.java    Everything is based around the          Executablefunctional interface    :  
  1. @FunctionalInterface
  2. public interface Executable {
  3.     void execute() throws Exception;
  4. }
复制代码
This executable can now be used to implement code that is asserted to throw (or not to throw) an exception. See the following methods in          Assertions      
  1. public static void assertThrows(Class<? extends Throwable> expected, Executable executable) {
  2.     expectThrows(expected, executable);
  3. }
  4. public static <T extends Throwable> T expectThrows(Class<T> expectedType, Executable executable) {
  5.     try {
  6.         executable.execute();
  7.     }
  8.     catch (Throwable actualException) {
  9.         if (expectedType.isInstance(actualException)) {
  10.             return (T) actualException;
  11.         }
  12.         else {
  13.             String message = Assertions.format(expectedType.getName(), actualException.getClass().getName(),
  14.                 "unexpected exception type thrown;");
  15.             throw new AssertionFailedError(message, actualException);
  16.         }
  17.     }
  18.     throw new AssertionFailedError(
  19.         String.format("Expected %s to be thrown, but nothing was thrown.", expectedType.getName()));
  20. }
复制代码
That’s it! Now, those of you who object to the verbosity of    try { .. } catch { .. }blocks can rewrite this:  
  1. try {
  2.     ubyte((UByte.MIN_VALUE) - 1);
  3.     fail("Reason for failing");
  4. }
  5. catch (NumberFormatException e) {}
复制代码
… Into this:
  1. expectThrows(NumberFormatException.class, () ->
  2.     ubyte((UByte.MIN_VALUE) - 1));
复制代码
And if I want to do further checks on my exception, I can do so:
  1. Exception e = expectThrows(NumberFormatException.class, () ->
  2.     ubyte((UByte.MIN_VALUE) - 1));
  3. assertEquals("abc", e.getMessage());
  4. ...
复制代码
Great work, JUnit lambda team!
  Functional Programming Beats Annotations Every Time

      Annotations were abused for a lot of logic, mostly in the JavaEE and Spring environments, which were all too eager to move XML configuration back into Java code. This has gone the wrong way, and the example provided here clearly shows that there is almost    alwaysa better way to write out control flow logic    explicitlyboth using object orientation or functional programming, than by using annotations.  
  In the case of    @Test(expected = ...), I conclude:  
  Rest in peace,    expected  
  (It is no longer part of the    JUnit 5      @Testannotation    , anyway).



上一篇:Web Developer Reading List: JavaScript Functions
下一篇:Understanding "This" in JavaScript
逝去的日子 投递于 2016-11-12 04:42:03
鄙视楼下的顶帖没我快,哈哈
回复 支持 反对

使用道具 举报

我要投稿

推荐阅读


回页顶回复上一篇下一篇回列表
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 | 粤公网安备 44010402000842号 )

© 2001-2017 Comsenz Inc.

返回顶部 返回列表