Michael Francis
Michael Francis

Reputation: 8757

Keeping unit tests separate and independent

I'm having trouble getting my unit tests to stay independent of each other. For instance, I have a linked list with two append methods, one that takes a single element and appends it to the list, and one that takes another list and appends the whole thing; but I can't test the second append method (the one that takes a whole list) without using the first append method to populate the list I'm passing in. How do I keep the unit tests for these two methods separate from each other?

Upvotes: 1

Views: 443

Answers (2)

Dirk Herrmann
Dirk Herrmann

Reputation: 5949

The situation you describe happens everywhere in testing: You have some class or library to test. The class or library has certain methods / functions that need to be tested, and for the test of some of these, you have to call other methods / functions of that same library.

In other words, when breaking down the test according to the four phase test pattern (setup, exercise, evaluate, cleanup), you want to call your class / lib in the exercise phase. However, it seems annoying that you have to call some elements of it also in the setup phase, and, possibly, also in the evaluate and/or cleanup phases.

This is simply unavoidable: You mentioned that in the setup for the list append function you had to use the single-element append function. But, it is even worse: You also had to use the constructor of your list class - no chance to get away without that one. But, the constructor could also be buggy...

What can certainly happen is, that tests fail (or, mistakenly pass) because the functions called in the setup are defective. A proper test suite, however, should (as was mentioned in the comments) also have tests for the other (call them lower-level) functions.

For example, you should have a number of tests which check that the constructor of your class works correctly. If at some point you modify the constructor so it becomes defective, all tests that use the constructor in the setup phase are no longer trustworthy. But, some of the tests that test the constructor itself (and thus call it in the exercise phase) should fail now.

From the overview of the test results you will then be able to identify the root cause of the test failures. This requires some understanding about the dependencies: Which of the tests focus on the lower-level aspects and which are higher-level in the sense that they depend on some lower-level functionality to work.

There are some ways to make these dependencies more apparent and therefore make it easier to analyse test failures later - but none of these are essential:

  • In the test-code, you put the tests for the lower-level aspects at the top of the file, and the more dependent tests further to the bottom. Then, when several tests fail, first look at the test that is closest to the top of the file. Note that the order of tests in the test code does not necessarily imply an execution order: JUnit for example does not care in which order the test methods are written in the test class.
  • As it was suggested in the comments, you may in addition configure the test framework to run the lower level tests before the others.

Upvotes: 2

bneely
bneely

Reputation: 9093

You can create one method which itself is not a unit test method but instead creates the conditions for multiple tests, then performs verification of the results. Your actual unit test methods will call into this other method. So you can use the same data set for multiple tests, and not introduce dependencies between test methods.

I don't know what language you are using, but here is an example for Objective-C in Xcode 5 with the new XCTest framework. I would do something like this:

- (void)performTestWithArray:(NSArray *)list
{
    NSMutableArray *initialList = ...; // create the initial list you will use with multiple tests
    [initialList addObjectsFromArray:list];
    XCTAssertTrue(testCondition, @"message");
}

- (void)testAddSingleElement
{
    NSArray *array = @[ @"one element" ];
    [self performTestWithArray:array];
}

- (void)testAddList
{
    NSArray *array = @[ @"first element", @"second element", @"third element" ];
    [self performTestWithArray:array];
}

Upvotes: 0

Related Questions