It is pretty hard writing an article on something that so many super cool authors have written books about. But, as I said it is my own experience learning and embracing TDD that I want to share here so that maybe I can help someone out there that can relate to this. And also I can always remind myself of the process I went through while learning it.
Bottom line is that nobody can teach you a programming approach like this by writing or making videos about it. They can only get you started and they can tell you why you should do it. The real power comes by you actually digging into it. The more you do it the more you master it and the more you can actually feel the benefits of it. I didn’t believe when people said it was addictive, in matter of fact I opposed to the whole idea. I was one of the people that thought this is a waste of time and that you can achieve more by just writing production code….and boy….was I wrong about it.
We have to start somewhere, so why not at the very core of it. The unit test definition….
Unit test definition:
A UNIT TEST confirms the functionality of a small UNIT or component in a larger system.
What does this mean? Well, it means isolating a small part of the system to test its proper internal functionality (emphasize on the “internal”). This, as we will see later in the text can be achieved bytesting for expected output or verifying that a method from another module has been called, depending on the test type.
Why Unit Testing?
Developer testing is more than just confirming functionality. It is an alternate way of coding. It is a culture. It puts us in the shoes of the user of the unit/class. It is one of those things that eliminate the fear of changing and improving the code. Adds that extra level of safety that we’re not going to break something if we change it in the code because we have a suit of tests that will tell us if it still works or we broke it. The need for debugging is taken down to a minimum.
It provides inspection at the point of creation…opposed to testing the functionality after the development has been completed (It can cost up to 500% more to fix a bug after it has been shipped than during development). This way we have far smaller cycles of development and testing that provides more “agile” approach i.e. you are more bulletproof to changes.
Some other test properties:
- Higher quality (more tests – fewer defects)
- Living documentation (specification of what the software can do under given circumstances)…. TESTS ARE THE SPECS …(we’ll come back more on this later).
- Well-crafted code
- Automatic regression harness (insurance that adding new features will not break something else in the system).
And the list goes on and on….and on….
Types of Unit Tests
– Testing for output value
– Testing if a method is being called
Greenfield vs. Brownfield
It is much easier to start TDD on Greenfield project because you can make it embrace decoupling unlike the Legacy systems which might have tight coupling and could be very hard to test.
As far as brownfield projects are concerned there are many strategies that can be taken on these legacy systems so they can be tested like Characterization Tests proposed by Michael Feathers in his book “Working Effectively with Legacy Code” .
Choose your testing framework wisely (explore NUnit, xUnit etc.)
Test Driven Development
TDD is one of the many agile methodologies and XP (eXteme programming) that recently created a lot of buzz and is taking more and more interest on its own. Kent Beck, who is credited with having developed the technique, states that TDD encourages simple designs and inspires confidence. And I totally agree with that statement. Once again the more you use it the more you benefit from it.
Software is often shaped as it is created. That’s why it makes sense to create unit tests just before we implement a feature.
A little bit of tests, a little bit of code, a little bit…and so on….Which in the end results in the Red-Green-Refactor routine for TDD.
- Create a failing test
- Write only enough code that is sufficient for the code to pass
- Refactor – Improve the internal implementation without changing the external behaviour
You should end up with just enough code and design to make what you’re doing work. I guess this raises the question of overworking things and designing big up front. This principle is exactly what opposes that.
What tests to write?
Same thinking when creating a class or a set of classes to achieve but from a different view.
One needs to look at starting of work as what do you expect this class or group of classes to do? What is the CORRECT input, and what is the CORRECT output.
Where to start?
Think like a client/user of a class.
Start by creating a test project and another project for the SUT.
Think about what do you want to do? Do we want a class, a set of classes, how are they achieving what they are supposed to do? Then we start writing test cases for valid and invalid input and test for valid or expected output.
Write tests for the more obvious cases first, cases that work, the main way the system will be used. Then work your way to the more specific side cases and handling invalid input.
·What does “Good suit of tests mean”?
Not only testing for coverage but covering all the valid and invalid cases that the SUT is supposed to work with, and testing for expected results, thus making sure that they SUT is working correctly.
How do I write unit tests?
Arrange, Act, Assert.
As Kent Beck says… if you don’t know how to write the test write the line of code that will actually test the SUT, then work your way up to making the test work.
Maintenance of unit test code
Test code is just as important as production code. They are mutually testing each other. Test code is maintaining the production code but the production code is maintaining the tests (Uncle Bob’s analogy of double entry bookkeeping). Tests should be organized hierarchically as classes and treated as though they are the production code itself. We need to keep the test code maintainable and DRY.
When you get new requirements and need to add new features, and your cod changes, the tests get updated too. This can cause the test code to become clumsy. We need to maintain it in order to be able to expand on it later.
· Repeatable – can be run at any time
· Independent – should run in any order
Unit Test Organization
· Happy path (Does the system behave as we expect in with normal input values)
· Sad path(test for expected exceptions and behavior if illegal input is provided)
These two test approaches can be separated in to two separate test classes.
Why bother refactoring?
– To improve the quality of the code(better, faster): Readability, Maintainability, Scalability, Extensibility
– Because we always tend to write better code. Something that is done in code is never finished, it can always be improved. We should always tend to leave the systems better than we found it.
– TDD can be seen as a “Clean as you go approach” with refactoring step being the cleaning
What is the definition of DONE?
– Done is not simply delivering the product and passing a set of failing unit tests. It is also cleaning after you’re done
Skipping steps in favor of a deadline implies TECHNICAL DEBT . It is the same as a money debt. We sacrifice some of the system properties to meet a deadline etc.This later comes as interest in form of code rot etc.
Refactoring is the operation we perform to pay off that debt.
We will explore much more test driven things in the future.
Until then…Happy coding.