首页 > 解决方案 > 务实地允许用户选择将运行哪些 JUnit 测试类

问题描述

我正在尝试将类列表作为参数传递。(我可以这样做吗?)我正在使用 JUnit 和 Selenium,我有由 JUnit 测试套件类调用的 JUnit 测试类,使用@SuiteClasses()并且该测试套件类由包含main(). 我的想法是允许用户从将存储在某种列表中的主类中选择 JUnit 类。调用要运行的 JUnit 测试类的测试套件将使用该列表并调用这些 JUnit 类。


原始代码:调用应该运行的 JUnit 测试类的测试套件类(工作)⬇</p>

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({ TestCase1.class, TestCase2.class})

public class AllTests {

}

我正在尝试将其更改为⬇</p>

@SuiteClasses(runnerClass.classesToTest)

在跑步者课上,我会有这样的东西。我在想,我也许可以从 prop 文件中提取类的名称,并允许用户选择将哪些类添加到变量classesToTest中⬇</p>

public class runnerClass {    
   public static Class<?>[] classesToTest = { testCase1.class, testCase2.class };
   public static void main(String[] args) {
      ...
   }
}

当我尝试做这样的事情时,我得到了这个错误⬇</p>

注释属性 Suite.SuiteClasses.value 的值必须是类文字

@SuiteClasses() 的 JavaDoc


所以问题是,我可以完成这项工作吗?我是否classesToTest错误地创建了我的变量?

标签: javajunittest-suitejunit-runner

解决方案


我在 JUnit 框架中找不到任何解决方案,所以我编写了一个快速而肮脏的测试运行器。它只是调用所有用@Test 注释的方法,甚至是不可访问的方法(以防万一)。

它不适用于任何包含 IDE 的 UnitTest 结果显示工具。

它的用法如下:

public static void main(String[] args) {
    Runner run = new Runner(TestCase.class, TestCase2.class);
    for(Exception e : run.runUnchecked()) {
        System.err.println(e.getCause());
    }
}

您可以将类作为可变参数或普通数组传递,两者都可以。Runner 将返回测试的异常列表。如果测试失败,则抛出异常,或者是导致失败的异常,或者如果断言失败,则AssertionFailedError抛出 a。您可以使用 轻松打印一行描述e.getCause(),这将显示如下消息:org.opentest4j.AssertionFailedError: expected: <1> but was: <2>

我的示例代码适用于 JUnit Jupiter 测试,您可以通过更改Test在 Runner 类中导入的类来调整它。这必须相同,用于您的测试用例。

这是代码

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.Test;

/**
 * A simple JUnit Test Case Runner, which can dynamically call TestCases by
 * their class.
 * 
 * @author Alexander Daum
 *
 */
public class Runner {
    private Class<?>[] testCases;

    public Runner(Class<?>... testCases) {
        this.testCases = testCases;
    }

    /**
     * Run all TestCases given in the constructor of this Runner.
     * 
     * @throws InvocationTargetException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public List<Exception> run()
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
        List<Exception> testErrors = new ArrayList<>();
        for (Class<?> testClazz : testCases) {
            Object testCase = testClazz.newInstance();
            Method[] methods = testClazz.getDeclaredMethods();
            methods = Arrays.stream(methods).filter(m -> m.isAnnotationPresent(Test.class)).toArray(Method[]::new);
            for (Method m : methods) {
                m.setAccessible(true);
                try {
                    m.invoke(testCase);
                } catch (InvocationTargetException e) {
                    testErrors.add(e);
                }
            }
        }
        return testErrors;
    }

    /**
     * The same as {@link Runner#run()}, but all exceptions are wrapped in
     * RuntimeException, so no try catch is neccessary, when Errorhandling is not
     * required
     */
    public List<Exception> runUnchecked() {
        try {
            return run();
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
                | InstantiationException e) {
            throw new RuntimeException(e);
        }
    }
}

推荐阅读