java 如何在测试套件中定义 JUnit 方法规则?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7639353/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 20:48:00  来源:igfitidea点击:

How to define a JUnit method rule in a test suite?

javajunitjunit4

提问by Sled

I have a class that is a JUnit suite of JUnit test classes. I would like to define a rule on the suiteto do something to the database before and after each unit test is run ifa certain annotation is present on that test method.

我有一个类是 JUnit 测试类的 JUnit 套件。如果该测试方法上存在某个注释,我想套件定义一个规则以便在运行每个单元测试之前和之后对数据库执行某些操作。

I've been able to create a @ClassRulein the suites and test classes that will do this before each and every class(which is not good enough) and I have been able to define the test rules with the test classes themselves, but this is repetitive and does not seem very DRY.

我已经能够@ClassRule在套件和测试类中创建一个将在每个之前执行此操作(这还不够好)并且我已经能够使用测试类本身定义测试规则,但这是重复的并且看起来不是很干。

Is it possible to define a per-test-method rule in the suite or must I add them to each and every test?

是否可以在套件中定义每个测试方法的规则,还是必须将它们添加到每个测试中?

Edit:To clarify, I want to declare code in a suite that will run between (i.e. "around") the test methods in the test classes.

编辑:为了澄清,我想在测试类中的测试方法之间(即“围绕”)运行的套件中声明代码。

回答by Matthew Farwell

This can be done, but it needs a bit of work. You need to define your own Suite runner and your own Test runner as well, and then override runChild() in the test runner. Using the following:

这可以做到,但需要做一些工作。您还需要定义自己的套件运行程序和自己的测试运行程序,然后在测试运行程序中覆盖 runChild()。使用以下内容:

AllTests.java:

AllTests.java:

@RunWith(MySuite.class)
@SuiteClasses({Class1Test.class})
public class AllTests {
}

Class1Test.java:

Class1Test.java:

public class Class1Test {
    @Deprecated @Test public void test1() {
        System.out.println("" + this.getClass().getName() + " test1");
    }

    @Test public void test2() {
        System.out.println("" + this.getClass().getName() + " test2");
    }
}

Note that I've annotated test1()with @Deprecated. You want to do something different when you have the @Deprecatedannotation on the test, so we need to extend Suite to use a custom Runner:

请注意,我已经test1()@Deprecated. 当您@Deprecated在测试中有注释时,您想要做一些不同的事情,因此我们需要扩展 Suite 以使用自定义Runner

public class MySuite extends Suite {
    // copied from Suite
    private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError {
        Suite.SuiteClasses annotation = klass.getAnnotation(Suite.SuiteClasses.class);
        if (annotation == null) {
            throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName()));
        }
        return annotation.value();
    }

    // copied from Suite
    public MySuite(Class<?> klass, RunnerBuilder builder) throws InitializationError {
        super(null, getRunners(getAnnotatedClasses(klass)));
    }

    public static List<Runner> getRunners(Class<?>[] classes) throws InitializationError {
        List<Runner> runners = new LinkedList<Runner>();

        for (Class<?> klazz : classes) {
            runners.add(new MyRunner(klazz));
        }

        return runners;
    }
}

JUnit creates a Runnerfor each test it will run. Normally, Suite would just create the default BlockJUnit4ClassRunner, all we're doing here is overriding the constructor for the Suite which reads the classes from the SuiteClassannotation and we're creating our own runners with them, MyRunner. This is our MyRunner class:

JUnitRunner为将要运行的每个测试创建一个。通常,Suite 只会创建 default BlockJUnit4ClassRunner,我们在这里所做的就是覆盖 Suite 的构造函数,该构造函数从SuiteClass注释中读取类,并且我们正在使用它们创建我们自己的 runners MyRunner。这是我们的 MyRunner 类:

public class MyRunner extends BlockJUnit4ClassRunner {
    public MyRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }

    @Override
    protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        Description description= describeChild(method);
        if (method.getAnnotation(Ignore.class) != null) {
            notifier.fireTestIgnored(description);
        } else {
            if (description.getAnnotation(Deprecated.class) != null) {
                System.out.println("name=" + description.getMethodName() + " annotations=" + description.getAnnotations());
            }
            runLeaf(methodBlock(method), description, notifier);
        }
    }
}

Most of this is copied from BlockJUnit4ClassRunner. The bit I've added is:

