Reducing Coupling Between Test Cases
java, junit, tdd, testing, unit-testing
Solution
When writing a test for one method, you have to assume that the rest of class is working correctly. If you wouldn't make this assumption, the only conclusion would be a single, massive test per class. And that's not what we do.
You can make the assumption that the other parts of the class work correctly, because there will be tests for those other parts, too, ensuring their correctness. If one part is not working correctly, a test will fail, showing you that something is not correct. As soon as a test of your test suite fails, there is an error you have to fix. You no longer can make any assumptions.
Example:
You have a simple list implementation with only three methods:
- insert
- remove
- count
You have three tests:
- Test for `insert`:
- create instance of list (Arrange)
- `insert` item (Act)
- check that `count` equals 1 (Assert)
- Test for `remove`:
- create instance of list and `insert` item (Arrange)
- `remove` item (Act)
- check that `count` equals 0 (Assert)
- Test for `count`:
- create instance of list and `insert` n items (Arrange)
- retrieve `count` (Act)
- check that `count` equals n (Assert)
Now, if any of the above tests fail, you can't be sure of the correctness of a singlemember of your class:
- If the first test fails, the third one will also fail. The second one will pass, but didn't actually test `remove`, because there was nothing to remove.
- If the second test fails, the other two tests will still pass. Still, you can't be sure that `insert` and `count` are working correctly, because the second test will fail if any of the three members doesn't work correctly.
- If the third test failes, the other two most likely will fail, too.
The failing tests tell you something though: Depending on the tests that fail, you often can deduct where the error has to be. Example: If only the second test fails but not the first or third, the error most likely is in the `remove` method.
Problem
I am trying to learn more about JUnit and TDD, but I am running into some issues with coupling between test cases. When I am writing a test case for a particular data type's API, say a `Deque<T>`, how can I limit the coupling between the test cases? For instance, if I were writing a test case for the method `insertFirst(T item)`, it seems straightforward to assume that I should be able to assert two things after calling the method on a properly initialized object: - The size of the `Deque` object should have increased by one - If I subsequently call the corresponding `T removeFirst()` method , it should return a reference to the object I inserted with the initial call. However, this creates an undesirable coupling between at least two of my test cases, where one test case passing depends on the correct implementation of another API method. For instance, in order for this test case to pass, I would need a correct implementation for checking the number of items in the `Deque` and also for removing items. If my test for either of those methods was incorrect or incomplete for whatever reason, then my test for the `insertFirst` method would automatically be suspect. What are best practices for avoiding this scenario? Is my approach to writing test cases wrong in some way?