TDD or Unit Test?
There is a big difference between TDD and Unit Test. TDD is based on unit tests, but both concepts are not exactly synonymous.
TDD (Test Driven Development) is a development methodology that measures project progress according to test results.
Unitary Test is one of the types of testing that is usually done in software. There are also integration tests, system, user, load, performance tests and other.
A unit test should test a single scenario of a system method without relying on external resources, such as databases, settings, and other factors that may interfere with the result. In summary, the unit test should test only one thing.
TDD in practice
TDD works well for some types of projects, especially when requirements are well defined and it is possible to write each test before implementation. It’s the ideal case.
In practice, however, many of the requirements are evolving throughout the project, both due to changes in business, as well as the failure to elicitate and even due to the maturation of the users' understanding. This and other reasons prevent a "pure" TDD because it adds a overhead of very great effort and many teams cannot afford this luxury.
Another factor that hinders is the maturity of the team. It is no use teaching a junior to use Junit and thinking that he will be able to do TDD. It is not easy to create a testable system, which is one of the qualities of a good software architecture.
The important thing is to understand that TDD is not a silver bullet .
Unit testing in practice
Test the important features
What I’ve seen in the market and what has worked out is to focus the test on what is important. There is no time to test everything, so the focus should be on the central functionalities of the system and not on registrations (CRUD).
Many people have doubts about what to test, some suggest even testing framework routines or, in the case of Java, check if Hibernate is saving the data in the database. This drives the developer crazy and goes in the wrong direction, because in the end he will test things that should be working and leave aside the most important.
Unit test, but not so unitary
The test need not be exactly unit, in the sense of testing only one method.
I worked on a project that involves integration with over half a dozen systems. These are integrations for all tastes: Web Services, files, databases. Many of the systems are still in the process of change. It would be impossible to create mocks for everything and having to update the mocks each time a system changed.
So to quote an example of file import testing, I created a Junit method that performs the task (job) import and check if the record has been stored successfully. Although you end up testing a lot at once, this is being enough to ensure functionality.
It is not "sin" to access the database in unit test. The bad thing is having the test fail frequently if the tables are not in the state necessary for the correct execution. But nothing prevents initializing values in setup test. Or there is the option to use a framework such as Testng, where you can set the execution order. Actually, it’s not exactly the order, what you can define is that an X test method depends on the Y test you have run before. I particularly like the Testng.
The design counts a lot
Speaking specifically about your service layer, I can pass on the lessons that experience has brought me.
The most important thing is to always design your classes and methods so that they are testable. At first it is difficult and you should spend time with Refactoring. Make each important method or routine as sparse as possible with other routines and static settings. Abuse of Inversion of Control.
Follow an example I will give in the next topic
A fictional example
Let’s create a class responsible for importing a file. Suppose the first implementation is quite naive:
public class ImportadorArquivo {
public void importar() {
//carrega local da configuração
File arquivo = new File(Configuracao.LOCAL_ARQUIVO);
//lista com itens lidos do arquivo
List<Entidade> entidadesLidas = new ArrayList<>();
//vários comandos para ler e interpretar o arquivo, colocando itens na lista...
String[] linhas = FileUtil.lerLinhas(arquivo);
for (String linha : linhas) {
Entidade e = new Entidade();
//preenche entidade com os dados da linha...
entidadesLidas.add(e);
}
//grava itens no banco
for (Entidade e : entidadesLidas) {
JPAUtil.getEntityManager().persist(e);
}
}
}
It’s a really bad method to test, right? Let’s refactor this class to make it more testable:
public class ImportadorArquivo {
private File arquivo;
private EntityManager em;
//recebe arquivo em entity manager (IoC)
public ImportadorArquivo(File arquivo, EntityManager em) {
this.em = em;
this.arquivo = arquivo;
}
public void importar() {
List<Entidade> entidadesLidas = ler();
salvar(entidadesLidas);
}
public List<Entidade> ler() {
List<Entidade> entidadesLidas = new ArrayList<>();
String[] linhas = FileUtil.lerLinhas(arquivo);
for (String linha : linhas) {
entidadesLidas.add(interpretar(linha));
}
return entidadesLidas;
}
public Entidade interpretar(String linhaArquivo) {
Entidade e = new Entidade();
//preenche entidade com os dados da linha...
return e;
}
public void salvar(List<Entidade> entidadesLidas) {
for (Entidade e : entidadesLidas) {
em.persist(e);
}
}
}
Note that each method now has a well-defined and distinct action. This allows you to test each action individually.
Also note that the class receives the settings by parameter. This is a type of Control Inversion. This allows you to test the class without any framework or magic.
Imagine the potential of a good design if applied to the whole system?
Real examples
I’ve been working on some small libraries and frameworks using TDD, at least to some extent.
I’ll list here two recent and updated Github projects with approximately 90% unit testing coverage:
- Myq: a library to organize, load, and process SQL queries in a Java project.
- T-Rex: an Excel spreadsheet generator via templates and an Expression language.
While I know I still have a lot to learn and improve in my implementations, I suggest it’s a good exercise to look at the class design in these projects and how they allow unit tests to run without any framework mock.
Additional reading
For an interesting discussion on the subject, see my article The TDD is dead? and, if you understand English, watch the video Is TDD Dead?.
If other people want to contribute to this post feel free even to disseminate knowledge!
– Macario1983