技术控

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

[其他] Use JUnit's Expected Exceptions Sparingly

[复制链接]
披着蚊帐当婚纱 发表于 2016-10-3 14:28:38
136 1

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
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
鄙视楼下的顶帖没我快,哈哈
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2017 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表