【问题标题】:running a subset of JUnit @Test methods运行 JUnit @Test 方法的子集
【发布时间】:2009-08-05 01:12:58
【问题描述】:

我们正在使用 JUnit 4 进行测试:我们的类不是 TestCase 的子类,并且它们具有使用 @Test 注释的公共方法。我们有一个包含许多@Test 方法的文件。如果能够通过 Ant 从命令行运行其中的一个子集,就像 JUnit 3 的这个秘籍的风格,那就太好了:

ant runtest -Dtest=MyTest -Dtests=testFoo,testBar

http://today.java.net/pub/a/today/2003/09/12/individual-test-cases.html

我一直在想办法通过 Java 反射等来实现这一点。由于似乎没有任何方法可以“隐藏”@Test 方法或在运行时删除它们的注释,唯一的选择似乎是要使用 ClassLoader 的 defineClass 方法,这似乎相当困难。

附:在这种情况下,正确的做法是拆分文件,但有其他选择吗?

感谢您的宝贵时间。

【问题讨论】:

    标签: java unit-testing reflection junit


    【解决方案1】:

    从 JUnit 4.12 开始,我们有 @Category annotations 来解决这个问题。

    【讨论】:

    • @Category 非常酷,但前提是您已经构建了套件。正如文章最后提到的那样,它并不适合每个人的测试环境。不过,他们绝对值得指出,因为对于一些幸运的人来说,这将完全解决他们的问题。
    • 从 Surefire 2.11(或更高版本)开始,我们可以使用 -Dgroups 按类别选择测试,而无需构建套件。由于这项改进,上周我很高兴删除了我们的许多测试套件! (Maven Surefire 插件的 参数仅记录在 TestNG 中,但由于 2.11 也适用于 JUnit。)
    • 与 TestNg 测试套件 xml 文件相比,这太不方便了。为什么 Junit 不允许我们在不注释测试方法的情况下选择方法? Junit 5 中解决了这个不便吗?
    【解决方案2】:

    guerda 的解决方案很好。这是我最终做的事情(它混合了我之前链接的 Luke Francl 的食谱,以及我在网上看到的其他一些东西):

    import org.junit.runner.manipulation.Filter;
    import org.junit.runner.Description;
    
    public final class AntCLFilter extends Filter {
        private static final String TEST_CASES = "tests";
        private static final String ANT_PROPERTY = "${tests}";
        private static final String DELIMITER = "\\,";
        private String[] testCaseNames;
    
        public AntCLFilter() {
            super();
            if (hasTestCases()) testCaseNames = getTestCaseNames();
        }
    
        public String describe() {
            return "Filters out all tests not explicitly named in a comma-delimited list in the system property 'tests'.";
        }
    
        public boolean shouldRun(Description d) {
            String displayName = d.getDisplayName();
            // cut off the method name:
            String testName = displayName.substring(0, displayName.indexOf('('));
            if (testCaseNames == null) return true;
    
            for (int i = 0; i < testCaseNames.length; i++)
                if (testName.equals(testCaseNames[i]))
                    return true;
            return false;
        }
    
        /**
         * Check to see if the test cases property is set. Ignores Ant's
         * default setting for the property (or null to be on the safe side).
         **/
        public static boolean hasTestCases() {
            return
                System.getProperty( TEST_CASES ) == null ||
                System.getProperty( TEST_CASES ).equals( ANT_PROPERTY ) ?
                false : true;
        }
    
        /**
         * Create a List of String names of test cases specified in the
         * JVM property in comma-separated format.
         *
         * @return a List of String test case names
         *
         * @throws NullPointerException if the TEST_CASES property
         * isn't set
         **/
        private static String[] getTestCaseNames() {
    
            if ( System.getProperty( TEST_CASES ) == null ) {
                throw new NullPointerException( "Test case property is not set" );
            }
    
            String testCases = System.getProperty( TEST_CASES );
            String[] cases = testCases.split(DELIMITER);
    
            return cases;
        }
    }
    
    import org.junit.internal.runners.*;
    import org.junit.runner.manipulation.Filter;
    import org.junit.runner.manipulation.NoTestsRemainException;
    
    public class FilteredRunner extends TestClassRunner {
    
        public FilteredRunner(Class<?> clazz) throws InitializationError {
            super(clazz);
            Filter f = new AntCLFilter();
            try {
                f.apply(this);
            } catch (NoTestsRemainException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
    

    然后我用以下注释注释了我的测试类:

    @RunWith(FilteredRunner.class)
    public class MyTest {
    

    并将以下内容放入我的 ant 构建文件中:

    <target name="runtest"
            description="Runs the test you specify on the command line with -Dtest="
            depends="compile, ensure-test-name">
        <junit printsummary="withOutAndErr" fork="yes">
            <sysproperty key="tests" value="${tests}" />
            <classpath refid="classpath" />
            <formatter type="plain" usefile="false" />
            <batchtest>
                <fileset dir="${src}">
                    <include name="**/${test}.java" />
                </fileset>
            </batchtest>
        </junit>
    </target>
    

    关键行是 sysproperty 标记。

    现在我可以跑了

    ant runtest -Dtest=MyTest -Dtests=testFoo,testBar
    

    根据需要。这适用于 JUnit 4.1 --- 在 4.4 中,来自 JUnit4ClassRunner 的子类,在 4.5 及更高版本中,来自 BlockJUnit4ClassRunner 的子类。

    【讨论】:

    • 好的,这比我的解决方案优雅得多:)
    • 几天来我一直在为同样的(或至少非常相似的)问题苦苦挣扎,有一件事一直困扰着我。如果您想将 FilteredRunner 与 Powermock 一起使用,这还需要它自己的 @RunWith(PowermockRunner.class) 注释怎么办?
    • 很好的答案,但现在已经过时了。我认为需要使用 BlockJUnit4ClassRunner 而不是 TestClassRunner
    【解决方案3】:

    创建您自己的TestClassMethodsRunner(没有文档记录,或者我现在找不到)。
    一个TestClassMethodsRunner 执行所有的TestCases,你可以设置一个过滤的TestClassMethodsRunner

    您所要做的就是覆盖TestMethodRunner createMethodRunner(Object, Method, RunNotifier) 方法。这是一个简单的 hacky 解决方案:

    public class FilteredTestRunner extends TestClassMethodsRunner {
    
        public FilteredTestRunner(Class<?> aClass) {
            super(aClass);
        }
    
        @Override
        protected TestMethodRunner createMethodRunner(Object aTest, Method aMethod, RunNotifier aNotifier) {
            if (aTest.getClass().getName().contains("NOT")) {
                return new TestMethodRunner(aTest, aMethod, aNotifier, null) {
                    @Override
                    public void run() {
                        //do nothing with this test.
                    }
                };
            } else {
                return super.createMethodRunner(aTest, aMethod, aNotifier);
            }
        }
    
    }
    

    使用此 TestRunner,您可以执行所有不包含字符串“NOT”的测试。其他的将被忽略 :) 只需将 @RunWith 注释与您的 TestRunner 类一起添加到您的测试中。

    @RunWith(FilteredTestRunner.class)
    public class ThisTestsWillNOTBeExecuted {
       //No test is executed.
    }
    
    @RunWith(FilteredTestRunner.class)
    public class ThisTestsWillBeExecuted {
       //All tests are executed.
    }
    

    createMethodRunner 方法中,您可以对照必须执行的测试列表或引入新标准来检查当前测试。

    祝你好运!

    感谢您提供更好解决方案的提示!

    【讨论】:

    • 与 TestNg 测试套件 xml 文件相比,这太不方便了。为什么 Junit 不允许我们在不注释测试方法的情况下选择方法? Junit 5 中解决了这个不便吗?
    【解决方案4】:

    对于只需要运行一种测试方法的常见情况,有一种更简单的方法,而无需经历创建自定义RunnerFilter 的麻烦:

    public class MyTestClass {
    
      public static void main(final String[] args) throws Exception {
        final JUnitCore junit = new JUnitCore();
        final String singleTest = // Get the name of test from somewhere (environment, system property, whatever you want).
        final Request req;
        if (singleTest != null) {
          req = Request.method(MyTestClass.class, singleTest);
        } else {
          req = Request.aClass(MyTestClass.class);
        }
        final Result result = junit.run(req);
        // Check result.getFailures etc.
        if (!result.wasSuccessful()) {
          System.exit(1);
        }
      }
    
      // Your @Test methods here.
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-26
      • 2021-04-30
      • 1970-01-01
      相关资源
      最近更新 更多