Treat Your Unit Tests As Production Code - DARGO - Amadeus - Soirée du Test Logiciel Sophia 2019
1. Treat your unit tests as production code
Sandor DARGO (Amadeus)
17/10/19 3ème édition Soirée du Test Logiciel Sophia #STLS2019 1
2. Merci aux Sponsors !
17/10/19 3ème édition Soirée du Test Logiciel Sophia #STLS2019 2
3. Agenda
Define unit tests
You and unit tests
Test vs Production code
Problems with unit tests
Some solutions (no silver bullet)
Conclusion
17/10/19 3ème édition Soirée du Test Logiciel Sophia #STLS2019 3
4. Who am I?
Sándor DARGÓ
Software developer in Amadeus
Enthusiastic blogger http://sandordargo.com
Passionate traveller
Curious home baker and cook (@sourdad.baker)
Happy father of two
17/10/19 3ème édition Soirée du Test Logiciel Sophia #STLS2019 4
5. What is a unit test?
- Automated and repeatable
- Written in the language of the code
- Exercises a small part of the program (a unit)
Ideally, they are
- Independent
- Isolated
17/10/19 3ème édition Soirée du Test Logiciel Sophia #STLS2019 5
6. What is a (good) unit test?
“A unit test is a piece of code that invokes a unit of work and checks one
specific end result of that unit of work. If the assumptions on the end result
turn out to be wrong, the unit test has failed. A unit test’s scope can span as
little as a method or as much as multiple classes.” - The Art of Unit Testing
by Roy Osherove
17/10/19 3ème édition Soirée du Test Logiciel Sophia #STLS2019 6
7. Are you confident in your unit test
suite?
Are they all green?
Are they all executed?
Do they all assert something?
Do they all assert something meaningful?
Do they all assert a single logical thing?
Are they maintained?
Do you delete unit tests?
How frequently do you have to update them?
17/10/19 3ème édition Soirée du Test Logiciel Sophia #STLS2019 7
10. The pessimist realistic
“Production code is written in a hurry and is most often tested by users
because there wasn't time for QA and you were under pressure to
release.
Who gets time to write test code?”
11. Different “raison d’etre”
“Production code is is written to be robust (ideally, but not always the
case).”
“Test code is written just as a poc (proof of concept).”
“The difference is in purposes. Production code is for fulfilling the
contract. Test code is for verify this contract.”
17/10/19 3ème édition Soirée du Test Logiciel Sophia #STLS2019 11
12. Some more technical differences
“Production code is DRY vs test code is WET.”
“Production code is generic vs test code is specific”
“Production code has to be fast enough, test code must be super fast.”
13. A tool for write production code
and documentation
“Production code is only what is necessary to get the test code to pass.
And then refactored to make it clean.”
“Test code is easily readable and rather blunt. When someone wants to
know how the system works, you should be able to point them to the
tests that prove the functionality.”
14. What is common in the previous
answers?
Tests are specific
They document something
Production behaviour
Contract
No mention of poor architecture, poor coding practices. Except for the
WET part.
15. Why are those differences?
The rest makes sense:
Differences in purposes
Not in implementation details
Do we have a good reason to have worse quality?
18. Common problems with test code
Show its value
Dependency injection
What to test? (public vs private; GUI)
Target code coverage? Does it matter?
TDD or not, test first or not?
Unit test vs integration test?
Test legacy code
Mocking
19. Common problems with test code
Missing assertions
Multiple assertions
Tests invoking other tests
Logic inside test code
Lack of coverage
Slowness (DB, I/O access)
Dependency between tests
Coupling, fragile test code
20. Common problems with test code
We have to write them
External dependencies
Fragile test code
25. Cut the external dependencies
DB and IO operations make tests
Dependent
Non-deterministic
Error-prone
Slow
26. The banana, monkey, jungle
problem
“The problem with object-oriented languages is they’ve got all this
implicit environment that they carry around with them. You wanted a
banana but what you got was a gorilla holding the banana and the
entire jungle.”
27. Code example
Welcome to the jungle!
class Alarm {
public:
Alarm();
void check();
bool isAlarmOn();
protected:
Sensor m_sensor;
double m_lowPressureTreshold, m_highPressureTreshold;
bool m_alarmOn;
};
double Sensor::popNextPressurePsiValue() { return random();}
Bring your own device
class Alarm {
public:
Alarm(Sensor* sensor, double lowP, double highP);
void check();
bool isAlarmOn();
protected:
Sensor* m_sensor;
double m_lowPressureTreshold, m_highPressureTreshold;
bool m_alarmOn;
};
29. Do you have the Fragile Test
Problem?
Test code is highly coupled to production code
If code changes, tests are broken
Cannot refactor
30. We must keep refactoring
Refactoring is part of TDD
Adding tests after the fact will also require refactoring
31. What happens when you refactor?
class SUT:
def operation_a(): pass
def operation_b(): pass
class TestSUT(unittest.TestCase):
def test_operation_a(): pass
def test_operation_b(): pass
32. What happens when you refactor?
class SUT:
def operation_a(): pass
def sub_operation_a(): pass
def operation_b(): pass
def sub_operation_b(): pass
class TestSUT(unittest.TestCase):
def test_operation_a(): pass
def test_operation_b(): pass
def test_sub_operation_a():
pass
def test_sub_operation_b():
pass
33. What happens when you refactor?
class SUT:
def operation_a(): pass
def operation_b(): pass
def sub_operation_a(): pass
def sub_operation_b(): pas
def common_sub_operation():
pass
class TestSUT(unittest.TestCase):
def test_operation_a(): pass
def test_operation_b(): pass
def test_sub_operation_a(): pass
def test_sub_operation_b(): pass
def test_common_sub_operation():
pass
34. What happens when you refactor?
class SUT(CommonSUT):
def operation_a(): pass
def operation_b(): pass
def common_sub_operation():
pass
class CommonSUT:
def sub_operation(): pass
class TestSUT(unittest.TestCase):
def test_operation_a(): pass
def test_operation_b(): pass
def test_common_sub_operation():
pass
class TestCommonSUT(unittest.TestCase):
def test_sub_operation(): pass
35. Test contravariance
“The structure of your tests should not be a mirror of the structure of
your code. The fact that you have a class named X should not
automatically imply that you have a test class named XTest.” - Uncle
Bob
36. What else can you do?
Test behavior
Don’t mimic production code structure
Let test code evolve on its own
37. Don’t be a micromanager
Should we test private?
No, only the API
Should we test every single aspect?
Don’t test every bits of implementation
38. Conclusion
Unit testing is difficult, but learnable
Different aims, similar best practices from production code
Unit testing is not optional
Maintainable UTs go down to coupling
39. References
Code Simplicity - Max Kanat-Alexander
The Art of Unit Testing - Roy Osherove
Culture Code
https://blog.cleancoder.com/uncle-bob/2017/10/03/TestContravariance.html
https://hackernoon.com/how-to-deal-with-test-and-production-code-
c64acd9a062
https://painlessdesign.wordpress.com/2015/06/27/how-to-avoid-fragile-unit-
tests/
https://www.developer.com/tech/5-simple-tips-to-more-robust-unit-tests.html
https://enterprisecraftsmanship.com/posts/unit-testing-private-methods/
40. Treat your unit tests as production code
Sandor DARGO (Amadeus)
17/10/19 3ème édition Soirée du Test Logiciel Sophia #STLS2019 40