2

I have an enum that can present in a number of different ways. As a String, as an Integer and as a Double (different ranges), as a Vector2D and finally as the enum value itself. Here is a generalised example, the values are not representative. The actual use I have for this has a greate deal more values and methods.

 public enum Example {

    value0("Name0", 0, 0.0, 0.01707, 0.12534);        
    value1("Name1", 1, 25.0, 0.1707, 0.53434);
    value2("Name2", 2, 55.0, 0.70701, 0.23534);
    value3("Name3", 3, 65.0, 0.01707, 0.34786);
    value5("Name4", 4, 100.0, 0.01707, 0.42594);

    private final String name;
    private final int number; 
    private final double head;
    private final Vector2d pointVec;

    /**
     * Constructor invoked for each value above. 
     */
    enumExample(String name, int no, double hdg, float compX, float CompY) {
      this.name = name;
      this.number = no;
      this.head = hdg;
      this.pointVec = new Vector2d(compX, compY);
    }

    public String getName(){
       return name;        
    }

    public int getNumber() {
        return no;
    }

    public int getHead() {
        return head;
    }

    public Vector2D getVector() {
        return pointVec;
    }        

    public Example getCalcValue(int value) {
        return calcValue(getNumber(value));
    }

    /*
     * There are more methods that perform calculations on the enum's 
     * attributes.
     */
 }

In order to ensure that other classes that make use of this enum are working with a correctly functional enum. I want to have a comprehensive set of tests for it, thus ensuring that the data entry has been performed correctly and that the enum and it's associated data has not got corrupted.

Currently an example of this with 5 enum values has 31 tests. I have a need for versions of this going up to 33 enum values. Which is approx 200 tests.

I was hoping to be able to use data driven testing as this would make checking the test data by eye easier.

Does anybody have any ideas as to how to set this up for an enum? All the examples of data driven testing I've found have a simple class with one method to test.

Richard Evans
  • 21
  • 1
  • 3
  • To me, it looks like you don't want to test the enum, but the constant data in the enum. To test that, you could probably write a parameterized test that runs a list of tests, taking the enum value and the expected results as parameters. So you only write one (or more) test per value(s) you want to test and then can run this (these) tests on all your enum values. If this is what you want, I could give you an example... – Florian Schaetz Oct 20 '15 at 07:55
  • 1
    What property of the enum are you actually trying to test? – Andy Turner Oct 20 '15 at 08:01
  • Show some of your existing tests. – weston Oct 20 '15 at 08:02
  • As @FlorianSchaetz mentioned you should have a look for parameterized tests. Here is one example https://github.com/junit-team/junit/wiki/Parameterized-tests – SubOptimal Oct 20 '15 at 08:03
  • Thank you SubOptimal, for the reference, but that is one of the examples I had already looked at and found inadequate. – Richard Evans Oct 21 '15 at 08:23

4 Answers4

2

