4

I have the following functions :

Get(ds,filename) - gets a file from a data source ds.
Put(ds,filename) - puts a file into a data source ds.
List(ds) - lists all files in data source ds.
Delete(ds,filename) - deletes file from data source ds.

How would I unit test these ? Do I write one unit test per function ?

What's bugging me is that to test the Put function I need to call the List function :-(

TestPut() {

 Put(test.data.source,test.file.name)
 files = List(test.data.source)
 Assert( files == test.file.name ) 

}

Hence, this unit test tests both "List" and "Put". Is it fair to say that I don't need a separate unit test for List ? Cheers !

4 Answers 4

4

What you're observing is common whenever the job of a method is to change some internal state (like the set of things stored in an internal collection).

There are two options:

  1. Recognize that the desired results are observable only through subsequent calls to other methods.
  2. Add a way to observe the internal state directly. This generally requires making some otherwise private or hidden thing (the internal list) a visible part of the API of the thing you're testing.

I typically prefer the first option. Every now and then I go with the second.

Notice that with the first option, you are not testing a single method at a time. You are testing that the methods work together to produce the desired result. For example, you are testing that an item that was added by Put appears in the list retrieved by List. In these cases, I like to name the tests to indicate the collaboration between methods. I might call your test something like ListIncludesEveryFilenameThatWasPut.

On the other hand, I might focus less on the methods, and more on the abstract responsibility that the methods implement. In your case, the datastore has the responsibility to remember filenames. So I might call the test DatastoreRemembersFilenames.

As for how many tests you need, the number of methods you're testing is not a reliable guide. You will probably need at least one test per method, but often you will need more, and sometimes many more.

The key is in the complexity of the responsibility (or responsibilities) being tested. This can depend on a variety of things, such as whether there are different categories of inputs, and whether the system should respond the same or differently to the same inputs under different conditions.

Without knowing more about the intent of the unit you're testing, I can imagine a few scenarios I might want to test:

  • ds is null (for any method)
  • filename is null (for any method)
  • filename is empty (for any method)
  • The system currently has no with the name in filename.
  • The file exists, but is not writeable.
  • Attempt to add when the data store is full.
  • Attempt to delete a file that is not in the data store.
  • Attempt to add a file that is already in the data store.
  • Adding the first file.
  • Add many, many files.
  • Add, delete, list, and get on multiple threads simultaneously.
  • Data store was newly created.
  • Data store has performed many, many operations.

You may decide that your system doesn't care about some of these scenarios (perhaps because other code prevents them from happening). But if these scenarios are possible, you will likely want to figure out what results your system should produce in each case. Then each combination of inputs, starting conditions, and results can be a test case of its own.

2

Alex is correct. In addition, you probably will want multiple tests per function covering both positive and negative cases. What happens if you try to get or delete a file that doesn't exist? What happens if the data source doesn't exist? How about if you don't have permissions? What if you want to put and have it add a new file vs modifying an existing file? etc, etc. Each of these should be a test case, not just one test per function that covers the happy path. Your tests should cover every branch within each function.

1

You're right that TestPut tests both Put and List, but that on its own doesn't mean List doesn't need its own unit test. For one thing, supposing TestPut were to fail, having a TestList would help you know whether the bug is in Put or List.

1

What I usually do in such cases is I have a test for Put and a test for List. The test for Put might (and probably will) use the List method, but it focuses on the boundaries of the Put method. I will make sure that the code coverage for the Put method is complete within the TestPut.

Likewise, the TestList method would focus on the boundaries of List, ensuring complete coverage of the List method.

Not all tests for List for example do need a Put. I would also have tests checking that no elements exist prior to Putting anything. Not all tests for Put would need a List. A test that uses Put with an illegal argument, expecting an exception would not use List.

The main point would be that the test for any of your methods might use any of the others, but it would be focussing on the boundaries of the method under test. When testing method A, I assume the other methods function as expected and any problems with the other methods will be found in their tests.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.