How in general should I know what to test?
During the daily meeting with your team, you find out that the client wants a new feature, and you're in charge of implementing it. You've wanted to write automated tests for a while, and this seems like an excellent opportunity. But where do you start? What tests should you even write?
In this article, we'll go over a process to turn a feature request into a list of tests that we can automate and use to help us in our development process. We'll start by looking at the requirements for the feature, and then go over the goals we want to achieve with the tests we'll be writing. After that, we'll make a more thorough analysis of the requirements that will allow us to write the test list.
The goal of the feature is to allow users to see information about characters from Rick and Morty. More specifically, the user should be able to:
- See a character name, picture, species, gender, and status (Alive/Dead/Unknown).
- Search characters for their name.
- Search characters for their status (Alive/Dead/Unknown).
- See the characters in a paginated fashion.
We're also told that there's an API that will give us the character information.
When writing a test list, we need to keep in mind the goals of the tests. Otherwise, we'll be lost, and we won't know what tests to write. The main objectives are:
- Document the feature - After someone has read the tests, he/she should know the behaviors of the feature.
- Prevent errors now and/or in the future - Whenever the feature stops working, tests should tell us.
Notice that I didn't put "finding unexpected bugs" as a goal for the tests of the test list. That's because trying to think about possible bugs for a feature that doesn't exist yet is extremely hard, time-consuming, and ineffective.
A better approach to find unexpected bugs in a feature is to build the feature worrying only about filling the given requirements and then use a technique like Exploratory Testing to find bugs.
By making a test list, we're looking to discover a set of examples that define essential behaviors of the feature. Those examples will then guide the automated tests we'll write, and by consequence, the implementation of the feature.
The test list is not something that we do once before implementing a feature, and we're done with it. While developing, we'll discover new behaviors that we'll want to test. When that happens, we should add the new test to the test list, so we don't forget about it.
Since we probably won't know every test we should have on our test list before starting to implement the feature, it's not worth it being too exhaustive. If we've got a couple of tests and we've been thinking about more examples for a minute and can't find new ones, we're in a good place to start writing code.
Depending on the size of the feature, knowing which tests to write first can be hard. One solution is to decompose it into smaller sub-features that we know how to write tests for.
Helpful questions to ask when trying to decompose a feature are "What's the simplest version of this feature that will still mean progress?" and "What's the behavior of this feature that without nothing else makes sense?". By answering these questions, we'll instinctively start to decompose the feature into sub-features, and we'll get to the core behavior(s). After we get to the core, we should be able to plan tests for it.
If we analyze the requirements for our Rick and Morty characters feature, we can decompose it into at least three sub-feature:
- Search for characters by name and status
- Show the characters' information
- Shows characters in a paginated format
The core behavior for this example is "Show the characters' information". Without that, searching for characters and pagination doesn't make sense. Also, the application showing the characters' information means substantial progress towards the completion of the feature.
We have already concluded that the sub-feature "Show the characters' information" is a good place to start. And from the requirements, we also know that for each character, we need to show its name, picture, gender, and status. But besides asserting that the application shows the expected information, what other tests can we write?
A great way to find tests is to look at the inputs and outputs of a feature and ask, "What values can the inputs have, and how does that affect the outputs?"
If we analyze our sub-feature, we can consider that there are one main input and one main output. The main input is the API response with the list of Rick and Morty characters. The main output is the information about the characters.
So what kind of values can the list of characters have, and how will that influence what we show to the user? Since we're dealing with a collection, we can use the "Zero One Many Lots Oops" analysis.
The idea of "Zero One Many Lots Oops" is to make us question what happens when:
- The collection is empty
- Only has one item
- Has multiple items
- Has a vast number of items
- There's an error trying to get the collection.
Looking at situation 1 makes us wonder if the API can return 0 characters, and what to do if that happens. I'm not entirely sure if that can happen, so to stay on the safe side, let's prepare the app for it and show a message. So we should write a test that asserts the app shows a message if the API returns an empty list.
Situations 2 and 3 represent common success paths for our sub-feature, so we want to write some tests to tell us if they stop working.
Since we won't work on pagination right now, we won't worry about situation 4. We'll assume that if we can show a small number of characters, like 3, we can also display a large number like 250. We can always get back to this in the end if it gives us more confidence that the application works.
Situation 5 makes us deal with errors. What kind of errors can happen? Should we distinguish between them or not? For our particular example and to not make this overly complicated, we'll treat any HTTP response besides a 200 as an error and don't distinguish between them. If an error happens, we just show a message to the user saying he/she has to reload the application.
So we've gone over the basic requirements, we settled on the core behavior of the feature and we also did an input output analysis aided by the "Zero One Many Lots Oops" methodology to help us find interesting test cases. That lead us to the following tests:
- Shows an empty message when there aren't characters
- Shows one character with expected info
- Shows three characters, each with the expected info
- Shows an error message when there was an error getting characters
Remember that this is by no means an exhaustive list, but it is enough to get us started and to produce something of value when we're done making these tests pass. While we're implementing these tests and writing the code for them, we'll surely remember more use cases that should be tested. When that happens we write those down and implement them when we find it appropriate.
After we're finished with these tests and others that may appear during development, we go back to the feature requirements and repeat the process for the search behavior and the pagination behavior.
Next time you'll be implementing a feature, follow these steps to help you produce a test list that can get you started testing and developing:
- Go over the requirements.
- Decompose the feature into sub-features and start with the core one. If it's not possible to decompose, start with what you have.
- Analyze the feature inputs and outputs to get test ideas.
- Turn the requirements and input/output analysis into a list of tests and start developing. Don't worry if the list is thorough. You'll find more test-cases as you implement the feature.
- One by one, make the tests pass.
- If there are other sub-features, go back to them and repeat the process.
If after applying steps 1, 2, and 3 you don't know where to start, chances are you're not sure about what the feature should do. Try to clarify the requirements with the management team if that's the case.
If you want an exercise to put the above steps into practice, try to make a test list for the search behavior of the Rick and Morty feature. You can find the test list I'd write for that sub-feature in this gist
Don't worry if your test list doesn't match mine exactly. What's important is that the relevant input/output combinations are covered.
If you enjoyed this article you can follow me on twitter where I share my thoughts about software development and life in general.