The last blog post in the series introduced some of the new features within Visual Studio 2012, applicable to Test Driven Development (TDD). This blog post is going to show you how to practically use all of those new features and apply them to Test Driven Development. You are going to see a full cycle of TDD using Visual Studio 2012, during the implementation of a simple calculator example!
Phase 1: Write a unit test for a new functionality
Respecting the Test Driven Development approach, you have to create your unit tests before starting with any implementations. In our example we have to write some unit tests for the methods “Addition” and “Multiplication”. But first of all, we have to add a project of type “Unit Test Project” to our solution (if you do not already have one).
You may now add the necessary unit tests.Here is an example of the unit tests you could add based on the unit test framework NUnit:
Please note that the “Calculator” class and its methods “Addition” and “Multiplication” do not exist at this stage yet.
Visual Studio 2012 provides the possibility to generate the missing code in an automatic way. For that you just have to right-click on the “new Calculator” definition in the unit test project and choose to generate the class via the “Generate/New Type“ option in the menu.
A wizard opens and you are now able to configure multiple options such as the type (classe, struct, interface, enum), the access (public, internal), the destination project and the file name for the generation of the missing class.
The next step, after having auto-generated the “Calculator” class, consists of auto-generating the missing methods within this class. This can be achieved in almost the same way as it was done for the missing class. You do a right-click on the method calls "calculator.Addition(…)” and “calculator.Multiplication(…)” in the unit test project and you generate them via the “Generate/Method Stub” option in the menu.
Here is the auto-generated source code of those two methods:
In the last step of this phase you have to open the “Test Explorer” window where you may now execute all your unit tests. This can be done by clicking on the “RunAll” button or by using the already explained “Post Build Test Runs” option (see the previous blog post in the series).
As expected your unit tests will fail, since the corresponding source code has not been implemented yet. We will see how to do that in the next phase.
Phase 2: Implement the minimum code necessary to pass the test
Now in this phase, the only thing that needs to be done, is to implement the expected functionalities. The idea is to develop the minimum code necessary, which responds to the functional requirements. Everything that concerns optimization and amelioration must not be addressed since it will be treated later in the next phase (refactoring).
Following the implementation you may now restart your unit tests by clicking on the “RunAll” button or by using the already explained “Post Build Test Runs” option (see the previous blog post in the series). Your should see that you unit tests have been terminated successfully. If this is not the case you have to review your code and iterate until all of your unit tests pass successfully.
At this stage the source code corresponds exactly to the functional needs and it provides the expected behavior. The final project structure includes a unit test project as well as an application implementation project.
But the source code might not be optimized. Its quality might not adhere to your quality standards, so it has to be ameliorate. The refactoring can be done without any problems since the unit test assure that there are no regressions. Moreover, regressions can be detected very quickly and thus can be handled as soon as possible. This is going to be explained in the next phase.
Phase 3: Refactor and optimize the source code
The process of improving your source code after the initial implementation phase (Phase 2) is called “Refactoring”. The source code structure is modified internally without any modifications to the external behavior (very important!!). A source code that just “works” is now transformed into a source code that works in an optimal way. Most of the time, the resulting source code is executing with better performance, using less memory and/or with a better software design.
The refactoring consists of the following steps (non-exhaustive list):
- Detect and eliminate all code duplication
- Limit complexity and the number of classes
- Simplify and optimize method algorithms
- Relocate, rename and harmonize methods
- Improve code readability
- Remove not used code (also called “dead code”)
- Add comments to complex code sections
In our simple example there is nothing to be refactored, since there are neither enough methods nor enough classes. But this last step has to be done in bigger developments at the end of each cycle. Afterwards, a new development cycle starts with new functionalities from Phase1 on.