有时,案例失败,不是因为应用程序错误,而是因为意外事件,如浏览器问题,网络滞后等,现在,我们可能需要重新验证失败的案例,看看这些意外事件是否仍然存在。TestNG为我们提供了一些方法来做到这一点。
让我们一个接一个地看每一种方式。
让我们首先尝试运行一个测试类。我们的测试类将有三个测试用例。
- package Test;
-
- import org.testng.Assert;
- import org.testng.annotations.Test;
-
- public class CodekruTest {
-
- @Test
- public void test1() {
- System.out.println("Executing test1 in CodekruTest class");
- Assert.assertTrue(true);
- }
-
- @Test
- public void test2() {
- System.out.println("Executing test2 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case
- }
-
- @Test
- public void test3() {
- System.out.println("Executing test3 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case as well
- }
-
- }
下面是运行上述测试类的XML文件
- <suite name="codekru">
- <test name="codekruTest">
- <classes>
- <class name="Test.CodekruTest" />
- classes>
- test>
- suite>
运行上述XML文件后的输出-

Executing test1 in CodekruTest class
Executing test2 in CodekruTest class
Executing test3 in CodekruTest class
===============================================
codekru Total tests run: 3, Passes: 1, Failures: 2, Skips: 0
现在,有两个用例失败了(test 2和test 3),并且必须在testng-output中创建一个testng-failed.xml文件,该文件可用于运行失败的测试用例。

testng—failed.xml文件的内容包含再次运行失败案例所需的信息。
- "1.0" encoding="UTF-8"?>
- suite SYSTEM "https://testng.org/testng-1.0.dtd">
- <suite name="Failed suite [Failed suite [codekru]]" guice-stage="DEVELOPMENT">
- <test thread-count="5" name="codekruTest(failed)(failed)">
- <classes>
- <class name="Test.CodekruTest">
- <methods>
- <include name="test3"/>
- <include name="test2"/>
- methods>
- class>
- classes>
- test>
- suite>
运行testng-failed.xml文件后的输出-
- [RemoteTestNG] detected TestNG version 7.4.0
- Executing test3 in CodekruTest class
- Executing test2 in CodekruTest class
-
- ===============================================
- Failed suite [Failed suite [codekru]]
- Total tests run: 2, Passes: 0, Failures: 2, Skips: 0
在这里,我们可以看到,只有失败的案件得到执行。
- package Test;
-
- import org.testng.Assert;
- import org.testng.annotations.Test;
-
- public class CodekruTest {
-
- @Test
- public void test1() {
- System.out.println("Executing test1 in CodekruTest class");
- Assert.assertTrue(true);
- }
-
- @Test(dependsOnMethods = "test4")
- public void test2() {
- System.out.println("Executing test2 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case
- }
-
- @Test(dependsOnMethods = "test4")
- public void test3() {
- System.out.println("Executing test3 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case as well
- }
-
- @Test
- public void test4() {
- System.out.println("Executing test4 in CodekruTest class");
- Assert.assertTrue(true);
- }
- }
让我们再次运行CodekruTest类
- Executing test1 in CodekruTest class
- Executing test4 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test3 in CodekruTest class
- PASSED: test4
- PASSED: test1
- FAILED: test3
- FAILED: test2
- ===============================================
- Default test
- Tests run: 4, Failures: 2, Skips: 0
- ===============================================
在这里,test 4用例通过,而test 2和test 3仍然失败。现在让我们看看testng-failed.xml文件的内容。
让我们在CodekruTest类中再包含一个测试用例(test4),test2和test3将使用dependsOnMethods属性依赖于test4。
- package Test;
-
- import org.testng.Assert;
- import org.testng.annotations.Test;
-
- public class CodekruTest {
-
- @Test
- public void test1() {
- System.out.println("Executing test1 in CodekruTest class");
- Assert.assertTrue(true);
- }
-
- @Test(dependsOnMethods = "test4")
- public void test2() {
- System.out.println("Executing test2 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case
- }
-
- @Test(dependsOnMethods = "test4")
- public void test3() {
- System.out.println("Executing test3 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case as well
- }
-
- @Test
- public void test4() {
- System.out.println("Executing test4 in CodekruTest class");
- Assert.assertTrue(true);
- }
- }
让我们再次运行CodekruTest类
Executing test1 in CodekruTest class
Executing test4 in CodekruTest class
Executing test2 in CodekruTest class
Executing test3 in CodekruTest class
PASSED: test4 PASSED: test1 FAILED: test3 FAILED: test2 ===============================================
Default test Tests run: 4, Failures: 2, Skips: 0 ===============================================
在这里,test 4用例通过,而test 2和test 3仍然失败。现在让我们看看testng-failed.xml文件的内容。
- "1.0" encoding="UTF-8"?>
- suite SYSTEM "https://testng.org/testng-1.0.dtd">
- <suite name="Failed suite [Default suite]" guice-stage="DEVELOPMENT">
- <test thread-count="5" name="Default test(failed)">
- <classes>
- <class name="Test.CodekruTest">
- <methods>
- <include name="test4"/>
- <include name="test3"/>
- <include name="test2"/>
- methods>
- class>
- classes>
- test>
- suite>
我们可以看到test 4也包含在testng-failed.xml文件中,因为test 2和test 3依赖于test 4。因此,现在在执行testng-failed.xml文件后将运行三个测试用例。
Executing test4 in CodekruTest class
Executing test3 in CodekruTest class
Executing test2 in CodekruTest class ===============================================
Failed suite [Default suite] Total tests run: 3, Passes: 1, Failures: 2, Skips: 0 ===============================================
通过testng-failed.xml运行失败的用例是一个很好的方法来验证测试用例,但是失败的用例将在TestNG执行整个测试套件之后执行。TestNG为我们提供了一个特性,它可以在失败时立即恢复我们的用例,我们可以通过使用retryAnalyzer属性来实现这一点。
使用retryAnalyzer属性的一些优点
那么,我们如何在代码中使用重试分析器呢?
让我们创建一个实现IRetryAnalyzer接口的类。IRetryAnalyzer有一个retry()方法,我们的类将覆盖该方法。
- package Test;
-
- import org.testng.IRetryAnalyzer;
- import org.testng.ITestResult;
-
- public class Retry implements IRetryAnalyzer {
-
- int counter = 0;
- int retryLimit = 2; // maximum number of times, the case can be retried
-
- public boolean retry(ITestResult result) {
- if (counter < retryLimit) {
- counter++;
- return true; // retry the case if retryLimit is not exhausted
- }
- return false; // Do not retry anymore, as retryLimit is exhausted
- }
-
- }
它将重试该案件,直到案件通过或重试已用尽。现在,我们将在我们的测试类(CodekruTest)中使用它。
- package Test;
-
- import org.testng.Assert;
- import org.testng.annotations.Test;
-
- public class CodekruTest {
-
- @Test
- public void test1() {
- System.out.println("Executing test1 in CodekruTest class");
- Assert.assertTrue(true);
- }
-
- @Test(retryAnalyzer = Retry.class)
- public void test2() {
- System.out.println("Executing test2 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case
- }
-
- @Test()
- public void test3() {
- System.out.println("Executing test3 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case as well
- }
- }
现在,test2()用例在失败后将重试2次。所以,现在让我们尝试使用下面的XML文件运行我们的CodekruTest类。
- <suite name="codekru">
- <test name="codekruTest">
- <classes>
- <class name="Test.CodekruTest" />
- classes>
- test>
- suite>
产出-
- Executing test1 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test3 in CodekruTest class
-
- ===============================================
- codekru
- Total tests run: 5, Passes: 1, Failures: 2, Skips: 0, Retries: 2
我们可以看到test2()已经重试了两次。
有时候,对每个用例都使用retryAnalyzer需要花费时间,尤其是当类有很多测试用例的时候。为了保存我们,TestNG为我们提供类级注释的功能。我们只能在类级别上放置@Test((retryAnalyzer = Retry.class)一次,它将应用于类中的所有案例。
- package Test;
-
- import org.testng.Assert;
- import org.testng.annotations.Test;
-
- @Test(retryAnalyzer = Retry.class)
- public class CodekruTest {
-
- public void test1() {
- System.out.println("Executing test1 in CodekruTest class");
- Assert.assertTrue(true);
- }
-
- public void test2() {
- System.out.println("Executing test2 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case
- }
-
- public void test3() {
- System.out.println("Executing test3 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case as well
- }
- }
现在,所有的测试用例如果失败,将被重试。让我们通过执行上面的类来尝试一下。
- Executing test1 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test3 in CodekruTest class
- Executing test3 in CodekruTest class
- Executing test3 in CodekruTest class
test2()和test3()又执行了两次。
现在让我们讨论一下如何在套件中运行的所有案例上设置重试?
我们可以通过使用IAnnotationTransformer接口来实现这一点,在该接口中我们可以实现transform方法。我们将使用一个类(比如AnnotationTransformer)来实现IAnnotationTransformer接口。
这个接口用于在运行时以编程方式添加注释,并且它的transform方法在每个测试用例运行时执行。因此,我们将重写transform方法,并在运行时在我们的测试用例中添加retryAnalyzer功能。
- package Test;
-
- import org.testng.IAnnotationTransformer;
- import org.testng.annotations.ITestAnnotation;
- import java.lang.reflect.Constructor;
- import java.lang.reflect.Method;
-
- public class AnnotationTransformer implements IAnnotationTransformer {
-
- public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
- annotation.setRetryAnalyzer(Retry.class); // setting the retryAnalyzer
- }
-
- }
现在,如何使用AnnotationTransformer类,以便它可以将retryAnalyzer功能添加到我们的测试用例中。我们必须在testng.xml文件中添加这个类作为侦听器,然后由XML文件运行的所有案例都将添加retryAnalyzer属性。
现在,我们的CodekruTest类看起来像
- package Test;
-
- import org.testng.Assert;
- import org.testng.annotations.Test;
-
- public class CodekruTest {
-
- @Test
- public void test1() {
- System.out.println("Executing test1 in CodekruTest class");
- Assert.assertTrue(true);
- }
-
- @Test
- public void test2() {
- System.out.println("Executing test2 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case
- }
-
- @Test
- public void test3() {
- System.out.println("Executing test3 in CodekruTest class");
- Assert.assertTrue(false); // failing this test case as well
- }
- }
我们的XML文件将是
- <suite name="codekru">
- <listeners>
- <listener class-name="Test.AnnotationTransformer" />
- listeners>
- <test name="codekruTest">
- <classes>
- <class name="Test.CodekruTest" />
- classes>
- test>
- suite>
运行上述XML文件后的输出-
- Executing test1 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test3 in CodekruTest class
- Executing test3 in CodekruTest class
- Executing test3 in CodekruTest class
-
- ===============================================
- codekru
- Total tests run: 7, Passes: 1, Failures: 2, Skips: 0, Retries: 4
- ===============================================
我们可以看到,失败的案例被自动重试。
您可能已经注意到,每当我们重试案例时,我们的总案例数就会增加,跳过的案例数也会增加。以下是我们上次执行死刑的总结
注意:TestNG的新版本将Retries字段添加到摘要中。
- Executing test1 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test3 in CodekruTest class
- Executing test3 in CodekruTest class
- Executing test3 in CodekruTest class
-
- ===============================================
- codekru
- Total tests run: 7, Passes: 1, Failures: 2, Skips: 0, Retries: 4
- ===============================================

我们可以看到跳过的案例或重试计数为4,总案例计数为7。但是有时候,我们不想把重试算在所有的用例中,而只想显示在我们的测试类中已经写好的用例的数量。
在这里,我们将不得不使用多一个接口,ITestrons,和一个类(比如TestListenerClass)来实现接口。
- package Test;
-
- import org.testng.ITestListener;
- import org.testng.ITestResult;
-
- public class TestListenerClass implements ITestListener {
-
- public void onTestSuccess(ITestResult result) {
-
- if (result.getMethod().getRetryAnalyzer(result) != null) {
- int numberOfSkippedCases = result.getTestContext().getSkippedTests().getResults(result.getMethod()).size();
-
- for (int i = 0; i < numberOfSkippedCases; i++) {
- result.getTestContext().getSkippedTests().removeResult(result.getMethod());
- }
- }
-
- }
-
- public void onTestFailure(ITestResult result) {
-
- if (result.getMethod().getRetryAnalyzer(result) != null) {
- int numberOfSkippedCases = result.getTestContext().getSkippedTests().getResults(result.getMethod()).size();
-
- for (int i = 0; i < numberOfSkippedCases; i++) {
- result.getTestContext().getSkippedTests().removeResult(result.getMethod());
- }
- }
-
- }
-
- }
那我们来这里干什么
现在,我们必须在XML文件中添加TestListenerClass作为侦听器。因此,我们更新的XML文件将是
- <suite name="codekru">
- <listeners>
- <listener class-name="Test.AnnotationTransformer" />
- <listener class-name="Test.TestListenerClass" />
- listeners>
- <test name="codekruTest">
- <classes>
- <class name="Test.CodekruTest" />
- classes>
- test>
- suite>
运行上述XML文件后的输出,它再次执行我们的CodekruTest类的案例-
- Executing test1 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test2 in CodekruTest class
- Executing test3 in CodekruTest class
- Executing test3 in CodekruTest class
- Executing test3 in CodekruTest class
-
- ===============================================
- codekru
- Total tests run: 3, Passes: 1, Failures: 2, Skips: 0
- ===============================================
这里我们可以看到,显示的计数仅为3,而不是7。
尽管TestNG摘要看起来仍然像