Parameterized tests are not the most beautiful pieces of code every conceived (I don't like the whole object[] stuff), but you could probably use them to do your stuff... In this example, I have two tests with expected results, but of course, you can add many parameters for multiple tests, with every test testing a certain part of the enum.

@RunWith(Parameterized.class)
public class SomeTest {

    @Parameters
    public static Iterable<Object[]> data() {
        return Arrays.asList(new Object[][] { 
                 { MyEnum.Value1, 0, "x" }, 
                 { MyEnum.Value2, 1, null }, 
                 { MyEnum.Value3, 127, "y" }, 
                 etc.
           });
    }

    private MyEnum enumValue;
    private int expectedInt;
    private String expectedString;

    // Each test set will get one set of parameters from your array
    public SomeTest(MyEnum enumValue, int expectedInt, String expectedString) {
        this.enumValue = enumValue;
        this.expectedInt = expectedInt;
        this.expectedString = expectedString;
    }

    @Test
    public void testInt() {
         // do whatever calculation you need to do on the data
         int result = this.enumValue.doSomething() * 2 - 100;  
         assertEquals(expectedInt, result);
    }

    @Test
    public void testString() {
         // do whatever calculation you need to do on the data
         String result = this.enumValue.doSomethingElse().substring(2,5);
         assertEquals(expectedString, result);
    }
}

This way you would only write one set of tests for the enum itself and then parameterize them with the actual enum values and the expected result for these values.

I suggest doing the actual calculations in the test class and not just check the values, since checking the values will lead to people copy&pasting them into your test, which will help nobody.

Florian Schaetz
  • 10,454
  • 5
  • 32
  • 58
  • JUnit's Parameterized runner is a pain because you cannot mix parameterized with non parameterized tests. Indeed with Parameterized all you tests will be considered as parameterized as parameters are passed to the constructor. If you want to have non parameterized tests the you need then to enclose your whole test class in a class annotated with @RunWith(Enclosed.class) which while valid becomes really difficult to read. That's why I recommend using junitparams instead. – Kraal Oct 20 '15 at 08:19
1

If you have a set of tests to do for all your Enum values, you should try to use parameterized tests with JUnitparams.

Here is an example of how JUnitParams can be used:

@RunWith(JUnitParamsRunner.class)
public class ExampleTest {

    //...

    /**
     * This test is parameterized !
     */
    @Test
    @Parameters
    public void yourTestMethod(ExampleEnum enumValue, Integer inputValue, String expectedString /* other parameters if you need them */) {
        // here you test what you need to test
        // for instance stupid dummy test
        assertEquals(enumValue.doSomething(inputValue), expectedString);
    }

    /**
     * This method provides the parameters values for the "yourTestMethod"
     * test. Note that there is a naming convention to be followed ! 
     * You need to prefix your test method's name with "parametersFor"
     */
    private Object[] parametersForYourTestMethod() { 
        return new Object[] {
            // each Object[] is a test case where elements are
            // inputs and / or expected outputs
            new Object[] { 
                ExampleEnum.CASE_1,
                1,
                "ExpectedString1"
            },
            new Object[] {
                ExampleEnum.CASE_2,
                2,
                "ExpectedString2"
            },
            new Object[] {
                ExampleEnum.CASE_3,
                3,
                "ExpectedString3"
            },
            new Object[] {
                ExampleEnum.CASE_4,
                4,
                "ExpectedString4"
            },
            // ... add as many test cases as you need
            new Object[] {
                ExampleEnum.CASE_N,
                Integer.MAX_VALUE,
                "ExpectedStringN"
            }
        }
    }

    //...

    /**
     * This test is not parameterized !
     */
    @Test
    public void anotherTest() {
        //...
    }
}

This test class provides you with a @Test named yourTestMethod that will be run N times, each time with a different set of parameters provided by the parametersForYourTestMethod method. You can add as many parameterized tests as you want with any inputs combination you want.

Note that this class also has a non-parameterized @Test named anotherTest().

This last point illustrates the advantage JUnitParams extension provides over "pure" JUnit usage. JUnit's Parameterized.class runner forces all your tests to be parameterized and you cannot have different sets of parameters for each test method. You need to pass all parameters to your constructor instead. As a result you cannot mix parameterized tests with non parameterized ones. If you want to have non parameterized tests or have different sets of parameters then you need to enclose your test classes in a class annotated with @RunWith(Enclosed.class) which, while valid, becomes really difficult to read.

That's why I really recommend using JUnitParamsRunner.class instead of JUnit's Parameterized.class runner.

Hope it helps,

Michel

Kraal
  • 2,779
  • 1
  • 19
  • 36
0

I think the danger is you're going to end up with a complete duplicate of the data in your tests 33 enums x 5 values = 165 values, whether you used parametrised or not, the pain will come from copying out those values. And I do not think that approach is going to give you more correct code/data. If you can make the mistake in the enum, you can make the same mistake in the test.

So I would only test a couple of these, that'll be enough to test the accessors are working.

Then this enum must be used by other classes. I'd concentrate on testing those classes such that if these values are wrong that code will fail. In fact my approach would be to add the extra enums as they are needed only to make the other tests pass.

weston
  • 54,145
  • 21
  • 145
  • 203
  • I would not duplicate the data, I would do something to the data and test against the expected result. This at least decreases the chances of data error. But of course, we could argue if testing your base data is in the scope of a unit test... – Florian Schaetz Oct 20 '15 at 08:26
  • @FlorianSchaetz you say that, but your answer shows the duplication of data in the tests. It sounds like you agree with me though. – weston Oct 20 '15 at 09:21
  • That's just because my data was supposed to be an example, for the real thing I would expect the test to DO something with the data and then compare the result to the expected value. This is why I didn't call the methods "get..." but "doSomething". I will make that more clear. – Florian Schaetz Oct 20 '15 at 09:28
0

if you want to ensure that the data entry has been performed correctly, don't test each enum separately - it's pointless. it would be very easy with parameterized testing but you will end up with just a copy of your production code:

test(enum0, "Name0", 0, 0.0, 0.01707, 0.12534)
test(enum1, "Name1", 1, 25.0, 0.1707, 0.53434)

it doesn't prevent malformed data input in any way. it just makes code changing process longer (because of duplication). if someone type in bad data, he will just copy paste them to the tests

instead try to check if all required properties hold. for example all values must be different, not overlapping, distributed evenly, have subsequent values etc. check how other code reacts to thet enum. for example if all the possible events are fired if a method receives all the enums.

this way when someone changes the enum, tests remain unmodified. it lets you, to some extent, detect malformed data

piotrek
  • 13,982
  • 13
  • 79
  • 165