【问题标题】:Excluding a non param test in parameterized test class在参数化测试类中排除非参数测试
【发布时间】:2010-07-29 09:55:21
【问题描述】:

JUnit 中是否有任何注释可以排除参数化测试类中的非参数测试?

【问题讨论】:

  • 目的是什么?您希望 @Parameters 在您的测试用例中使用什么?

标签: java junit parameterized


【解决方案1】:

JUnit 5

从 Junit 5.0.0 开始,您现在可以使用 @ParameterizedTest 注释您的测试方法。所以不需要内部类。除了 ValueSource 之外,还有许多方法可以为参数化测试提供参数,如下所示。详情见official junit user guide:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

public class ComponentTest {

    @ParameterizedTest
    @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
    public void testCaseUsingParams(String candidate) throws Exception {
    }

    @Test
    public void testCaseWithoutParams() throws Exception {
    }
}

JUnit 4

如果您仍在使用 Junit 4(我使用 v4.8.2 进行了测试),您可以将封闭式运行器与内部类和参数化运行器结合使用:

import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Enclosed.class)
public class ComponentTest {

    @RunWith(Parameterized.class)
    public static class ComponentParamTests {

        @Parameters
        ...

        @Test
        public void testCaseUsingParams() throws Exception {
        }
    }

    public static class ComponentSingleTests {

        @Test
        public void testCaseWithoutParams() throws Exception {
        }
    }
}

【讨论】:

  • 不错且清晰的解决方案,您可以在同一模块中很好地分离参数化测试和单个测试。
  • 不错的解决方案,但是它运行了两次测试! Enclosed.class 是 Suite.class 的扩展,它使所有 Suite 运行两次,而不是本例中的参数数量。
  • 我也是这样做的。但我希望 nonParametrized 测试仅在 Parametrized 之后运行。不能让它这样运行。我也试过FixMethodOrder。
  • 神奇的解决方案。谢谢!
【解决方案2】:

没有。最佳做法是将这些非参数化测试移动到不同的类(.java 文件)

【讨论】:

  • 一个模块 = 一个测试类是与此实践相冲突的另一种最佳实践。这更像是一种解决方法,而不是最佳实践。
  • 你对这个断言有任何理由吗?这意味着一个给定的类将在两个不同的测试类中进行测试,这也让我觉得这是个坏主意。
  • @scubbo 我不明白为什么在两个不同的类中进行测试是个坏主意。即使没有参数化测试,我也为某些类做到了这一点。例如 Foo_SecurityTest、Foo_BusinessLogicTest。
【解决方案3】:

Zohhak test runner 是一种参数化特定测试的更简单方法。 谢谢彼得!

