【问题标题】:Continuing test execution in junit4 even when one of the asserts fails即使其中一个断言失败,也可以在 junit4 中继续测试执行
【发布时间】:2021-02-13 03:44:06
【问题描述】:

我使用 Jfunc 构建了我现有的框架,它提供了一种工具,即使在测试用例中的一个断言失败时也可以继续执行。 Jfunc 使用 junit 3.x 框架。但是现在我们正在迁移到 junit4,所以我不能再使用 Jfunc 并用 junit 4.10 jar 替换它。
现在的问题是,因为我们在框架中广泛使用了 jfunc,并且在 junit 4 中,我们希望即使在测试用例中某个断言失败时,我们的代码也能继续执行。
有没有人对此有任何建议/想法,我知道在junit中测试需要更加原子,即每个测试用例一个断言,但由于某种原因我们不能在我们的框架中这样做。

【问题讨论】:

    标签: java junit4


    【解决方案1】:

    您可以使用ErrorCollector 规则执行此操作。

    要使用它,首先将规则添加为测试类中的字段:

    public class MyTest {
        @Rule
        public ErrorCollector collector = new ErrorCollector();
    
        //...tests...
    }
    

    然后将您的断言替换为对collector.checkThat(...) 的调用。

    例如

    @Test
    public void myTest() {
        collector.checkThat("a", equalTo("b"));
        collector.checkThat(1, equalTo(2));
    }
    

    【讨论】:

    • 感谢克里斯的回复。它在第一次失败后继续工作(即collector.checkThat(10, CoreMatchers.equalTo(11)))。但问题是即使其中一项检查失败,我也希望它将测试标记为失败。目前在执行上述测试后,测试被标记为通过。我希望它失败并在失败行显示堆栈跟踪(类似于断言失败时)。
    • 这很奇怪。我刚刚尝试了我在答案中编写的代码,它按预期工作。测试失败,它打印出每个失败断言的堆栈跟踪。这是完整的测试代码和我得到的输出,从 IntelliJ 运行测试:gist.github.com/2419626
    • 哦......我的错,我只是忘记添加规则,它造成了所有的混乱......抱歉造成混乱,非常感谢这个帖子。
    • 仅供参考,您可以通过简单地添加一个字符串作为第一个参数来将消息附加到checkThat 方法。 但是这个方法只有4.10(含)以后才支持.
    • 嗨,它是否也将所有失败的步骤都标记为“失败”?
    【解决方案2】:

    我也使用 ErrorCollector,但也使用 assertThat 并将它们放在 try catch 块中。

    import static org.junit.Assert.*;
    import static org.hamcrest.Matchers.*;
    
    @Rule
    public ErrorCollector collector = new ErrorCollector();
    
    @Test
    public void calculatedValueShouldEqualExpected() {
        try {
            assertThat(calculatedValue(), is(expected));
        } catch (Throwable t) {
            collector.addError(t);
            // do something
        }
    }
    

    【讨论】:

      【解决方案3】:

      你也可以使用assertj - soft assertion

      @Test
      public void testCollectErrors(){
         SoftAssertions softly = new SoftAssertions();
         softly.assertThat(true).isFalse();
         softly.assertThat(false).isTrue();
         // Don't forget to call SoftAssertions global verification !
         softly.assertAll();
      }
      

      还有其他不用手动调用softly.assertAll();

      1. with rule
      2. with autoclosable
      3. Using the static assertSoftly method

      【讨论】:

      • 因为没有 try...catch 并且按照 lib-authors 的意图工作,我认为这是最规范的解决方案。
      【解决方案4】:

      使用 try/finally 块。这在我的情况下有效:

      ...
      try {
          assert(...)
      } finally {
          // code to be executed after assert
      }
      ...
      

      【讨论】:

      • 您知道这个问题是 2 年前发布的,并且已经有一个被接受的答案吗?
      • @TheJuniorProgrammer 只是一个一般说明,这个网站不仅仅是关于接受的答案。重点是帮助搜索答案的用户找到问题的解决方案。有时有多种方法可以解决一个问题,有时对一个人有效的方法可能对另一个人无效。
      • @nomistic 很好,很真实。
      • 我发现这个答案只有在只有一个断言语句时才有效。抛出错误后不执行多个断言。
      • @LydiaRalph:是的,每个断言都需要自己的 try/finally 块。答案中没有解释。
      【解决方案5】:

      Try - catch,在“try”中使用断言,在“catch”中将可能的错误添加到集合中。 然后在测试结束时在 tearDown() 中抛出异常。 因此,如果断言中出现失败/错误,它将被捕获并继续测试。 (示例中的集合是静态的,您也可以在 setUp() 中为每个 @Test 创建新实例)

          public static List<String> errors = new ArrayList<>();
      
      
          try {
              //some assert...
          }
          catch (AssertionError error) {
              errors.add(error.toString());
          }
      
      
      
          @After
          public void tearDown() {
      
              try {
                  if (!errors.isEmpty()) {
                      throw new AssertionError(errors);
                  }
              }
              finally {
                  //empty list because it's static, alternatively make instance for each test in setUp()
                  errors.clear();
              }
          }
      

      【讨论】:

        【解决方案6】:

        我创建了自己的简单断言类。易于使用您的用例进行扩展:

        public class MyEquals {
        
        public static void checkTestSummary(MyTestSummary myTestSummary) {
            final List<MyTestResult> conditions = myTestSummary.getTestResults();
            final int total = conditions.size();
        
            final boolean isSuccessful = myTestSummary.isSuccessful();
            if (isSuccessful) {
                System.out.println(format("All [%s] conditions are successful!", total));
            } else {
                final List<MyTestResult> failedConditions = conditions.stream().filter(MyTestResult::isTestResult).collect(Collectors.toList());
                System.out.println(format("\nNot yet.. [%s out of %s] conditions are failed", failedConditions.size(), total));
            }
        
            if (!isSuccessful) {
                for (int i = 0; i < total; i++) {
                    final MyTestResult myTestResult = conditions.get(i);
                    if (myTestResult.isTestResult()) {
                        System.out.println(format("  Success [%s of %s] => Expected %s Actual %s Good!", i + 1, total, myTestResult.getExpected(), myTestResult.getActual()));
                    } else {
                        System.out.println(format("!! Failed [%s of %s] => Expected %s Actual %s", i + 1, total, myTestResult.getExpected(), myTestResult.getActual()));
                    }
                }
            }
        
            assertTrue(isSuccessful);
        }
        
        public static void myAssertEquals(MyTestSummary myTestSummary, Object expected, Object actual) {
            if (checkEquals(expected, actual)) {
                assertEquals(expected, actual);
                myTestSummary.addSuccessfulResult(expected, actual);
            } else {
                myTestSummary.addFailedResult(expected, actual);
                myTestSummary.setSuccessful(false);
            }
        }
        
        public static boolean checkEquals(Object value1, Object value2) {
            if (value1 == null && value2 == null) {
                return true;
            } else if (value1 != null && value2 == null) {
                return false;
            } else if (value1 == null && value2 != null) {
                return false;
            } else if (value1 != null && value2 != null) {
                return value1.equals(value2);
            }
            return false;
        }
        
        }
        
        @Builder
        @Value
        public class MyTestResult {
        
            String expected;
        
            String actual;
        
            boolean testResult;
        
         }
        
        
        
        @Data
        public class MyTestSummary {
        
        private boolean successful = true;
        
        private List<MyTestResult> testResults = new ArrayList<>();
        
        public MyTestSummary() {
        }
        
        public void addSuccessfulResult(Object expected, Object actual) {
            getTestResults().add(MyTestResult.builder()
                .expected(String.valueOf(expected))
                .actual(String.valueOf(actual))
                .testResult(true)
                .build()
            );
        }
        
        public void addFailedResult(Object expected, Object actual) {
            getTestResults().add(MyTestResult.builder()
                .expected(String.valueOf(expected))
                .actual(String.valueOf(actual))
                .testResult(false)
                .build()
            );
        }
        }
        

        在junit测试中的使用

        @Test
        public void testThat() {
            MyTestSummary myTestSummary = new MyTestSummary();
            myAssertEquals(myTestSummary, 10, 5 + 5);
            myAssertEquals(myTestSummary, "xxx", "x" + "x");
            checkTestSummary(myTestSummary);
        }
        

        输出:

        Not yet.. [1 out of 2] conditions are failed
        Success [1 of 2] => Expected 10 Actual 10 Good!
        !! Failed [2 of 2] => Expected xxx Actual xx
        
         org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
         Expected :true
         Actual   :false
        

        【讨论】:

          【解决方案7】:

          另一个选项是结合 lambda 表达式的可观察模式。你可以使用类似上面的东西。

          public class MyTestClass {
          
              private final List<Consumer<MyTestClass>> AFTER_EVENT = new ArrayList<>();
          
              @After
              public void tearDown() {
                  AFTER_EVENT.stream().forEach(c -> c.accept(this));
              }
          
              @Test
              public void testCase() {
                  //=> Arrange
                  AFTER_EVENT.add((o) -> {
                      // do something after an assertion fail.
                  }));
          
                  //=> Act
          
                  //=> Assert
                  Assert.assertTrue(false);
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-12-18
            • 1970-01-01
            • 2019-04-14
            • 2017-10-31
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多