As a developer, you are writing new functionality. The implementation is ready, but you have to write tests. To simplify the tests, you create additional helpful methods in one of the production class:
class Employee {
...
//for testing purpose only
static Employee of (String name) {
this.surname = "rodo";
this.name = name;
}
bool isEmployeeOnPayrol (Payroll payroll) {
return payroll.hasEmployee (this, null);
}
}
Completed. Code review – comments are not allowed, removed. Merged.
After some time … a problem appeared in production. The financial report shows that there are no employees on the payroll! WTF. A critical error that should be immediately resolved.
The whole team is desperately analyzing what is wrong. After many hours, the problem was found. Someone, use your test code from the Employee class, in the new financial functionality! Now the problem is known. The ‘only’ thing is fixing thousands of records, running the correct financial function, checking data integrity, etc., in a production environment! After two days of stress and hard work, the problem was finally solved.
A small thing, a big problem. You blame the programmer who used your method designed for testing purposes, but it wasn’t his fault. It was yours because the method exists in the production code:
Employee {
...
static Employee of (String name) {
this.surname = "rodo";
this.name = name;
}
bool isEmployeeOnPayrol (Payroll payroll) {
return payroll.hasEmployee (this, null);
}
}
What is the production code? The code in:
/src/main
What is the testing code? The code in:
/src/test
That’s it. Simple distinction.
Sometimes developers, and as I remember I do the same, put some utils/tools or helpful code to support testing in the production space. This is not good because:
- someone can use this code and break the production system
As the story above shows, it is possible, especially in complex systems.
- misleads programmers who read the code
Developers when reading the code in /src/main assume, correctly, that the code is production only. They don’t have time to analyze where the code is used.
- make refactoring more difficult
Several times I found that the most difficult part of refactoring was the ‘dead’ code and the code that supports testing.
- code coverage system complains
The code that supports testing is in the production scope, so it should be covered by tests! Although it looks crazy, testing test code, but it is required.
How it should be?
You should place all logic-supporting tests in the test scope: /src/test.
I prefer class extensions where the new functionality is closely related to the class/concept:
/src/test scope
PersonLogicInTests extends Person {
static Person returnCustomizedPerson ()
static Person returnTestPerson ()
static int compareForTest (Person p1, Person p2)
}
For functionalities that are generic and do not fit into a specific class, it is better to have them separately:
/src/test scope
class FunctionsSupportingFinanceTesting {
static FinanceReport createSimpleFinanceReport ()
static Data prepareDataForFinanceAnalysis ()
}
I am convinced that most helper functions should be static – simple and isolated – not referencing any state or other methods. Tests should be as clear and simple as possible.
Summary
In this post, we saw the littering of production code with test code and its consequences. But there can be more cases: a production configuration that can support DEV and QA environments, specific code that supports specific environments, infrastructure, configuration, etc. You should pay special attention to these cases and try to isolate them from the production space or at least clearly label them.