其中大部分是从BlockJUnit4ClassRunner. 我添加的一点是:

if (description.getAnnotation(Deprecated.class) != null) {
    System.out.println("name=" + description.getMethodName() + " annotations=" + description.getAnnotations());
}

where we test for the existence of the @Deprecatedannotation on the method, and do something if it's there. The rest is left as an exercise for the reader. When I run the above Suite, I get as output:

我们测试@Deprecated方法上的注解是否存在,如果它存在就做一些事情。其余部分留给读者作为练习。当我运行上述套件时,我得到如下输出:

name=test1 annotations=[@java.lang.Deprecated(), @org.junit.Test(expected=class org.junit.Test$None, timeout=0)]
uk.co.farwell.junit.run.Class1Test test1
uk.co.farwell.junit.run.Class1Test test2

Please note that Suite has multiple constructors depending upon how it is invoked. The above works with Eclipse, but I haven't tested other ways of running the Suite. See the comments alongside the various constructors for Suite for more information.

请注意,Suite 有多个构造函数,具体取决于它的调用方式。以上适用于 Eclipse,但我还没有测试其他运行套件的方法。有关更多信息,请参阅 Suite 的各种构造函数旁边的注释。

回答by Yishai

You can use a RunListenerthat you add to the Suite. It doesn't give you everything a Rule can do, but it will provide you a Description class which should have the annotations available. At least, I don't think JUnit filters it down to its understood annotations only.

您可以使用添加到套件的RunListener。它不会为您提供 Rule 可以做的所有事情,但它会为您提供一个 Description 类,该类应该具有可用的注释。至少,我认为 JUnit 不会仅将其过滤为可理解的注释。

The developer of JUnit just discussed the mechanics of adding a RunListener to a Suite here.

JUnit 的开发人员刚刚在这里讨论了向套件添加 RunListener 的机制。

回答by pholser

By itself, adding a rule to the class annotated with @RunWith(Suite.class)won't do the trick. I believe this is because a Suiteis a ParentRunner<Runner>rather than a Runnersuch as BlockJUnit4ClassRunnerwhich would attempt to scrape rules on the classes it runs. To run its children, it tells the child Runnersto run. Those Runners may have built up their tests by applying rules on those classes, but the Suiterunner doesn't take any special action to apply rules from itself to the tests its child Runners build.

就其本身而言,向带有注释的类添加规则是@RunWith(Suite.class)行不通的。我相信这是因为 aSuite是 aParentRunner<Runner>而不是 a Runner,因为BlockJUnit4ClassRunner它会试图在它运行的类上抓取规则。为了跑它的孩子,它告诉孩子Runners跑。那些Runners 可能已经通过在这些类上应用规则来构建他们的测试,但是Suiterunner 没有采取任何特殊操作来将自身的规则应用于其子级Runner构建的测试。

回答by LoganMzz

Have you tried to use "test class hierarchy" ? I often use abstract test class to share test or fixture. For example, all my DB test are initialising an embedded datasource. I first create an abstract "DbTestCase" class which handles the init logic. Then all subclass will benefit the test and the fixtures.

您是否尝试过使用“测试类层次结构”?我经常使用抽象测试类来共享测试或夹具。例如,我所有的数据库测试都在初始化一个嵌入式数据源。我首先创建一个抽象的“DbTestCase”类来处理初始化逻辑。然后所有子类都将受益于测试和夹具。

However, sometimes I encounter problem when my test cases requires many test/fixture-logic that I can't store in a single hierarchy. In this case, only aspect programming solves the issue. Marking test/fixture-logic through specific annotation/interfaces that any required-class can implement.

但是,有时当我的测试用例需要许多无法存储在单个层次结构中的测试/夹具逻辑时,我会遇到问题。在这种情况下,只有方面编程才能解决这个问题。通过任何必需类可以实现的特定注释/接口标记测试/夹具逻辑。

You can optionnaly consider handling the "aspect" using a custom runner that will "inject" test/fixture-logic depending on annotation/interface of tested classes.

您可以选择考虑使用自定义运行程序处理“方面”,该运行程序将根据测试类的注释/接口“注入”测试/夹具逻辑。

回答by ollins

You can group testswith TestNG. And you can configure TestNG to run some logic @BeforeGroup and @AfterGroup.

您可以使用 TestNG 对测试进行分组。你可以配置 TestNG 来运行一些逻辑@BeforeGroup 和 @AfterGroup