|Read more about this book|
(For more resources related to this subject, see here.)
Getting started with TDD
Briefly, Test Driven Development is the strategy of writing tests along the development process. These test cases are written in advance of the code that is supposed to satisfy them.
A single test is added, then the code needed to satisfy the compilation of this test and finally the full set of test cases is run to verify their results.
This contrasts with other approaches to the development process where the tests are written at the end when all the coding has been done.
Writing the tests in advance of the code that satisfies them has several advantages. First, is that the tests are written in one way or another, while if the tests are left till the end it is highly probable that they are never written. Second, developers take more responsibility for the quality of their work.
Design decisions are taken in single steps and finally the code satisfying the tests is improved by refactoring it.
This UML activity diagram depicts the Test Driven Development to help us understand the process:
The following sections explain the individual activities depicted in this activity diagram.
Writing a test case
We start our development process with writing a test case. This apparently simple process will put some machinery to work inside our heads. After all, it is not possible to write some code, test it or not, if we don’t have a clear understanding of the problem domain and its details. Usually, this step will get you face to face with the aspects of the problem you don’t understand, and you need to grasp if you want to model and write the code.
Running all tests
Once the test is written the obvious following step is to run it, altogether with other tests we have written so far. Here, the importance of an IDE with built-in support of the testing environment is perhaps more evident than in other situations and this could cut the development time by a good fraction. It is expected that firstly, our test fails as we still haven’t written any code!
To be able to complete our test, we usually write additional code and take design decisions. The additional code written is the minimum possible to get our test to compile. Consider here that not compiling is failing.
When we get the test to compile and run, if the test fails then we try to write the minimum amount of code necessary to make the test succeed. This may sound awkward at this point but the following code example in this article will help you understand the process.
Optionally, instead of running all tests again you can just run the newly added test first to save some time as sometimes running the tests on the emulator could be rather slow. Then run the whole test suite to verify that everything is still working properly. We don’t want to add a new feature by breaking an existing one.
Refactoring the code
When the test succeeds, we refactor the code added to keep it tidy, clean, and minimal.
We run all the tests again, to verify that our refactoring has not broken anything and if the tests are again satisfied, and no more refactoring is needed we finish our task.
Running the tests after refactoring is an incredible safety net which has been put in place by this methodology. If we made a mistake refactoring an algorithm, extracting variables, introducing parameters, changing signatures or whatever your refactoring is composed of, this testing infrastructure will detect the problem. Furthermore, if some refactoring or optimization could not be valid for every possible case we can verify it for every case used by the application and expressed as a test case.
What is the advantage?
Personally, the main advantage I’ve seen so far is that you focus your destination quickly and is much difficult to divert implementing options in your software that will never be used. This implementation of unneeded features is a wasting of your precious development time and effort. And as you may already know, judiciously administering these resources may be the difference between successfully reaching the end of the project or not. Probably, Test Driven Development could not be indiscriminately applied to any project. I think that, as well as any other technique, you should use your judgment and expertise to recognize where it can be applied and where not. But keep this in mind: there are no silver bullets.
The other advantage is that you always have a safety net for your changes. Every time you change a piece of code, you can be absolutely sure that other parts of the system are not affected as long as there are tests verifying that the conditions haven’t changed.
Understanding the requirements
To be able to write a test about any subject, we should first understand the Subject under test.
We also mentioned that one of the advantages is that you focus your destination quickly instead of revolving around the requirements.
Translating requirements into tests and cross referencing them is perhaps the best way to understand the requirements, and be sure that there is always an implementation and verification for all of them. Also, when the requirements change (something that is very frequent in software development projects), we can change the tests verifying these requirements and then change the implementation to be sure that everything was correctly understood and mapped to code.
Creating a sample project—the Temperature Converter
Our examples will revolve around an extremely simple Android sample project. It doesn’t try to show all the fancy Android features but focuses on testing and gradually building the application from the test, applying the concepts learned before.
Let’s pretend that we have received a list of requirements to develop an Android temperature converter application. Though oversimplified, we will be following the steps you normally would to develop such an application. However, in this case we will introduce the Test Driven Development techniques in the process.
The list of requirements
Most usual than not, the list of requirements is very vague and there is a high number of details not fully covered.
As an example, let’s pretend that we receive this list from the project owner:
- The application converts temperatures from Celsius to Fahrenheit and vice-versa
- The user interface presents two fields to enter the temperatures, one for Celsius other for Fahrenheit
- When one temperature is entered in one field the other one is automatically updated with the conversion
- If there are errors, they should be displayed to the user, possibly using the same fields
- Some space in the user interface should be reserved for the on screen keyboard to ease the application operation when several conversions are entered
- Entry fields should start empty
- Values entered are decimal values with two digits after the point
- Digits are right aligned
- Last entered values should be retained even after the application is paused
User interface concept design
Let’s assume that we receive this conceptual user interface design from the User Interface Design team:
Creating the projects
Our first step is to create the project. As we mentioned earlier, we are creating a main and a test project. The following screenshot shows the creation of the TemperatureConverter project (all values are typical Android project values):
When you are ready to continue you should press the Next > button in order to create the related test project.
The creation of the test project is displayed in this screenshot. All values will be selected for you based on your previous entries: