在 Junit 5 中,如何从扩展中调用测试 class 方法?
In Junit 5 how can I call a test class method from an extension?
在 Junit 5 中,我试图从扩展程序中获取 class 到 运行 的测试方法。我正在使用 Junit 5 扩展接口 TestWatcher,并重写了 testFailed() 方法。
此扩展的目的是在测试 class 的 Selenium WebDriver 浏览器中拍摄失败的屏幕截图,并将其附加到该测试的 Allure 报告中。测试 class 方法具有实例化的浏览器和用于附加到 Allure 的注释。我的 takeScreenshot 方法依赖于浏览器和从测试 class 到 运行 的 testName 字符串正确。
package utils;
public class ScreenshotOnFailureExtension implements TestWatcher{
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
try {
Object clazz = context.getRequiredTestInstance();
Method takeScreenshot = clazz.getClass().getMethod("takeScreenshot");
takeScreenshot.setAccessible(true);
Object test = clazz.getClass().getConstructor().newInstance();
takeScreenshot.invoke(test);
} catch (Exception e) {
e.printStackTrace();
}
}
我测试中的代码 class 是这样的:
package tests;
@ExtendWith(ScreenshotOnFailureExtension.class)
public class MyTest implements Config {
public WebDriver driver;
public String testName;
//bunch of Junit5 annotations with functions to initialize above variables omitted...
//take a screen shot
public void takeScreenshot() {
System.out.println("Taking screenshot.");
byte[] srcFile=((TakesScreenshot)driver).getScreenshotAs(OutputType.BYTES);
saveScreenshot(srcFile, testName+ ".png");
}
//this attaches screenshot to an allure test result
@Attachment(value = "{testName}", type = "image/png")
public byte[] saveScreenshot(byte[] screenShot, String testName) {
System.out.println("Attaching screenshot to Allure report");
return screenShot;
}
}
上面的测试class在测试方法中从@AfterEach调用时能够正确截屏。但我只想接受失败。
当我 运行 测试时它调用了 takeScreenshot,但在执行它时出现异常:
Taking screenshot.java.lang.reflect.InvocationTargetException
at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native
Method) at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566) at
utils.ScreenshotOnFailureExtension.testFailed(ScreenshotOnFailureExtension.java:49)
at
org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$nodeFinished(TestMethodTestDescriptor.java:299)
at
org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor.lambda$invokeTestWatchers(MethodBasedTestDescriptor.java:134)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at
org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor.invokeTestWatchers(MethodBasedTestDescriptor.java:132)
at
org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.nodeFinished(TestMethodTestDescriptor.java:290)
at
org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.nodeFinished(TestMethodTestDescriptor.java:65)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.reportCompletion(NodeTestTask.java:176)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:89)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at
org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:143)
at
org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129)
at
org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127)
at
org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at
org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:143)
at
org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129)
at
org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127)
at
org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at
org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at
org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at
org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at
org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at
org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at
org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at
org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute[=16=](EngineExecutionOrchestrator.java:54)
at
org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at
org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at
org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
at
org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
at
org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89)
at
org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
Caused by: java.lang.NullPointerException at
tests.Base.takeScreenshot(Base.java:240) ... 49 more
您可以看到我的日志语句在该方法的下一行代码(引用测试实例中的 driver
)导致的 NullPointerException 之前输出。是否有正确的方法在上下文中触发现有测试实例的 takeScreenshot()
方法?
或
如果有更简单的方法直接在测试的 @AfterEach
方法中拍摄失败的屏幕截图,请告诉我。似乎是一个非常基本的用例。 :)
您不应该在扩展中执行实例化测试 类 之类的操作,框架应该会处理所有事情。
请参考https://junit.org/junit5/docs/current/user-guide/#extensions 5.9.1 in the documentation, and look at
您可以使用它,也可以修改您的 TestWatcher 以按照评论中的建议进行屏幕截图。您必须将驱动程序引用保存在 ExtensionContext 中才能访问它。
IMO 问题出在您描述的流程中。
JUnit 为每个测试方法创建一个新的测试实例 class(尽管这可以更改)。
更好的方法是:
- 在某种意义上使扩展“有状态”,它将包含对 Web 驱动程序的引用。
- 不要在测试中实现
takeScreenshot
方法,而是在扩展(私有方法)中实现
- 在扩展中实现回调并将 WebDriver 的实例“注入”(通过反射)到测试中(如果您需要在测试中使用它)。这将保证测试以正确实例化的“状态”(webdriver 实例)运行。
- 在扩展中实现“如果测试方法失败则调用扩展的私有方法”的逻辑
takeScreenshot
解决方案最终看起来像这样。您可以在此处为 Selenium 测试添加其他操作,因为它会在测试 tear-down.
之前执行
如果您使用 Junit5 进行 Selenium 测试,您可以使用 AfterTestExecutionCallback,以便 RequiredTestInstance 包含对浏览器的引用和测试的最终结果!
package utils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class ActionsOnFailureExtension implements AfterTestExecutionCallback {
@Override
public void afterTestExecution(ExtensionContext context) throws Exception {
// if an ExecutionException is part of the context then the test failed
Boolean testFailed = context.getExecutionException().isPresent();
if (testFailed) {
// take a screenshot via Java reflection
try {
Object clazz = context.getRequiredTestInstance();
Method takeScreenshot = clazz.getClass().getMethod("takeScreenshot");
// 'takeScreenshot' is a method in my test class
// that uses the Selenium driver to take the screenshot
// and then attaches it to the Allure report
takeScreenshot.setAccessible(true);
takeScreenshot.invoke(clazz);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
在 Junit 5 中,我试图从扩展程序中获取 class 到 运行 的测试方法。我正在使用 Junit 5 扩展接口 TestWatcher,并重写了 testFailed() 方法。
此扩展的目的是在测试 class 的 Selenium WebDriver 浏览器中拍摄失败的屏幕截图,并将其附加到该测试的 Allure 报告中。测试 class 方法具有实例化的浏览器和用于附加到 Allure 的注释。我的 takeScreenshot 方法依赖于浏览器和从测试 class 到 运行 的 testName 字符串正确。
package utils;
public class ScreenshotOnFailureExtension implements TestWatcher{
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
try {
Object clazz = context.getRequiredTestInstance();
Method takeScreenshot = clazz.getClass().getMethod("takeScreenshot");
takeScreenshot.setAccessible(true);
Object test = clazz.getClass().getConstructor().newInstance();
takeScreenshot.invoke(test);
} catch (Exception e) {
e.printStackTrace();
}
}
我测试中的代码 class 是这样的:
package tests;
@ExtendWith(ScreenshotOnFailureExtension.class)
public class MyTest implements Config {
public WebDriver driver;
public String testName;
//bunch of Junit5 annotations with functions to initialize above variables omitted...
//take a screen shot
public void takeScreenshot() {
System.out.println("Taking screenshot.");
byte[] srcFile=((TakesScreenshot)driver).getScreenshotAs(OutputType.BYTES);
saveScreenshot(srcFile, testName+ ".png");
}
//this attaches screenshot to an allure test result
@Attachment(value = "{testName}", type = "image/png")
public byte[] saveScreenshot(byte[] screenShot, String testName) {
System.out.println("Attaching screenshot to Allure report");
return screenShot;
}
}
上面的测试class在测试方法中从@AfterEach调用时能够正确截屏。但我只想接受失败。
当我 运行 测试时它调用了 takeScreenshot,但在执行它时出现异常:
Taking screenshot.java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at utils.ScreenshotOnFailureExtension.testFailed(ScreenshotOnFailureExtension.java:49) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$nodeFinished(TestMethodTestDescriptor.java:299) at org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor.lambda$invokeTestWatchers(MethodBasedTestDescriptor.java:134) at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor.invokeTestWatchers(MethodBasedTestDescriptor.java:132) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.nodeFinished(TestMethodTestDescriptor.java:290) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.nodeFinished(TestMethodTestDescriptor.java:65) at org.junit.platform.engine.support.hierarchical.NodeTestTask.reportCompletion(NodeTestTask.java:176) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:89) at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:143) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:143) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute[=16=](EngineExecutionOrchestrator.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75) at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209) Caused by: java.lang.NullPointerException at tests.Base.takeScreenshot(Base.java:240) ... 49 more
您可以看到我的日志语句在该方法的下一行代码(引用测试实例中的 driver
)导致的 NullPointerException 之前输出。是否有正确的方法在上下文中触发现有测试实例的 takeScreenshot()
方法?
或
如果有更简单的方法直接在测试的 @AfterEach
方法中拍摄失败的屏幕截图,请告诉我。似乎是一个非常基本的用例。 :)
您不应该在扩展中执行实例化测试 类 之类的操作,框架应该会处理所有事情。
请参考https://junit.org/junit5/docs/current/user-guide/#extensions 5.9.1 in the documentation, and look at
您可以使用它,也可以修改您的 TestWatcher 以按照评论中的建议进行屏幕截图。您必须将驱动程序引用保存在 ExtensionContext 中才能访问它。
IMO 问题出在您描述的流程中。 JUnit 为每个测试方法创建一个新的测试实例 class(尽管这可以更改)。
更好的方法是:
- 在某种意义上使扩展“有状态”,它将包含对 Web 驱动程序的引用。
- 不要在测试中实现
takeScreenshot
方法,而是在扩展(私有方法)中实现 - 在扩展中实现回调并将 WebDriver 的实例“注入”(通过反射)到测试中(如果您需要在测试中使用它)。这将保证测试以正确实例化的“状态”(webdriver 实例)运行。
- 在扩展中实现“如果测试方法失败则调用扩展的私有方法”的逻辑
takeScreenshot
解决方案最终看起来像这样。您可以在此处为 Selenium 测试添加其他操作,因为它会在测试 tear-down.
之前执行如果您使用 Junit5 进行 Selenium 测试,您可以使用 AfterTestExecutionCallback,以便 RequiredTestInstance 包含对浏览器的引用和测试的最终结果!
package utils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class ActionsOnFailureExtension implements AfterTestExecutionCallback {
@Override
public void afterTestExecution(ExtensionContext context) throws Exception {
// if an ExecutionException is part of the context then the test failed
Boolean testFailed = context.getExecutionException().isPresent();
if (testFailed) {
// take a screenshot via Java reflection
try {
Object clazz = context.getRequiredTestInstance();
Method takeScreenshot = clazz.getClass().getMethod("takeScreenshot");
// 'takeScreenshot' is a method in my test class
// that uses the Selenium driver to take the screenshot
// and then attaches it to the Allure report
takeScreenshot.setAccessible(true);
takeScreenshot.invoke(clazz);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}