我正在为我的Java项目编写JUnit 5测试。
我有一些测试方法需要耗时的清理工作(每个方法之后)。理想情况下,我想用一些注释标记它们,并只为它们运行清除方法。
这是我尝试过的:
class MyTest {
@AfterEach
@Tag("needs-cleanup")
void cleanup() {
//do some complex stuff
}
@Test
void test1() {
//do test1
}
@Test
@Tag("needs-cleanup")
void test2() {
//do test2
}
}
我希望仅在cleanup
之后运行test2
方法。但是它实际上在两个测试之后都可以运行。
是否可以通过JUnit 5注释的某种组合来实现?我不想将测试类分为几个类,也不想直接从测试方法中调用cleanup
。
答案 0 :(得分:3)
您可以将test注入测试中,并检查测试用哪些标签进行了标注:
class MyTest {
private TestInfo testInfo;
MyTest(TestInfo testInfo) {
this.testInfo = testInfo;
}
@AfterEach
void cleanup() {
if (this.testInfo.getTags().contains("needs-cleanup")) {
// .. do cleanup
}
}
@Test
void test1() {
//do test1
}
@Test
@Tag("needs-cleanup")
void test2() {
//do test2
}
}
答案 1 :(得分:2)
您可以创建自己的AfterEachCallback
扩展名并将其应用于所需的测试方法。此扩展将在应用每个测试后执行。然后,可以使用自定义注释将特定的清理方法与特定的测试链接在一起。这是扩展程序的示例:
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.List;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.platform.commons.support.AnnotationSupport;
import org.junit.platform.commons.support.HierarchyTraversalMode;
public class CleanupExtension implements AfterEachCallback {
private static final Namespace NAMESPACE = Namespace.create(CleanupExtension.class);
private static boolean namesMatch(Method method, String name) {
return method.getAnnotation(CleanMethod.class).value().equals(name);
}
private static Exception suppressOrReturn(final Exception previouslyThrown,
final Exception newlyThrown) {
if (previouslyThrown == null) {
return newlyThrown;
}
previouslyThrown.addSuppressed(newlyThrown);
return previouslyThrown;
}
@Override
public void afterEach(final ExtensionContext context) throws Exception {
final Method testMethod = context.getRequiredTestMethod();
final Cleanup cleanupAnno = testMethod.getAnnotation(Cleanup.class);
final String cleanupName = cleanupAnno == null ? "" : cleanupAnno.value();
final List<Method> cleanMethods = getAnnotatedMethods(context);
final Object testInstance = context.getRequiredTestInstance();
Exception exception = null;
for (final Method method : cleanMethods) {
if (namesMatch(method, cleanupName)) {
try {
method.invoke(testInstance);
} catch (Exception ex) {
exception = suppressOrReturn(exception, ex);
}
}
}
if (exception != null) {
throw exception;
}
}
@SuppressWarnings("unchecked")
private List<Method> getAnnotatedMethods(final ExtensionContext methodContext) {
// Use parent (Class) context so methods are cached between tests if needed
final Store store = methodContext.getParent().orElseThrow().getStore(NAMESPACE);
return store.getOrComputeIfAbsent(
methodContext.getRequiredTestClass(),
this::findAnnotatedMethods,
List.class
);
}
private List<Method> findAnnotatedMethods(final Class<?> testClass) {
final List<Method> cleanMethods = AnnotationSupport.findAnnotatedMethods(testClass,
CleanMethod.class, HierarchyTraversalMode.TOP_DOWN);
for (final Method method : cleanMethods) {
if (method.getParameterCount() != 0) {
throw new IllegalStateException("Methods annotated with "
+ CleanMethod.class.getName() + " must not have parameters: "
+ method
);
}
}
return cleanMethods;
}
@ExtendWith(CleanupExtension.class)
@Retention(RUNTIME)
@Target(METHOD)
public @interface Cleanup {
String value() default "";
}
@Retention(RUNTIME)
@Target(METHOD)
public @interface CleanMethod {
String value() default "";
}
}
然后您的测试类如下:
import org.junit.jupiter.api.Test;
class Tests {
@Test
@CleanupExtension.Cleanup
void testWithExtension() {
System.out.println("#testWithExtension()");
}
@Test
void testWithoutExtension() {
System.out.println("#testWithoutExtension()");
}
@Test
@CleanupExtension.Cleanup("alternate")
void testWithExtension_2() {
System.out.println("#testWithExtension_2()");
}
@CleanupExtension.CleanMethod
void performCleanup() {
System.out.println("#performCleanup()");
}
@CleanupExtension.CleanMethod("alternate")
void performCleanup_2() {
System.out.println("#performCleanup_2()");
}
}
正在运行Tests
,我得到以下输出:
#testWithExtension()
#performCleanup()
#testWithExtension_2()
#performCleanup_2()
#testWithoutExtension()
此扩展名将应用于用CleanupExtension.Cleanup
或ExtendWith(CleanupExtension.class)
注释的任何测试方法。前一个注释的目的是将配置与也应用扩展的注释结合起来。然后,在每个测试方法之后,扩展将调用用CleanupExtension.CleanMethod
注释的类层次结构中的任何方法。 Cleanup
和CleanMethod
都具有String
属性。此属性是“名称”,只有与CleanMethod
测试具有匹配“名称”的Cleanup
才会被执行。这使您可以将特定的测试方法链接到特定的清除方法。
有关JUnit Jupiter扩展的更多信息,请参见§5 of the User Guide。另外,对于CleanupExtension.Cleanup
,我正在使用§3.1.1中所述的元注释/组合注释功能。
请注意,这比 @Roman Konoval 给出的the answer更为复杂,但是如果您必须多次执行此类操作,则可能对用户更友好。但是,如果您只需要针对一两个测试班级进行此操作,那么我建议您使用Roman的答案。
答案 2 :(得分:2)
摘自文档:
TestInfo::如果方法参数的类型为TestInfo,则 TestInfoParameterResolver将提供TestInfo的实例 对应于当前测试作为参数值。的 然后可以使用TestInfo检索有关当前信息 测试,例如测试的显示名称,测试类,测试方法, 或相关标签。显示名称可以是技术名称,例如 作为测试类或测试方法的名称,或自定义名称 通过@DisplayName配置。
TestInfo代替了以下内容中的TestName规则 JUnit4。
关于上面的描述,您可以使用TestInfo类,该类为您提供应运行cleanUp的类的信息,然后您需要检查条件并通过检查其标签来允许想要的那些人:
@AfterEach
void afterEach(TestInfo info) {
if(!info.getTags().contains("cleanItUp")) return; // preconditioning only to needs clean up
//// Clean up logic Here
}
@Test
@Tag("cleanItUp")
void myTest() {
}