CridManager 2.0 - Unit testing
Aus open7x0.org
Unit testing
Read more about it...
- Frank Westphal, "Testgetriebene Entwicklung mit JUnit & FIT", ISBN 3-89864-220-8
- Testdriven.com
- XUnit Test Patterns
- jUnit home page
Unit testing wasn't done in CridManager 1.* and I think now, that was the biggest mistake we did. Without unit testing, the design goes weird more or less, and testing new features is a pain. Regression testing is impossible without it. We had more than one occasion in CM 1.* where a simple change broke another function and we did not notice it prior to the release.
Unit testing is the way we refer to Test Driven Developing. (see table on the right for links). In fact, Unit testing is one of the legs of TDD, but not the only one. Others are Refactoring and Continuous Integration.
Closely related to testdriven development are Refactoring and Continuous Integration.
Project structure
We decided on a simple structure for each project. All tests belonging to this project are in a separate source folder inside the project, but use the same package structure as the system under test. That has, in our opinion, two advantages: the tests are closely related to the tested code, so that package and protected visibility can be accessed, and it's easily possible to exclude the tests for release.
An example
The TDD encourage people to do the tests code first, and then write the simpliest code that pass the test. Doing that, you are sure that your code works, but only for what ever you wrote a test code for. You can never be sure that your code works if you haven't tested it. Then, after several lines of code, TDD recommends to refactorize the code too. I will explain this further with a little example.
You want to make a program that calculates the sum of two integers. (Yes, it is a very simple example). The way a programmer would normally do it is to start to code and when he have 100 lines of code, start to test it introducing the numbers and watching on screen if it is what is expected. 2 months later, he is asked to change the code to include float numbers, and after making the changes, he will need to retest manually all the numbers again. This is crazy.
Coding tests first will give you a complete acceptance test for your code you can run automatically for every change you made in your code, and you will detect any simple error on the code. If after a change, you passed all tests ok, you will be complety sure that your code still works for the things you have tested.
Now, the example for the sum. You still don't have any line of code
1.- Test that you can instantiate the class in your test class
package tests
import de.cm.main
public class TestSum {
public static void testClass() {
Sum a = new Sum();
}
}
If we run this test, it will fail because it cannot find the class we are trying to get (Eclipse will show this instantly). In fact, it doesn't even compile. So, we write the class that accomplish the test
package de.cm.main
public class Sum {
}
Now, if we pass the test, we will see that it works. OK!
2.- Now, let write a test to be sure that 1+1=2
package tests
import de.cm.main
public class TestSum {
public static void testClass() {
Sum a = new Sum();
}
public static void testSum1_1() {
Sum a = new Sum();
assertTrue(a.sum(1,1)==2,"It doesn't work");
}
}
Now we will see that it doesn't compile. We need to modify our class (let Eclipse do this with the quick fix Create method sum(int, int) in Class Sum):
package de.cm.main
public class Sum {
public void sum(int a, int b) {
return 0;
}
}
If we try to execute the test, it will compile, but it will fail because we are telling the test to check the result of the method to be 2, and it is returning 0.
Now, TDD says that you need to get the test passed OK with the simplest code you can. So, using that rule you write:
package de.cm.main
public class Sum {
public void sum(int a, int b) {
return 2;
}
}
This code will pass the test. This demostrate the philosophy of TDD. At this moment, we have a test set and a code that pass the test. Then we could say that we have finished the work. :) Obviously not. We have a code that is at least able of calculating the sum of 1 plus 1 correctly. But nothing more. we cannot say anything more than that. So, we will need to be sure we have a good code to have more tests. We were asked to make a program to sum integers. Will we need to make a test for each integer combination? No, but just enough tests to cover the most of the requierements that make us to write a code.
TDD philosophy encourage us to write as much tests as we can focus on requirements of the code. It has no sense on our example to make a load test or a recursive test because our requirements is just to sum 2 integers. We will focus on integers then. Next test:
package tests
import de.cm.main
public class TestSum {
public static void testClass() {
Sum a = new Sum();
}
public static void testSum1_1() {
Sum a = new Sum();
assertTrue(a.sum(1,1)==2,"It doesn't work");
}
public static void testSum1_2() {
Sum a = new Sum();
assertTrue(a.sum(2,1)==3,"It doesn't work");
}
}
In this case, the test will fail because our sum method returns always 2. Again, following the write-the-simplest-code-you-can theory, we modify our sum method to pass the test.
package de.cm.main
public class Sum {
public void sum(int a, int b) {
if (a==1)
return 2;
else
return 3;
}
}
Again, this code is the simplest one and it pass the test. In our example, we could continue to make tests over different numbers until we make a 1.000.000 lines of code program. At this point, we should use a little thought about adding numbers and perhaps write a test for Commutativity. I leave the older tests out, but of course they're still there.
package tests
import de.cm.main
public class TestSum {
public static void testSumCommutativity() {
Sum a = new Sum();
assertTrue(a.sum(2,1)==a.Sum(1,2),"Sum is not commutativ");
}
}
This fails again. Now it's time to think about refactoring our code into a more general form:
package de.cm.main
public class Sum {
public void sum(int a, int b) {
return a+b;
}
}
We have changed the code of our main class. Usually programmers make a lot of changes in codes when bugs are found, and you always will see a terror face on the programmer waiting to see on the production envoronment to serve 1 million of users if the change will work or not..... Because if you don't use this TDD philosophy we will never be able to test all the things that you code can be. But, in our example, because we have a set of tests, we can check if our change will work for all of our tests. The developer will be more relaxed and probably the risk to rollback a change in a production enviroment is very small.
Remember that it is better to refactorize the test units too. In our case, we tested the instantiation of the class on our first test, but we repeat that on the rest of the tests. So the first test is not needed any more.
On Internet you can find a lot of resources and similar "tutorials" and examples than this. The problem is to put all this to work in a real code. Remember:
- Write tests first, and write all the tests are need to satisfy the requierements of your code. This owe you to have clear requirements and a good design of inter-program and APIs relationships, in order to write the correct test.
- Write one test at a time and make sure your code satisfies this test before writing the next test. Use a scratch pad for writing down more tests you think you need, as long as it's time to write them in code.
- Code the simplest code that pass the test. After that, take a look to the code, and try to code it better (refactorize) if you can. Don't worry, you always have your tests to know if you are doing the correct thing.
- Don't think you are wasting the time writing the tests first. This is what all of us think when we hear about TDD for the first time. "This is a lost of time" I always hear from others. No, what you waste now writing tests is the half of the time (for sure) or less that you will dedicate to solve a bug with a non-TDD code, and you will be more relaxed and confident on changes on the code. Your time and your soul will thank you a lot.
Our first steps into unit testing are in the repository. Do not hesitate to give some (fair) criticism.

