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

时间:2011-10-03 18:55:31

标签: java junit junit4

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

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

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

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

5 个答案:

答案 0 :(得分:14)

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

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

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");
    }
}

请注意,我已使用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为它将运行的每个测试创建一个Runner。通常,Suite只会创建默认的BlockJUnit4ClassRunner,我们在这里所做的就是覆盖Suite的构造函数,它从SuiteClass注释中读取类,我们用它们创建自己的运行器, 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);
        }
    }
}

大部分内容都是从BlockJUnit4ClassRunner复制的。我添加的位是:

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

我们在那里测试方法上是否存在@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

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

答案 1 :(得分:2)

您可以使用添加到套件中的RunListener。它没有为您提供规则可以执行的所有操作,但它将为您提供一个应该具有注释的Description类。至少,我认为JUnit不会将其过滤到其理解的注释中。

JUnit的开发人员刚刚讨论了将RunListener添加到套件here的机制。

答案 2 :(得分:1)

单独向使用@RunWith(Suite.class)注释的类添加规则将无法解决问题。我认为这是因为SuiteParentRunner<Runner>而不是Runner,例如BlockJUnit4ClassRunner,它会尝试在其运行的类上删除规则。要运行其子项,它会告诉孩子Runners运行。那些Runner可能通过对这些类应用规则来构建测试,但Suite运行器不会采取任何特殊操作将规则从其自身应用于其子Runner的测试建造。

答案 3 :(得分:0)

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

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

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

答案 4 :(得分:-1)

您可以使用TestNG group tests。您可以将TestNG配置为运行某些逻辑@BeforeGroup and @AfterGroup