Create multiple parameter sets in one parameterized class (junit)

后端 未结 8 923
无人共我
无人共我 2021-01-30 13:05

Currently I have to create a parameterized test class for every method that I want to test with several different inputs. Is there a way to add this together in one file?

<
相关标签:
8条回答
  • 2021-01-30 13:07

    you can use parameters with https://github.com/piotrturski/zohhak:

    @TestWith({
       "1, 7, 8",
       "2, 9, 11"
    })
    public void addTest(int number1, int number2, int expectedResult) {
        BigDecimal result = calculator.add(number1, number2);
        assertThat(result).isEqualTo...
    }
    

    if you want to load parameters from file, you can use http://code.google.com/p/fuzztester/ or http://code.google.com/p/junitparams/

    and if you need real flexibility you can use junit's @Parameterized but it clutters your code. you can also use junit's Theories - but it seems an overkill for calculator tests

    0 讨论(0)
  • 2021-01-30 13:10

    Another pure JUnit but yet elegant solution in my view is to encapsulate each parameterized test(s) in their own inner static class and use the Enclosed test runner on the top level test class. This allows you not only to use different parameter values for each test independently of each other but also to test methods with completely different parameters.

    This is how it would look like:

    @RunWith(Enclosed.class)
    public class CalculatorTest {
    
      @RunWith(Parameterized.class)
      public static class AddTest {
    
        @Parameters
        public static Collection<Object[]> data() {
          return Arrays.asList(new Object[][] {
              { 23.0, 5.0, 28.0 }
          });
        }
    
        private Double a, b, expected;
    
        public AddTest(Double a, Double b, Double expected) {
          this.a = a;
          this.b = b;
          this.expected = expected;
        }
    
        @Test
        public void testAdd() {
          assertEquals(expected, Calculator.add(a, b));
        }
      }
    
      @RunWith(Parameterized.class)
      public static class SubstractTest {
    
        @Parameters
        public static Collection<Object[]> data() {
          return Arrays.asList(new Object[][] {
              { 3.0, 2.0, 1.0 }
          });
        }
    
        @Parameter(0)
        private Double a;
        @Parameter(1)
        private Double b;
        @Parameter(2)
        private Double expected;
    
        @Test
        public void testSubstract() {
          assertEquals(expected, Calculator.substract(a, b));
        }
      }
    
      @RunWith(Parameterized.class)
      public static class MethodWithOtherParametersTest {
    
        @Parameters
        public static Collection<Object[]> data() {
          return Arrays.asList(new Object[][] {
              { 3.0, 2.0, "OTHER", 1.0 }
          });
        }
    
        private Double a;
        private BigDecimal b;
        private String other;
        private Double expected;
    
        public MethodWithOtherParametersTest(Double a, BigDecimal b, String other, Double expected) {
          this.a = a;
          this.b = b;
          this.other = other;
          this.expected = expected;
        }
    
        @Test
        public void testMethodWithOtherParametersTest() {
          assertEquals(expected, Calculator.methodWithOtherParametersTest(a, b, other));
        }
      }
    
      public static class OtherNonParameterizedTests {
    
        // here you can add any other test which is not parameterized
    
        @Test
        public void otherTest() {
          // test something else
        }
      }
    }
    

    Note the usage of the @Parameter annotation in the SubstractTest, which I consider more readable. But this is more a matter of taste.

    0 讨论(0)
  • 2021-01-30 13:12

    With Junit Jupiter: https://www.petrikainulainen.net/programming/testing/junit-5-tutorial-writing-parameterized-tests/

    import intf.ICalculator;
    
    public class Calculator implements ICalculator {
        @Override
        public int plus(int a, int b) {return a + b; }
    
        @Override
        public int minuis(int a, int b) {return a - b;}
    
        @Override
        public int multy(int a, int b) {return a * b;}
    
        @Override  // check in junit byZero
        public int divide(int a, int b) {return a / b;}
    
    }
    

    Test class:

    import static org.junit.Assert.assertEquals;
    
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.params.ParameterizedTest;
    import org.junit.jupiter.params.provider.CsvSource;
    
    class CalculatorJupiter5Test {
    
        Calculator calculator = new Calculator();
    
        @DisplayName("Should calculate the correct sum")
        @ParameterizedTest(name = "{index} => a={0}, b={1}, sum={2}")
        @CsvSource({
                "5, 3, 8",
                "1, 3, 4",
                "6, 6, 12",
                "2, 3, 5"
        })
        void sum(int a, int b, int sum) {
            assertEquals(sum, calculator.plus(a, b) );
        }
    
        @DisplayName("Should calculate the correct multy")
        @ParameterizedTest(name = "{index} => a={0}, b={1}, multy={2}")
        @CsvSource({
            "5, 3, 15",
            "1, 3, 3",
            "6, 6, 36",
            "2, 3, 6"
        })
        void multy(int a, int b, int multy) {
            assertEquals(multy, calculator.multy(a, b) );
        }
    
        @DisplayName("Should calculate the correct divide")
        @ParameterizedTest(name = "{index} => a={0}, b={1}, divide={2}")
        @CsvSource({
            "5, 3, 1",
            "14, 3, 4",
            "6, 6, 1",
            "36, 2,  18"
        })
        void divide(int a, int b, int divide) {
            assertEquals(divide, calculator.divide(a, b) );
        }
    
       @DisplayName("Should calculate the correct divide by zero")
       @ParameterizedTest(name = "{index} => a={0}, b={1}, divide={2}")
       @CsvSource({
          "5, 0, 0",
       })
        void divideByZero(int a, int b, int divide) {
         assertThrows(ArithmeticException.class,
             () -> calculator.divide(a , b),
             () -> "divide by zero");
        }
    
        @DisplayName("Should calculate the correct minuis")
        @ParameterizedTest(name = "{index} => a={0}, b={1}, minuis={2}")
        @CsvSource({
            "5, 3, 2",
            "1, 3, -2",
            "6, 6, 0",
            "2, 3, -1"
        })
        void minuis(int a, int b, int minuis) {
            assertEquals(minuis, calculator.minuis(a, b) );
        }
    }
    
    0 讨论(0)
  • 2021-01-30 13:19

    I'm sure you are not having this problem anymore, but I thought of 3 ways you can do this, each with its pros and cons. With the Parameterized runner, you'll have to use a workaround.

    - Using more parameters with Parameterized

    In case you have to load the parameters externally, you simply add a parameter for the expected results.

    Pros: less coding, and it runs all the tests.

    Cons: new parameters for each different set of tests.

    @RunWith(Parameterized.class)
    public class CalculatorTest extends TestCase {
        private Calculator calculator;
        private int operator1;
        private int operator2;
        private int expectedSum;
        private int expectedSub;
    
        public CalculatorTest(int operator1, int operator2, int expectedSum, int expectedSub) {
            this.operator1 = operator1;
            this.operator2 = operator2;
        }
    
        @Params
        public static Collection<Object[]> setParameters() {
            Collection<Object[]> params = new ArrayList<>();
            // load the external params here
            // this is an example
            params.add(new Object[] {2, 1, 3, 1});
            params.add(new Object[] {5, 2, 7, 3});
    
            return params;
        }
    
        @Before
        public void createCalculator() {
            calculator = new Calculator();
        }
    
        @Test
        public void addShouldAddTwoNumbers() {
            assertEquals(expectedSum, calculator.add(operator1, operator2));
        }
    
        @Test
        public void subtractShouldSubtractTwoNumbers() {
            assertEquals(expectedSub, calculator.subtract(operator1, operator2));
        }
    
        @After
        public void endTest() {
            calculator = null;
            operator1 = null;
            operator2 = null;
            expectedSum = null;
            expectedSub = null;
        }
    }
    

    - Not using Parameterized runner

    This works fine if you set your parameters programatically.

    Pros: You can have as many tests as you want without having to set a huge set of parameters.

    Cons: More coding, and it stops at the first failure (which might not be a con).

    @RunWith(JUnit4.class)
    public class CalculatorTest extends TestCase {
        private Calculator calculator;
    
        @Before
        public void createCalculator() {
            calculator = new Calculator();
        }
    
        @Test
        public void addShouldAddTwoNumbers() {
            int[] operator1 = {1, 3, 5};
            int[] operator2 = {2, 7, 9};
            int[] expectedResults = {3, 10, 14};
    
            for (int i = 0; i < operator1.length; i++) {
                int actualResult = calculator.add(operator1[i], operator2[i]);
                assertEquals(expectedResults[i], actualResult);
            }
        }
    
        @Test
        public void subtractShouldSubtractTwoNumbers() {
            int[] operator1 = {5, 8, 7};
            int[] operator2 = {1, 2, 10};
            int[] expectedResults = {4, 6, -3};
    
            for (int i = 0; i < operator1.length; i++) {
                int actualResult = calculator.subtract(operator1[i], operator2[i]);
                assertEquals(expectedResults[i], actualResult);
            }
        }
    
        @After
        public void endTest() {
            calculator = null;
        }
    }
    

    - Using JUnitParams

    I have no affiliation with Pragmatists, I just found this a few days ago. This framework runs on top of JUnit and handles parameterized tests differently. Parameters are passed directly to the test method, so you can have in the same class different params for different methods.

    Pros: achieves the same results as the solutions above without workarounds.

    Cons: maybe your company doesn't allow you add a new dependency to the project or forces you to use some bizarre coding rule (like using the Parameterized runners exclusively). Let's face it, it happens more than we'd like to.

    Here's a fine example of JUnitParams in action, and you can get the project/check the code on this Github page.

    0 讨论(0)
  • 2021-01-30 13:20

    Well, now JUnit-5 offers you a solution for this - by redefining a way to write parameterized tests. Now a parameterized test can be defined at a method level using @ParameterizedTest and can be given a method source using @MethodSource.

    So in your case you can have 2 separate data source methods for providing input data for your add() and subtract() test methods, both in the same class. Your code should go something like this:

    public class CalculatorTest{
        public static int[][] dataSetForAdd() {
            return new int[][] { { 1 , 2, 3 }, { 2, 4, 6 }, { 121, 4, 125 } };
        }
    
        public static int[][] dataSetForSubtract() {
            return new int[][] { { 1 , 2, -1 }, { 2, 4, -2 }, { 121, 4, 117 } };
        }
    
        @ParameterizedTest
        @MethodSource(names = "dataSetForAdd")
        void testCalculatorAddMethod(int[] dataSetForAdd) {
            Calculator calculator= new Calculator();
            int m1 = dataSetForAdd[0];
            int m2 = dataSetForAdd[1];
            int expected = dataSetForAdd[2];
            assertEquals(expected, calculator.add(m1, m2));
        }
    
        @ParameterizedTest
        @MethodSource(names = "dataSetForSubtract")
        void testCalculatorAddMethod(int[] dataSetForSubtract) {
            Calculator calculator= new Calculator();
            int m1 = dataSetForSubtract[0];
            int m2 = dataSetForSubtract[1];
            int expected = dataSetForSubtract[2];
            assertEquals(expected, calculator.subtract(m1, m2));
        }
    }
    
    0 讨论(0)
  • 2021-01-30 13:21

    This answer is similar to Tarek's one (the parametrized part), although I think it is a bit more extensible. Also solves your problem and you won't have failed tests if everything is correct:

    @RunWith(Parameterized.class)
    public class CalculatorTest {
        enum Type {SUBSTRACT, ADD};
        @Parameters
        public static Collection<Object[]> data(){
            return Arrays.asList(new Object[][] {
              {Type.SUBSTRACT, 3.0, 2.0, 1.0},
              {Type.ADD, 23.0, 5.0, 28.0}
            });
        }
    
        private Type type;
        private Double a, b, expected;
    
        public CalculatorTest(Type type, Double a, Double b, Double expected){
            this.type = type;
            this.a=a; this.b=b; this.expected=expected;
        }
    
        @Test
        public void testAdd(){
            Assume.assumeTrue(type == Type.ADD);
            assertEquals(expected, Calculator.add(a, b));
        }
    
        @Test
        public void testSubstract(){
            Assume.assumeTrue(type == Type.SUBSTRACT);
            assertEquals(expected, Calculator.substract(a, b));
        }
    }
    
    0 讨论(0)
提交回复
热议问题