ArticleS
.
DavidChelimsky
.
UsingMultipleFixtures
Edit Page:
!title Using Multiple Fixtures I remember reading an article when I was first learning about jUnit. I don't remember who the author was, but I do remember this: the author described the setUp method in !-TestCase-! as setting up a fixture, which was described as an object in a known state. If you want to test that object in more than one state, you use more than one fixture in the form of multiple !-TestCases-!. At the time I really didn't get it. This was all sort of new to me and everyone around me who did unit test their code (there were a couple) was writing one !-TestCase-! per class and sometimes one test per method as well. Recently I was guiding some students through the exercise of test driving a Stack implementation. We started off with a single !-TestCase-!. Here are the names of the test methods that evolved. {{{StackTest { testSizeShouldBeZeroAfterInit testSizeAfterPushingOne testSizeAfterPushingTwo testPeekWithOneElement testPeekWithTwoElements testPopWithOneElement testPushAndPopTwoElements testPopOnEmptyStackShouldThrowEmptyStackError testShouldAllowTenItems testPushOnFullStackShouldThrowStackOverflowError testPush10Pop1Push1More } }}} When we got to this point, we all agreed that the implementation was done, but we decided that while looking at the code, not the tests. Looking over the !-TestCase-! it was difficult to see whether we had really specified everything we wanted to. Remember, tests are not just tests, they are user guides for future development. They are executable specifications. So I introduced the idea of multiple test cases per class. There was some resistance, but we agreed to try it and see what would happen. So we started with an empty stack, pulled in all the methods related to an empty stack, renamed a couple of the methods and ended up with this: {{{EmptyStackTest { testSizeShouldBeZero testSizeShouldBeOneAfterPush testPopShouldThrowEmptyStackException } }}} Notice something missing here? We did right away once we had this structure. We had an object in a known state (an empty stack), and we were making assertions about the initial state and then how the empty stack responded to push() and pop(). Well what about peek()? How should an empty stack behave when you send it the peek() message? I'm sure we could have analyzed all of the tests in the big single !-TestCase-! and discovered this, but what would this analysis look like? We likely would have broken the tests down by state... "when the stack is empty we're testing this, that and the other". Restructuring exposed the missing specification immediately. In the end we found ourselves with four test cases: {{{EmptyStackTest FullStackTest StackWithOneElementTest StackWithNineElementTests}}} In each case there was a test method that made assertions about the initial state, and then how the stack in that state responded to push(), pop() and peek(). Admittedly, this did not change our implementation at all in this case. Perhaps that's because we had enough tests to guide us to the right code. Remember, however, that the tests are multi-purpose. In addition to driving the design, they live on as executable specifications - reference documentation that is, by definition, accurate. The result of our experiment really '''looked like''' a specification. If you were a developer trying to use this stack and wanted to know what to expect when you try to pop an empty stack, you could very easily find the right test method to answer your questions. ----!commentForm !* Mon, 27 Mar 2006 09:29:29, ${pagel}, ...behavior is behind the wheel? This sounds like a great idea. Does the common setup always have to drive the seperation? If you have specs seperated by behavior rather than object state does this testing ethos work out as well? Could there be : !-PopStackTest-! !-PushStackTest-! !-PeekStackTest-! These tests have different setup, however it is easier to read behavior as specifications than state, which implies you understand the behavior well enough to understand the state. For efficency, I agree with the state seperation, however, specs are driven more by the behavioral names. Or does the state imply the behavior? *! !* Tue, 4 Apr 2006 11:38:10, Clint Shank, BDD, plus more This describes the impression I got when I read about Behavior Driven Development. If I remember correctly, you have Contexts instead of TestCases. I think Contexts map to initial object states. Do you think the testSizeShouldBeOneAfterPush() should really be on the StackWithOneElementTest instead of the EmptyStackTest? What I don’t like about this style is all the TestCase classes. And there’s common setup among the TestCases that should be factored out somewhere else as well. So you end up with many classes necessary to test/specify one production class. *! !* Mon, 10 Apr 2006 14:02:55, ${chelimsky}, re: BDD, plus more Hi Clint - I've been paying very close attention to the conversation around BDD, so the connection you make is spot on. I do think that testSizeShouldBeOneAfterPush belongs on !-EmptyStackTest-! for the reason that you cite: contexts make to initial object states. You get the object into a known state (empty), do something (push) and compare the results to your expectations. Having testSizeShouldBeOneAfterPush on !-StackWithOneElementTest-! would be confusing because the initial state in that context IS a stack with one element. In !-StackWithOneElementTest-!, you could have testSizeShouldBeOne if you wanted. There's no hard rule here. As for the number of !-TestCase-! classes exceeding the number of production classes, that doesn't bother me at all. The clarity you gain from having a clearly understood context far outweighs any clutter in the directories caused by the additional classes. And by all means, if you see duplication in the setups, pull that out to some helper. In this case that could be a !-StackTestHelper-! or a !-StackFactory-! or something (please, NO !-BaseStackTest-! - they usually end up hurting clarity rather than helping). *!
Hints:
Use alt+s (Windows) or control+s (Mac OS X) to save your changes. Or, tab from the text area to the "Save" button!
Grab the lower-right corner of the text area to increase its size (works with some browsers).