【讨论】:

    【解决方案4】:

    我能够做一些与 Matthew Madson 的回答非常相似的事情,并发现创建一个基类来封装单测试和参数测试之间的设置和常用帮助函数很有用。这可以在不使用 Enclosed.class 的情况下工作。

     @RunWith(Suite.class)
     @SuiteClasses({ComponentTest.ComponentParamTests.class, ComponentTest.ComponentSingleTests.class})
     public class ComponentTest {
    
        public static class TestBase {
            @Spy
            ...
            @Before
            ...
        }
    
        @RunWith(Parameterized.class)
        public static class ComponentParamTests extends TestBase{
            @Parameter
            ...
            @Parameters
            ...
            @Test
            ...
        }
        public static class ComponentSingleTests extends TestBase{
            @Test
            ...
        }
    }
    

    【讨论】:

    • 很好的解决方案。我唯一要补充的是,您可以使用@RunWith(Suite.class) 和@SuiteClasses({ ComponentTest.ComponentParamTests.class, ComponentTest.ComponentParamTests.class }) 注释外部类ComponentTest 以使其可运行。然后它将在两个嵌套类中运行所有测试。来源:junit.sourceforge.net/javadoc/org/junit/runners/Suite.html
    • 当我想在这个类中运行所有测试时效果很好,以前我必须选择一个或另一个。但是,在包/项目级别运行单元测试时,这两个类都运行了。谢谢!
    【解决方案5】:

    看来TestNG does not suffer from this problem。 我并没有那么绝望,所以我修改了内置的参数化类来支持这个特性。只需将适用的测试注释为@NonParameterized。 请注意,此类仅适用于其 on 注释,即检查您的导入。

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.lang.reflect.Modifier;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Iterator;
    import java.util.List;
    
    import org.junit.Test;
    import org.junit.runner.Runner;
    import org.junit.runner.notification.RunNotifier;
    import org.junit.runners.BlockJUnit4ClassRunner;
    import org.junit.runners.Suite;
    import org.junit.runners.model.FrameworkMethod;
    import org.junit.runners.model.InitializationError;
    import org.junit.runners.model.Statement;
    import org.junit.runners.model.TestClass;
    
    /**
     * <p>
     * The custom runner <code>Parameterized</code> implements parameterized tests.
     * When running a parameterized test class, instances are created for the
     * cross-product of the test methods and the test data elements.
     * </p>
     * For example, to test a Fibonacci function, write:
     *
     * <pre>
     * &#064;RunWith(Parameterized.class)
     * public class FibonacciTest {
     *     &#064;Parameters
     *     public static List&lt;Object[]&gt; data() {
     *         return Arrays.asList(new Object[][] {
     *                 Fibonacci,
     *                 { {0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5},
     *                         {6, 8}}});
     *     }
     *
     *     private int fInput;
     *
     *     private int fExpected;
     *
     *     public FibonacciTest(int input, int expected) {
     *         fInput = input;
     *         fExpected = expected;
     *     }
     *
     *     &#064;Test
     *     public void test() {
     *         assertEquals(fExpected, Fibonacci.compute(fInput));
     *     }
     * }
     * </pre>
     * <p>
     * Each instance of <code>FibonacciTest</code> will be constructed using the
     * two-argument constructor and the data values in the
     * <code>&#064;Parameters</code> method.
     * </p>
     */
    public class Parameterized extends Suite {
    
        /**
         * Annotation for a method which provides parameters to be injected into the
         * test class constructor by <code>Parameterized</code>
         */
        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.METHOD)
        public static @interface Parameters {
        }
    
        /**
         * Annotation for a methods which should not be parameterized
         */
        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.METHOD)
        public static @interface NonParameterized {
        }
    
        private class TestClassRunnerForParameters extends
                BlockJUnit4ClassRunner {
            private final int fParameterSetNumber;
    
            private final List<Object[]> fParameterList;
    
            TestClassRunnerForParameters(Class<?> type,
                    List<Object[]> parameterList, int i) throws InitializationError {
                super(type);
                fParameterList = parameterList;
                fParameterSetNumber = i;
            }
    
            @Override
            public Object createTest() throws Exception {
                return getTestClass().getOnlyConstructor().newInstance(
                        computeParams());
            }
    
            private Object[] computeParams() throws Exception {
                try {
                    return fParameterList.get(fParameterSetNumber);
                } catch (ClassCastException e) {
                    throw new Exception(String.format(
                            "%s.%s() must return a Collection of arrays.",
                            getTestClass().getName(), getParametersMethod(
                                    getTestClass()).getName()));
                }
            }
    
            @Override
            protected String getName() {
                return String.format("[%s]", fParameterSetNumber);
            }
    
            @Override
            protected String testName(final FrameworkMethod method) {
                return String.format("%s[%s]", method.getName(),
                        fParameterSetNumber);
            }
    
            @Override
            protected void validateConstructor(List<Throwable> errors) {
                validateOnlyOneConstructor(errors);
            }
    
            @Override
            protected Statement classBlock(RunNotifier notifier) {
                return childrenInvoker(notifier);
            }
    
            @Override
            protected List<FrameworkMethod> computeTestMethods() {
                List<FrameworkMethod> ret = super.computeTestMethods();
                for (Iterator<FrameworkMethod> i = ret.iterator(); i.hasNext();) {
                    FrameworkMethod frameworkMethod =
                        (FrameworkMethod) i.next();
                    if (isParameterized() ^
                        !frameworkMethod.getMethod().isAnnotationPresent(
                            NonParameterized.class)) {
                        i.remove();
                    }
                }
                return ret;
            }
    
            protected boolean isParameterized() {
                return true;
            }
        }
    
        private class TestClassRunnerForNonParameterized extends
            TestClassRunnerForParameters {
    
            TestClassRunnerForNonParameterized(Class<?> type,
                List<Object[]> parameterList, int i)
                throws InitializationError {
                super(type, parameterList, i);
            }
    
            protected boolean isParameterized() {
                return false;
            }
        }
    
        private final ArrayList<Runner> runners = new ArrayList<Runner>();
    
        /**
         * Only called reflectively. Do not use programmatically.
         */
        public Parameterized(Class<?> klass) throws Throwable {
            super(klass, Collections.<Runner> emptyList());
            List<Object[]> parametersList = getParametersList(getTestClass());
            if (parametersList.size() > 0) {
                try {
                    runners.add(new TestClassRunnerForNonParameterized(
                        getTestClass()
                            .getJavaClass(), parametersList, 0));
                } catch (Exception e) {
                    System.out.println("No non-parameterized tests.");
                }
            }
            try {
                for (int i = 0; i < parametersList.size(); i++) {
                    runners.add(new TestClassRunnerForParameters(getTestClass()
                        .getJavaClass(),
                        parametersList, i));
                }
            } catch (Exception e) {
                System.out.println("No parameterized tests.");
            }
        }
    
        @Override
        protected List<Runner> getChildren() {
            return runners;
        }
    
        @SuppressWarnings("unchecked")
        private List<Object[]> getParametersList(TestClass klass)
                throws Throwable {
            return (List<Object[]>) getParametersMethod(klass).invokeExplosively(
                    null);
        }
    
        private FrameworkMethod getParametersMethod(TestClass testClass)
                throws Exception {
            List<FrameworkMethod> methods = testClass
                    .getAnnotatedMethods(Parameters.class);
            for (FrameworkMethod each : methods) {
                int modifiers = each.getMethod().getModifiers();
                if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
                    return each;
            }
    
            throw new Exception("No public static parameters method on class "
                    + testClass.getName());
        }
    
    }
    

    更新:I'm trying to get this sort of thing added to junit.

    【讨论】:

      【解决方案6】:

      对于那些希望参数来自java函数而不是注解的人:

      @ParameterizedTest
      @MethodSource("provideStringsForIsBlank")
      void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input, boolean expected) {
          assertEquals(expected, Strings.isBlank(input));
      }
      
      private static Stream<Arguments> provideStringsForIsBlank() {
          return Stream.of(
            Arguments.of(null, true),
            Arguments.of("", true),
            Arguments.of("  ", true),
            Arguments.of("not blank", false)
          );
      }
      

      来源:https://www.baeldung.com/parameterized-tests-junit-5

      【讨论】:

        【解决方案7】:

        假设您使用 Parametrized.class 来运行您的测试类 - 将所有非参数化测试标记为 @Ignored。否则,您可以创建一个静态内部类来对所有参数化测试和另一个 - 用于非参数化测试进行分组。

        【讨论】:

          【解决方案8】:

          我做了类似于 Matthew 的解决方案的事情。但是,我创建了两个扩展当前文件的新 java 文件,这样 ComponentSingleTests 就不会运行两次。这样,它们可以共享来自父类的公共成员变量和辅助方法。我对 Matthew 的解决方案的问题是我的单个测试运行了两次而不是一次,因为 Enclosed.class(它扩展了 Suite.class)将使您的测试运行两次,如此链接中所述 Prevent junit tests from running twice

          ComponentTest.java

          public class ComponentTest {
              public int sharedMemberVariables; 
              ... 
          }
          

          ComponentParamTests.java

          import org.junit.Test;
          import org.junit.runner.RunWith;
          import org.junit.runners.Parameterized;
          
          @RunWith(Parameterized.class)
          public class ComponentParamTests extends ComponentTest {
          
              @Parameters
              ...
          
              @Test
              public void testCaseUsingParams() throws Exception {
              }
          }
          

          ComponentSingleTests.java

          import org.junit.Test;
          
          public class ComponentSingleTests {
          
              @Test
              public void testCaseWithoutParams() throws Exception {
                  ...
              }
          }
          

          【讨论】:

            【解决方案9】:

            我在 Spring Boot MockMvc 中编写测试时遇到了这个问题 我只是在单独的 java 文件中创建了两个类(一个用于 ParameterizedTest,另一个用于 SingleTest)并为它们创建一个套件。因为内部类正在为静态成员而不是静态成员和类创建错误。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-01-21
              • 2019-01-04
              • 1970-01-01
              • 2017-10-12
              相关资源
              最近更新 更多