All tests run through the following stages:
- Test Setup: The goal of this stage is to create an environment for your test. It could be as simple as instantiating an object or as complex as installing all the dependencies for your web application.
- Text Execution: The goal of this stage is to run through a certain scenario and validate the outcomes.
- Test Clean Up: Clean up any dependencies of your test.
Let’s go through an example.
Assume that just finished a new queue Implementation called MyQueue. The class MyQueue supports the methods insert and remove. It also provides a property for the count. There are many things that you need to verify this class implementation. For example:
- That you can insert values into the queue.
- That the count property reflects the number of items in the queue.
- That the remove method would return the items in the right order.
- That the remove method and count property reflect the proper behavior you designed when the queue is empty. In our example, we aim for the remove method to return nil and count to return zero.
Now, let’s think of the test cases. We can start with a test case that inserts several values into the queue and verify that they get returned in the right order when removed.
Assuming that we have a helper method called ExpectEqual. This method accepts two operands and reports an error if the two are not equal. The code for the test case will be:
let values = [19, 20, 40]
let q = MyQueue()
for v in values {
q.insert(val: v)
}
ExpectEqual(values.count, q.count)
for v in values {
ExpectEqual(v, q.remove()!)
}
This test covers many things that we want to test. But there are more:
- Are we sure that the count property returns zero if it was called when the object is fresh?
- How about when the last element is removed?
- Does the object accept inserting and removing if they are interleaved?
You can cover these extra ideas by either modifying the test or create more test cases where you cover different ideas via different scenarios. There is no clear cut way to do that. I recommend that you keep the size of your test cases manageable. A very long and complicated case may cover a lot of ideas, but it could be hard to understand when you come back to it later. It could even make things harder to debug when failures happen. On the reverse side, the setup and clean up in some test cases could be pretty expensive so you want to keep the number of the test case manageable as well. You can combine several related ideas such as having a single test for all the range checks on an empty object.