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:
- Recognize that the desired results are observable only through subsequent calls to other methods.
- 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.