Introducing BDD & TDD

reading-time:

5m30

Is Behavior & Test Driven Development an essential aspect of professional software engineering? Leaders such as #UncleBob compare it to ‘double entry bookkeeping’: a crucial discipline. He claims that when our industry doesn’t professionalize quickly, we have the risk that B&TDD will be enforced by law (too).
So, it can be wise to learn to practice it quickly.

It is not easy to start when writing (long-lasting) embedded systems. We have other demands, codebases with a lot of history. Still, we can (& should).
And we have an advantage, our Typically engineers are clever: When they understand the objectives, they will find a solution; that is their job!

Updated on 2020/07/15

This old article was never really published. As it fits my new MESS blogs, I reworked and posted it again. This is part-I; I expect other parts coming weeks.

Ideally, each team should understand the concept of this development process first and then exercise it before practicing it in a project. However, IMHO, it is possible to learn it on the job. As long as one is carefully trained & couched while practicing.
A pitfall is to focus on the tools and learn new tricks. That hardly ever makes a process lean.

Instructions too often focus on using the tools. And almost automatically, the goal becomes to blend those new tools into the existing Way-of-Work (WoW). Eventually, however, somebody will get disappointed. There will be no quality improvement, no speed-up, or any effect.

Tip

Without set goals, one shouldn’t complain when expectations are not met!

So, what are the goals one can aim for? That can be formulated as simply: a more Lean & Agile approach.
Or even bolder:

Design a (Modern) Embedded Software System that fits the needs of the end-users better, with no flaws, in less time and with less effort (cost) than traditionally.

What is What (Introduction)

There are a lot of opinions on BDD and TDD, which can be confusing. As in many modern improvements, words often become hyped to justify any change. That is not my goal. So let’s start with a summary backed by the definitions on Wikipedia.

TDD (Test-driven development)

TDD is a process; where the tests are described first and the code second. One keeps executing all tests until the code works. This encourages good habits such as refactoring also.
Change code is constantly tested, so the risk of inserting a bug is minimal.

TDD is typically practiced at the unit level (one file, class, or function), where the programmer writes test and production code in a short loop (a minute)
The are many variants, like STDD (system-level TDD), ATDD (Acceptance TDD), and BDD; the latter is well-known and popular.

TDD also provides an ”exit” strategy (see The Exit Strategy of TTD [ToDo]).

BDD (Behavior-driven development)

BDD is a variant of TDD focusing on the system (or acceptance) level. Again, tests are written first and executed constantly; when all tests are OK, the product development is done.

Here the testing focuses on the (top-level) requirements, the system, and/or business features. Typically, they are designed by test professionals, system architects, or business experts. They are less technical compared with TDD tests. And, to be practical, those tests are written in a dedicated tool; using a high-level “almost English” language.

As the size of the change is bigger – like a feature or user story– the cycle is (also) longer. Typically a few days.

Like with TTD, BDD tests are executed frequently.
Some prefer to “enable” new tests only when the feature is coded – this prevents a failing test (as the production code isn’t done). IMHO, one should avoid this. One should run the tests but in a lower urgency branch. And promote both to a higher level when integrating (see an upcoming blog on this).

All levels

One can (should) practice this process for all levels in the V.

Each classical ’test & integration’ step can be split into a test preparation and an execution activity. The preparation phase becomes the test-design activity, executed early and resulting in an ATS (Automated Test Script).
That ATS is executed frequently (at least nightly) as soon as it is available.

Executing all tests at all levels for all units and modules and for every story and feature verifies everything. This covers pure integration errors. This covers pure integration errors but is also a safety net when mistakes are not found at a lower level.

Remember: those ATSes run fully automatically. So, the cost of all those executions add-up to almost nothing.

Why not just write and run the test?

TTD and Unit Tests are related but not the same! When practicing TDD, the focus should be on preventing flaws instead of finding them.

TDD is a process that dictates when to write a test (first); when to write production code (second); and when to execute the tests (constantly and automatically).
The same applies to BDD, even though the frequency is slower.

Testability

Everybody knows some code that is hard to test. I have seen functions without input or output – acting purely on global variables. We know globals are bad! And it is also untestable.
We should avoid that.

By writing tests first, we enforce an implicit requirement: code should be testable. Besides:

It’s hard to write untestable code when you write the test first!

Legacy code

Using TDD with legacy code –code that does not have a lot of (unit) tests and that was never designed for testability– is more arduous than starting TDD in a green field. Still, there are options: at least make all new code testable and use the concepts of TDD where possible.
In an upcoming blog, I will give some tricks on that.

Typically, people advise starting with TDD before applying for BDD. For legacy, the opposite is often true. As BDD depends less on the coding style, legacy isn’t a game-changer.
A reasonable set of acceptance tests, preferably ones that can be automated, is already a start-point: just run those tests (automatically) every few days or every pull request or so.

Next, design more ATSes. Add a set for all new features, user-stories, etc. And(!) Run them
Again, the idea is: start soon. Don’t wait on the code. You don’t even have to wait for the final story. Start as soon as a draft is available.

In a future blog, I will dive into the details. For now: remember: the test is needed to prevent errors, not to find them: My motto is:

When a tester can’t design the test, the coder can’t program it!

So, make sure all omissions are removed before the SW engineers write the code. Else, they write the wrong code!

Comments

comments powered by Disqus