【问题标题】:How to define a JUnit method rule in a test suite?如何在测试套件中定义 JUnit 方法规则?
【发布时间】:2011-10-03 18:55:31
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: java junit junit4


    【解决方案1】:

    这可以做到,但需要一些工作。您还需要定义自己的 Suite runner 和 Test runner,然后在 test runner 中覆盖 runChild()。使用以下内容:

    AllTests.java:

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

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

    请注意,我已将 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
    

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

    【讨论】:

      【解决方案2】:

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

      JUnit 的开发者刚刚讨论了将 RunListener 添加到 Suite here 的机制。

      【讨论】:

        【解决方案3】:

        单独向带有@RunWith(Suite.class) 注释的类添加规则并不能解决问题。我相信这是因为SuiteParentRunner&lt;Runner&gt; 而不是Runner,例如BlockJUnit4ClassRunner,它会尝试在它运行的类上刮取规则。为了运行它的孩子,它告诉孩子Runners 运行。那些Runners 可能已经通过在这些类上应用规则来构建他们的测试,但是Suite 运行器不会采取任何特殊操作来将自身的规则应用到其子Runners 构建的测试中。

        【讨论】:

        • 那么目前没有办法吗?说得通。我想知道单个测试会失败的情况,因为它假定了套件设置的资源。同样有意义的是,一个套件只会按原样运行单元测试,而不能进入他们的生命周期,同时仍然能够围绕它进行挂钩
        • 不过,我会尝试@Yishai 的建议。
        【解决方案4】:

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

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

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

        【讨论】:

        • 层次结构的想法虽然是合理的,但方面就像黑魔法或芥末:应该非常谨慎地使用它们,即使这样也只能谨慎使用。
        【解决方案5】:

        您可以使用 TestNG group tests。并且你可以配置TestNG来运行一些逻辑@BeforeGroup and @AfterGroup

        【讨论】:

        • 我认为这不是我想要的。我想要在每次测试之间运行的东西。
        • 但是如果你有一组测试应该可以使用 BeforeMethod 和 AfterMethod。 (我猜)
        • 也许,无论如何,我需要使用 JUnit 并且 TestNG 不是一个选项。
        猜你喜欢
        • 1970-01-01
        • 2014-07-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-15
        • 1970-01-01
        • 1970-01-01
        • 2017-12-20
        相关资源
        最近更新 更多