向TestNG失败添加自定义消息

时间:2014-08-01 19:21:29

标签: java exception junit exception-handling testng

我正在将测试框架从JUnit迁移到TestNG。该框架用于使用Selenium执行大型端到端集成测试,需要几分钟才能运行,并且包含数十个浏览器页面的数百个步骤。

免责声明:据我所知,这使得单元测试理想主义者非常不安,但大多数面向大型服务的公司都需要进行此类测试,并且使用单元测试工具来管理这些集成测试是目前最普遍的解决方案。这不是我的决定。这就是我被要求做的工作,并且我试图充分利用它。

无论如何,这些测试经常失败(意外)并且使它们易于调试非常重要。出于这个原因,我们希望检测之前报告的测试失败,附加有关失败的一些信息,然后允许JUnit使用这些额外信息失败。例如,如果没有此信息,则可能会出现以下情况:

java.lang.<'SomeObscureException'>: <'Some obscure message'> at <'StackTrace'>

但是添加的信息看起来像是:

java.lang.AssertionError:
Reproduction Seed: <'Random number used to generate test case'>
Country: <'Country for which test was set to run'>
Language: <'Localized language used by test'>
Step: <'Test step where the exception occurred'>
Exception Message: <'Message explaining probable cause of failure'>
Associated Exception Type: <'SomeObscureException'>
Associated Exception Message: <'Some obscure message'>
Associated Exception StackTrace: <'StackTrace'>
Exception StackTrace: <'StackTrace where we appended this information'>

值得注意的是,我们会在测试实际失败之前添加此信息。因为我们的报告工具完全基于JUnit抛出的异常,所以这确保了我们需要的信息存在于这些异常中。理想情况下,我希望在测试失败后但在执行拆解之前使用记者类将此信息添加到HTML或XML文档中,然后修改我们的报告工具以获取此额外信息并将其附加到我们的电子邮件中报告。然而,这在我们的sprint计划会议上一直很难卖,并且我没有在任何时候分配它(为开发人员运行无休止的回归比在测试框架本身上工作更优先。这就是生命现代SDET)。我也非常相信平衡,拒绝切入我生命的其他部分,以便在追踪时间之外完成这项工作。

我们目前正在做的是:

public class SomeTests extends TestBase {

    @Test
    public void someTest() {
        // Test code
    }

    // More tests
}

public abstract class TestBase {

    @Rule
    public MyWatcher watcher = new MyWatcher();

    // More rules and variables

    @Before
    public final void setup() {
        // Read config, generate test data, create Selenium WebDriver, etc.
        // Send references to all test objects to MyWatcher
    }
}

public class MyWatcher extends TestWatcher {

    // Test object references

    @Override
    public void failed(Throwable throwable, Description description) {
        StringBuilder sb = new StringBuilder();

        // Append custom test information to sb.

        String exceptionSummary = sb.toString();
        Assert.fail(exceptionSummary);
    }

    @Override
    public void finished(Description description) {
        // Shut down Selenium WebDriver, kill proxy server, etc.
    }

    // Miscellaneous teardown and logging methods
}
  1. JUnit开始。
  2. SomeTests继承自TestBase课程。 TestBase通过TestWatcher注释(@Rule)实例化我们自己的MyWatcher实例。
  3. 测试设置在TestBase课程中运行。
  4. 对测试对象的引用将发送到MyWatcher
  5. JUnit开始someTest()方法。
  6. someTest在某些时候失败了。
  7. JUnit调用failed()中的重写MyWatcher方法。
  8. failed()方法使用TestBase传递的引用将自定义测试信息附加到新邮件。
  9. failed()方法使用自定义消息调用JUnit的Assert.fail()方法。
  10. JUnit使用自定义消息为此新故障引发java.lang.Assertion错误。这是实际记录在测试结果中的例外情况。
  11. JUnit调用覆盖的finished()方法。
  12. finished()方法执行测试拆解。
  13. 我们的报告工具会收集JUnit抛出的汇总错误,并将它们包含在我们收到的电子邮件中。这使得生活比调试原始异常更容易,原始失败后MyWatcher没有添加任何额外信息。
  14. 我现在想要使用TestNG实现类似的机制。我首先尝试在@Listener注释中添加IInvokedMethodListener到我们的TestBase类,作为替换我们在JUnit中使用的TestWatcher的方法。不幸的是,在每次@BeforeMethod@AfterMethod调用以及实际测试之后,都会调用此侦听器中的方法。当我从IInvokedMethodListener内部调用Assert.fail时,这会造成相当大的混乱,因此我选择废弃此方法并将代码直接插入@AfterMethod类的TestBase调用中。< / p>

    不幸的是,TestNG似乎没有处理过两次失败的问题。我们在JUnit中使用的方法。当我在已经失败的测试的@AfterMethod中调用Assert.fail时,它会被报告为额外的失败。似乎我们必须提出另一种方法,直到我可以获得编写适当的测试记者的授权,其中包括我们需要调试的信息。

    与此同时,我们仍需要修饰TestNG抛出的异常,以便调试信息显示在我们的电子邮件报告中。我这样做的一个想法是将每个测试包装在try / catch块中。如果测试失败(抛出异常),那么我们可以捕获该异常,将其装入一个摘要异常中,并将调试信息添加到该异常消息中,并使用我们新的汇总异常调用Assert.fail。这样,TestNG只能看到一个例外,并且只应报告一个失败。尽管如此,这感觉就像是一个kludge上面的污垢,我无法帮助,但我觉得有更好的方法。

    有人知道修改TestNG报告内容的更好方法吗?是否有某种技巧可以用ITestContextITestResult替换我自己的原始异常?我可以在某个地方潜水并从某个列表中删除原始故障,还是在我到达@AfterMethod功能时停止TestNG的内部报告已经太晚了?

    您是否对此类测试或异常处理有任何其他建议?我没有很多知识渊博的同事来帮助解决这些问题,所以我只是勉强支持它。

2 个答案:

答案 0 :(得分:1)

您可以在testNG中使用SoftAssert类来实现上述方案。 SoftAssert类有一个哈希映射数组,它在测试用例中存储Asserts的所有错误消息,并在测试用例的末尾打印它们。您还可以扩展Assertion类以根据您的要求实现方法。

有关SoftAssert类及其实现的更多信息,请参见here

答案 1 :(得分:0)

实施IInvokedMethodListener

public class InvokedMethodListener implements IInvokedMethodListener {
    @Override
    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
    }

    @Override
    public void afterInvocation(IInvokedMethod method, ITestResult result) {
        if (method.isTestMethod() && ITestResult.FAILURE == result.getStatus()) {
            Throwable throwable = result.getThrowable();
            String originalMessage = throwable.getMessage();
            String newMessage = originalMessage + "\nReproduction Seed: ...\nCountry: ...";
            try {
                FieldUtils.writeField(throwable, "detailMessage", newMessage, true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

在测试中注册:

@Listeners(InvokedMethodListener.class)
public class YourTest {
    @Test
    public void test() {
        Assert.fail("some message");
    }
}

testng.xml

如果你执行它,你应该得到:

java.lang.AssertionError: some message
Reproduction Seed: ...
Country: ...