2

I have a unit test (JUnit 4) with some pretty complex setup:

public class HDF5CompoundDSCutoffCachingBridgeTest {
    protected final String TEST_PATH = "test-out.h5";
    protected final DatasetName TEST_DS = new DatasetName("group", "foo");
    protected WritableDataPoint testPoint1;
    protected WritableDataPoint emptyPoint;
    protected WritableDataPoint[] emptyPoints;
    protected WritableDataPoint[] fullPoints;
    protected IHDF5Writer writer;
    protected HDF5CompoundDSBridgeConfig config;


    protected HDF5CompoundDSCachingBridge<WritableDataPoint> dtBridge;

    @Before
    public void setUp() throws Exception {
        try {
            File file = new File(TEST_PATH);

            testPoint1 = new WritableDataPoint(new long[][]{{1, 2}}, new long[][]{{3, 4}}, 6, 10l);
            emptyPoint = new WritableDataPoint(new long[][]{}, new long[][]{}, 0, 0l);

            emptyPoints = new WritableDataPoint[]{emptyPoint, emptyPoint, emptyPoint, emptyPoint, emptyPoint,};
            fullPoints = new WritableDataPoint[]{testPoint1, testPoint1, testPoint1, testPoint1, testPoint1,};


            config = new HDF5CompoundDSBridgeConfig(HDF5StorageLayout.CHUNKED,
                                                    HDF5GenericStorageFeatures.MAX_DEFLATION_LEVEL,
                                                    5);

            writer = HDF5Writer.getWriter(file);
            HDF5CompoundDSBridgeBuilder<WritableDataPoint> dtBuilder =
                    new HDF5CompoundDSBridgeBuilder<>(writer, config);
            dtBuilder.setChunkSize(5);
            dtBuilder.setStartSize(5);
            dtBuilder.setTypeFromInferred(WritableDataPoint.class);
            dtBuilder.setCutoff(true);

            buildBridge(dtBuilder);

        } catch (StackOverflowError e) {
            // other stuff
        }

    }

    protected void buildBridge(HDF5CompoundDSBridgeBuilder<WritableDataPoint> dtBuilder) throws Exception {
        dtBridge = dtBuilder.buildCaching(TEST_DS);
    }

    // a bunch of tests follow
}

Now, I've subclassed the class that's under test here. It still needs the complex setup in the original test. It's behavior is pretty much the same -- it needs to do all of the things that the parent class does, plus it passes slightly different information to dtBuilder.

I could just copy-paste the configuration code over. This does appeal to me a little bit: these are really two different objects under test, and it's possible that in the future their configuration needs will change. If they have completely separate setup code, then I can make changes to the way either object works without worrying about breaking tests unnecessarily. But that seems wrong.

What I've done instead is inherit from the class for the superclass:

public class HDF5CompoundDSAsyncBridgeTest extends HDF5CompoundDSCutoffCachingBridgeTest {
    protected HDF5CompoundDSAsyncBridge<WritableDataPoint> dtBridge;


    @Override
    protected void buildBridge(HDF5CompoundDSBridgeBuilder<WritableDataPoint> dtBuilder) throws Exception {
        dtBuilder.setAsync(true);
        dtBridge = dtBuilder.buildAsync(TEST_DS);
        assertNotNull(dtBridge);
    }
    // more tests go here
}

I like this because I get to verify that my new class still passes all of the tests its parent class did, but there seems to be something wrong about having unit tests inheriting from each other.

What's the right move here? I've read posts like this: What does “DAMP not DRY” mean when talking about unit tests? that suggest that inheriting for unit tests is a bad idea, but it seems wrong to copy + paste whole chunks of code, ever.

Community
  • 1
  • 1
Patrick Collins
  • 10,306
  • 5
  • 30
  • 69
  • "but it seems wrong to copy + paste whole chunks of code" - and why would you have to do that ? If the parent-class has its own unit-tests that means that this code is already being tested. – Nir Alfasi Aug 07 '14 at 16:50
  • @alfasin I still need the same set up for the derived class in order to test its behavior. Or it needs to be filled with test data somehow, anyway. – Patrick Collins Aug 07 '14 at 16:51

1 Answers1

2

IMO there's nothing intrinsically wrong with tests living in a hierarchy, but it's not the only option, and there are a variety of arguments either way. Tests do tend to repeat more code, but for me, identical setup is too prone to errors, fat-fingering, etc. to rely on copy-and-paste, particularly if that setup changes often enough that it's likely you'd screw up one of the subclass's setup.

All that said, why not just call the superclass's setup method(s) from the subclass, overriding and/or extending behavior when necessary? It's not like the subclass lives in isolation.

If there's some reason that doesn't work, or there's a lot of subclass customization needed, consider wrapping up those options/values, datasinks, etc. in a value object.

Then each test class may use the same @Before functionality, but it would look more like:

protected TestValues testValues;

@Before
public void setUp() throws Exception {
    testValues = new TestSetupHelper();
}

If things need to be parameterized you can add stuff to TestSetupHelper's ctor etc.

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
  • I agree. One point for sub-classing the parent class test is that it asserts the same behavior in the sub-class as in the parent except where the test explicitly expects different behavior. Personally, I really like this. – John B Aug 07 '14 at 18:34