The Italian Social Security Office INPS suffered a tremendous debacle few days ago. The INPS website collapsed in time of need after a modest and very predictable traffic peak. Worst, the publicly exposed browser-side software code clearly showed approximation and poor Engineering standards . The fact brought to public attention the issue of Quality in software development and in organizational processes.
Quality is a central theme in Agile, with its enphasis on maximising value and sustainable development process. In this post I am focusing on the specific aspect of quality in Agile Softweare Development Process, and particularly on Unit Test adoption, also showing practical usage examples .
Agile and Testing Pyramid
Agile development left-swithces testing activities, with great emphasis on developer side test automation, test coverages criteria in the Definition of Done as well as in acceptance criteria directly incorporating te automated tests. The rationale for that is about designing better code, and providing a safety net against Regression Problems. The approach in its purest form is exemplified by the TDD-Test Driven Development that creates tests before the coding to be tested. We can say that Agile moves a big part of the testing effort on the developer side promoting automation and unit testing.
That’s not trivial. It’s still quite common to meet senior developers who never used automation test frameworks and with no clue on how to incorporate unit testing in their development cycle.
MIke Cohn explained the Testing Pyramid concept in his book Succeding with Agile. The pyramid helps us to understand the volume and automation of tests in the different layers of the pyramid. Maximum automation and coverage at the unit test level, minimum volume but maximum integration and manual effort at the end-to-end level
Testable and untestable code
Writing unit tests is not difficult, but you have to write your code in a testable way. What does that mean? Let’s say we have a function, which return the appropriate greetings )good morning, good afternoon, etc) based on the hour of the day . Here’s the code in Python
""" Function welcomeWith() returns appropriate greetings according to the hour of the day """ import datetime def welcomeWith_funct(): now = datetime.datetime.now() hour = now.hour if 5 <= hour <= 13: welcomeWith = "Good morning!" elif 14 <= hour <= 17: welcomeWith = "Good afternoon!" elif 18 <= hour <= 22: welcomeWith = "Good evening!" else: welcomeWith = "Good night!" return welcomeWith
What’s the problem with the above function? The function is very difficult to be tested, The method datetimenow() is an ever changing input , every moment you call it, it returns different results. More, the test returns the hour of the machine that runs the function, there is no guarantee that’s the same machine/hour you are running the test. Man, that function is really hard to test. How can I write the function in a testable way, then?. Here’s the solution:
def testable_welcomeWith_fuct(hour_of_day): hour = hour_of_day if 5 <= hour <= 13: welcomeWith = "Good morning!" elif 14 <= hour <= 17: welcomeWith = "Good afternoon!" elif 18 <= hour <= 22: welcomeWith = "Good evening!" else: welcomeWith = "Good night!" print(welcomeWith, "testable function") return welcomeWith
You call now the same function with a parameter ( hour_of_day) so you can have always the same output whit the same input, this way you can test the function!
That’s only a specific case, but there are many anti-patterns causing the code not to be testable. Lucky us, those patterns are already known and documented. Google, for instance, published its guide to testable code, a great starting point for agile teams. Clean Code, by Robert C. Martin is a great book presenting the same concept along with many others. TDD-Test Driven Development, is an agile practice which makes you designing your tests even before starting writing your code. This way, your code becomes naturally testable.
Unit Test, an example
Unit Testing is mad in three phases
- ARRANGE. In this phase, you set up the SUS – System Under Stress
- CREATE. Applying a stimulus to the SUS
- ASSERT. Tracking the results from stimulus->SUS-> and comparing them with pre-asserted results.
Test is passed when predetermined results are coherent with results coming from the stimulus. Test fails when results do not match.
Let’s see a practical example of Unit Test. I have written it in Python, but almost any language in the world has its own Unit Test library, nowadays. Let’s say we want to test a very basic application for financial analysis. The application calculates Margins, Taxes, ROI from a specific organization. Here’s the code:
def margin(r, c=100): return r - c def roi(m, i): return m / i def taxes(m, p): return m * p def net_profit(m, t): return m - t
We are using pytest as the library for unit testing, Here’s the code for the tests, it includes two mistakes. Can you spot them? If you can’t lucky us, the unit tests themselves will show us the problems
(venv) PS C:\Users\documents\0-Python-Unit-Test-with-PY> pytest ================================================= test session starts ================================================= platform win32 -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 rootdir: C:\Users\documents\0-Python-Unit-Test-with-PY collected 4 items test_roi.py .FF. [100%] ====================================================== FAILURES ======================================================= ______________________________________________________ test_roi _______________________________________________________ def test_roi(): > assert round(roi.roi(100,700)) == 0.14 E assert 0 == 0.14 E + where 0 = round(0.14285714285714285) E + where 0.14285714285714285 = <function roi at 0x03AFD580>(100, 700) E + where <function roi at 0x03AFD580> = roi.roi test_roi.py:10: AssertionError _____________________________________________________ test_taxes ______________________________________________________ def test_taxes(): > assert round(roi.taxes(100, 42)) == 42 E assert 4200 == 42 E + where 4200 = round(4200) E + where 4200 = <function taxes at 0x03AFD5C8>(100, 42) E + where <function taxes at 0x03AFD5C8> = roi.taxes test_roi.py:14: AssertionError =============================================== short test summary info =============================================== FAILED test_roi.py::test_roi - assert 0 == 0.14 FAILED test_roi.py::test_taxes - assert 4200 == 42 ============================================= 2 failed, 2 passed in 0.13s ============================================= (venv) PS C:\Users\documents\0-Python-Unit-Test-with-PY>
The test is showing us two trivial errors:
- Round() function used is missing the correct number of decimals to be used (default=0), and
- Taxes() needs as input a percentage value (42) not the decimal absolute value (0.42) . Let’s solve the issue, and see the result.
(venv) PS C:\Users\documents\0-Python-Unit-Test-with-PY> pytest test_roi.py -v ================================================= test session starts ================================================= platform win32 -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- c:\users\documents\0-python-unit-test-with-py\venv\scripts\python.exe cachedir: .pytest_cache rootdir: C:\Users\0-Python-Unit-Test-with-PY collected 4 items test_roi.py::test_margin PASSED [ 25%] test_roi.py::test_roi PASSED [ 50%] test_roi.py::test_taxes PASSED [ 75%] test_roi.py::test_net_profit PASSED [100%] ================================================== 4 passed in 0.02s ================================================== (venv) PS C:\Users\documents\0-Python-Unit-Test-with-PY>
Now all the tests are passed 🙂 Unit Test can be started at any build, any deploy, any pipeline activation. They are safety networks against regression problems and silent guardians of the quality in your system.
THat’s too bad that in 2020 many teams are still NOT USING UNIT TESTS, thus causing weaknesses and problems in their organizations.
I hope these post will inspire you and your team in starting using and mastering Unit Tests 🙂
- Unit Testing on wikipedia
- Unit Testing in Agile
- TDD-Test Driven Development on wikipedia
- Extreme Programming, by Kent Beck
- Guide to Writing Testable Code
- Clean Code, by “Uncle Bob” Robert C. Martin
- pytest documentation
- Junit, the Java Unit Testing framework
- A comprehensive list of Unit Tes Frameworks available
Marcello Del Bono è Product Owner di due team Agile e consulente per programmi di Trasformazione Agile. vanta esperienza pluriennale come ScrumMaster, Agile Coach e Project Manager tradizionale in progetti IT, System Integration, Marketing Digitale, e-commerce nei settori Finance/Banking, Media, Moda, Lifestyle