3 TDD Examples
3.1 Reasons to study
The first time I heard about TDD was at university in the course “Software Engineering”. We had to use it inside of a project immediately. At first, I did not understand the idea behind it, but I got it by using it inside of the project. Only because of that, I got how useful this process can be.
‒ Max from Mannheim, Germany 1
Before I start a project in a language I have not used for a while, I look for current recommendations from the community.
For example, once I made a tiny project in Haskell.
At that time, for managing the dependencies, builds, and publishing, one tool seemed to be the default choice. It was called “Stack”.
Four years passed, and at a DevOps conference I talked to a random man, who turned out to have considerable experience with Haskell.
It was Rok Garbas, working on reproducible dev and production environments with Flox.
He says: Stack used to be the default, but these days, Cabal (another build and package management tool for that ecosystem) has more features and is so reliable there is no real reason to use Stack anymore.
Like in this example, the recommended way changes every few years.
In Python world, it was: PIP to Pipenv, to Poetry, to anything-but-Poetry.
The last trend due to the fact that Poetry at some point had reoccurring problems that made it unreasonable to use it for anything more serious than experimentation.
That trend was justified for a number of years, and faded only
recently with stability improvements to Poetry, and with added support for
installing projects from pyproject.toml
by PIP itself.
PIP support meant: if Poetry errors prevented the package from installing, it was now reasonably simple to install that package using PIP. If you used a VirtualEnv as the install target, the result is similar to installing that package with Poetry, only without the automation of these two steps.
All this dynamic nature of developer tooling means every time you need to test several approaches before you decide which of the most convenient tools available is stable enough that you can trust it with the hours, days and weeks of your life.
Once you decide on the tool, you need to make one more decision: which test tooling will you use?
I have some experience in both of these areas, meaning - I have made painful mistakes in each of them.
Hopefully with this book, you will have some reasonable defaults to start with for both project management and test tooling.
Someone will be worried: If I pick the tool you recommend and I find it is not suited to my needs, will I not be in much more trouble than I would be had I given it more research instead of relying on someone’s recommendation?
My answer is: if you conduct some research, then choose the tool, you are still likely to find something seemingly better, earlier than you would wish.
With build and dependency management, it is much more important to keep track of your dependencies at all than to pick the “right” tool from the start.
Once you find a better tool, it will be much easier to migrate your current configuration to it than it would be to create such a configuration from scratch when you “finally found the perfect tool”.
It is very similar with tests.
Once you find a tool you like better than the recommended one, you are always free to migrate your existing checks to it.
The most difficult part of the work - figuring out, what to test and how to test it - will already have been done.
My experience is still fresh from migrating tests from UnitTest style to PyTest style. It proved to be so simple it coud even be automated for the most part. Had I decided to wait with writing the tests until PyTest became more standard, I would now have a lot of tests to write from scratch.
And a lot of untested (and un-testable) code.
I invite you to start with one of the presented examples, and feel free to move on when the time comes.
3.2 Codility: How TDD can help you get a job
Codility is an online platform for running coding tests.
As a developer looking for work, I met Codility when trying to join a global network of vetted professionals.
One of the interview stages was a Codility test.
The network said it only accepted the “top 3%” of all their candidates.
To recruiters, Codility offers a streamlined screening process optimized to minimize your interaction with the candidates, keeping the process rigorous and result-oriented.
An example of a Codility question is:
Find longest sequence of zeros in binary representation of an integer.
The platform lets you code inside their specialized environment.
Codility challenges require you to design and implement an algorithm to solve a specific problem.
You can use a range of popular languages, including Java, Python, and JavaScript.
Of all the languages I have tried (C++, Java, JavaScript, Python), I observed the most efficiency when I used Python.
Unless your test supervisor (eg. prospective employer) requires you pick a specific language, I would advise you to pick Python too if you know it on a comfortable level.
Codility exercises are not so much about knowing a language, as about being able to solve problems in code, in the language of your choice. At least as long as you choose from their list of available languages.
In the challenges, if you pick C++, you don’t get the “speed bonus” you would normally expect when implementing things in C++.
However, you get all the complexity C++ has compared to Python.
This way it makes sense to pick the least complicated language you know.
It would be even better if it was also the most flexible language you know.
Python offers you a wealth of packages and available answers on QA sites.
In Python, you can do TDD conveniently with DocTest, which can be used both in Codility, and locally.
To make use of TDD in Codility, import the following function from the
doctest
module:
from doctest import run_docstring_examples
With this function, you will be able to specify an example in the form of a docstring, and then run that example to check if its output is what you specified.
from doctest import run_docstring_examples
def get_square(x):
"""
>>> get_square(2)
4
"""
pass
def solution(x):
globals())
run_docstring_examples(get_square, return 0
The output from every run of this code in Codility is likely an error from
outside of the run_docstring_examples
function.
Why is that? Checking the output, we see that it is different from what Codility expected for the example input (bundled with the task).
To make sure this error doesn’t clutter the output, we need to return the right
value from the solution
function.
3.4 LaTeX: Testing your package
\ASSERT
and\ASSERTSTR
Asserts if the full expansion of the two required arguments are the same: the\ASSERT
function is token-based, the\ASSERTSTR
works on a string basis.
‒ l3build
It may often be difficult to use TDD with l3build. These tests are designed with regression testing in mind.
An alternative would be a simple assert.sty
library like this one:
assert.sty.
3.5 How NOT to do TDD
Once my task became to implement a parser for a standard document format, I took the specification and started implementing that.
I did it instead of talking to the users about what they would want to have implemented.
I implemented a bunch of features.
The users were not happy with them. One feature would have sufficed. The one that was not there.
3.6 The missing ingredient of TDD
Don’t be afraid to make mistakes.
‒ “What can we learn about iterative project planning from space travel?”, Krzysztof Borcz 2
I initially got the impression that TDD is about running tests to avoid running the program as a whole.
This got me in trouble.
I wrote all the tests. I would make sure the tests passed, even in most sophisticated corner cases.
Then I connected the code to the main routine of the program, and ran the program.
The program was too slow to even load in a few minutes. And then, anyway, it crashed.
Debugging it was a nightmare. The same nightmare I hoped I could avoid by using TDD.
Finally, I watched “How Not to Land an Orbital Rocket Booster” 3.
Elon Musk, when he was designing SpaceX space ships, he would send a rocket into the air every month or few months.
He did not trust a near-perfect unit test suite to prove the rocket was going to fly. He would actually set the rocket up to start and return, and let it fly. Even if it was going to finish in pieces.
Multiple times, launches and landings failed, but every time the SpaceX team would learn something new, and come back the next time, better prepared.
I recognised this was the answer to my dilemma: write unit tests, follow the TDD cycle of red-green-refactor, and once in a while run the program end-to-end.
Doing that will make sure you detect your blind spots in time to fix them. Perhaps using TDD on the way. But don’t avoid running the program as a whole until the very end.