Ward Cunningham introduced this metaphor, explaining that "shipping code first time code is like going into debt", and immediately emphasizing that not repaying it very soon "counts as interest on that debt". The metaphor used here is very powerful, because we can immediately visualise the added interest to the initial debt. The same author remarks, more than 20 years later, that: "The irony of our industry is that code turns out to be 'harder' than hardware when it is thought to be finished and the developers dismissed."
Another famous technical author, Uncle Bob, makes a clear distinction between technical debt and poorly written codebases when he stated that "a mess is not technical debt". For the remaining of this article, we are going to refer to those projects which have been crafted appropriately.
There are a lot of ways technical debt is added to a project, and Martin Fowler takes the explanation further, by distinguishing between prudent and reckless debt.
On July 31st 2012, Knight Capital Group, the largest trader in US equities, went bankrupt in just 45 minutes, due to a series of events that led to executing 8-year old unused code that had not been removed from the codebase. It was called the Power Peg functionality.
Their core algorithm was called SMARS, an automated router which sent orders into the market. When coding an update for it, the team repurposed an old flag that was initially used to activate Power Peg. The code needed to be installed on 8 servers, and this happened as a manual process over a few days, but only 7 of the target machines received the update. When the market opened and the code started to run, the 7 machines that got the correct code were working fine, but the 8th one started to run the dead Power Peg code. This led to the market being flooded with out of the ordinary orders, about 4 million transactions that resulted in a loss of \$460 million dollars.
The cause is neither the lack of testing, nor the incorrect deployment, taken individually, but rather a chain of events that led to the Power Peg code being executed in a live trading environment with no off-switch, as the SEC filing shows. It is the accumulated technical debt and interest, all paid at once. As a medical doctor used to explain it, "post-accident attribution accident to a 'root cause' is fundamentally wrong [...] do not reflect a technical understanding of the nature of failure but rather the social, cultural need to blame specific, localized forces or events for outcomes.
It is important to understand the built-in mentality of each team member, as it sheds a particularly interesting light on how they approach the software construction process. We are concerned with this aspect because of Conway's Law stating that "any organization that designs a system will inevitably produce a design whose structure is a copy of the organization's communication structure".
Even if you do not have dedicated persons for each of these roles, and you might have one person wearing multiple hats, it is important to understand the focus of each of these roles, and to use them to your advantage.
We need to integrate testing in the development workflow, and not leave it until after the code is written. You will encounter resistance, as changing the status quo is never a trivial task. Even if the team embraces testing, their skill levels will be different, and harmonizing them into a common strategy will raise some hurdles.
What are the most common excuses software engineers employ when they do not want to include testing in their workflow?
"We have a dedicated QA team. It's their job to signal possible issues."
"Our client requested this functionality live by next Monday. We are already short of time for executing the implementation. There is no time for testing other than what our manual QA guy already does."
"My code works. I have a great track record. Why start now?"
"The customer will pay only if the software works. Programmers are expensive, and we cannot possibly sell this to the customer. They will pick another software shop to do the work."
"I am just making a small cosmetic change, nothing can go wrong."
Let us have a look at the most important types of tests that your system should undergo. Below we will only mention the most common types. Please refer to the extended classification for a more extensive coverage and description.
This is the most known type of testing, and it is usually performed even without the team being aware of it. Its purpose is to determine if the requirements are met, without digging further into individual features.
Release acceptance test (RAT), also known as smoke testing, checks that the build is stable enough to conduct further testing on it.
Functional acceptance simple test (FAST) should be executed on each development release, to check that the major features are accessible and working on a known (usually minimal) configuration of the underlying systems. It replaces the infamous "works on my machine" with a more relevant "works in QA".
Compliance acceptance testing verifies if the software complies with given regulations. For projects which need to follow certain regulations, this is extremely important and needs to be performed often.
Deployment acceptance test takes the release and installs it fully in a target environment, with a configuration that is close to production. Ideally, the configuration matches the production.
Task-oriented functional tests (TOFTs) address the happy flows of each feature and check that they are valid.
Forced-error tests (FOTs) will force the program into error conditions, and will verify whether they are handled appropriately or not.
Exploratory testing typically reveals problems which would otherwise not be found, because it does not follow a pre-established plan.
The other types of checks performed on a feature level are equally important, we will just enumerte them below: load testing, stress testing, performance testing, security testing, failover testing, scalability testing, regression testing, availability testing, configuration testing, usability testing, accessibility testing, documentation testing, install/uninstall testing.
Introduced in 2006 by Dan North, behaviour-driven development asks for a consistent vocabulary that both business and geeks would understand to be used when describing the behaviour through its acceptance criteria. Gherkin is the language commonly used to describe the behaviour, and its rules are pretty simple, allowing for a very accurate description of the desired behaviour. The Given-When-Then style is now far more popular than its creators thought it would be.
Writing tests that walk the system automatically through the scenarios checked by stakeholders ensures the specification is clear, and prevents regressions from being introduced.
Short answer: it is not. TDD is more fine-grained, and its discipline adds many benefits to your code. They address different levels of testing, and thus cannot compare with each other. What we mean is that we should take BDD for a drive first.
The way BDD appeared on the tech scene is rooted in Dan North's dissatisfaction with the way the TDD adoption process worked on its own. Therefore, North went one level up looked at the analysis process, and found a way to simplify it. This is why it gained so much popularity, and support in many languages.
We understand that changing the entire software delivery mechanism you employ today is a drastic change that cannot happen overnight. Therefore, we extracted several practices that you can start incorporating in your flow today, while immediately ripping the benefits.
Testing should not happen solely after the software has already been written. Teach testing concepts to your software developers, to have them think in a destructive way before they start coding.
For a given feature, one person can be either the one writing test scenarios, or code, but not both, as "a developer should never test their own code". This sounds a little like pair programming, but the pair works on different goals, with different mindsets.
Note: Even if someone becomes very skilled at doing the scenarios, make sure they get to write code in the next selection, to keep their coding skills sharp.
Input partitioning Divide the inputs into sets having the same behaviour, so that we write one test per set, and thus eliminate redundant tests that walk through the same path in the underlying codebase. Partition the set into: empty data, single data, multiple data.
Boundary testing Identify items at the boundaries of the input space, and write one test for each of them. Examples: zero; null; min/max values; empty set; empty string.
State transitions Analyse transitions between the application states, the events that trigger them, and the results of those transitions.
Treat code modules as independent components. Describe their behaviour using static inputs and outputs that can be fed to automated tests. If there is something not described in such a way, it means it was not in the requirements.
This speeds up the tests, since we do not need to setup the entire system, and revert its state after the test finished.
An added benefit is that the tests become modular, so that they depend on (and describe) the specification.
Introducing bugs in existing features is not desirable. BDD helps us prevent this by having tests in place. However, the tests need to run as smoothly as possible.
Make sure there is a way for the developer to run the entire test suite painlessly. The harder it is to execute it, the lower the probability it will happen in real life.
Conway, Melvin E. (1968). How Do Committees Invent? URL: http : / / www . melconway . com / research/committees.html.
Myers, Glenford J. (1976). Software Reliability: Principles and Practices. Wiley. ISBN: 978-0471627654.
Gleick, James (1996). A Bug and A Crash. URL: https://around.com/ariane.html.
Cook, Richard I. (1998). How Complex Systems Fail. URL: http://web.mit.edu/2.75/resources/ random/How%20Complex%20Systems%20Fail.pdf.
Nguyen, Hung Q., Bob Johnson, and Michael Hackett (2003). Testing Applications on the Web: Test Planning for Mobile and Internet-Based Systems. Wiley Publishing Inc. ISBN: 0-471-20100-6.
Andersson, Eve, Philip Greenspun, and Andrew Grumet (2006). Software Engineering for Internet Applications. MIT Press. ISBN: 0262511916. URL: http://philip.greenspun.com/seia/.
North, Dan (2006). Introducing BDD. URL: https://dannorth.net/introducing-bdd/.
Fowler, Martin (2009). Technical Debt Quadrant. URL: http : / / martinfowler . com / bliki / TechnicalDebtQuadrant.html.
Martin, Robert Cecil (2009). A Mess is not a Technical Debt. URL: https://sites.google.com/ site/unclebobconsultingllc/a-mess-is-not-a-technical-debt.
Group, PA Consulting (2011). Major UK water utility - Architecture and design assurance. URL:
Miller, Robert (2011). Lecture notes in Elements of Software Construction. URL: http://ocw.mit. edu/courses/electrical- engineering- and- computer- science/6- 005- elements- of- software-construction-fall-2011/.
Fowler, Martin (2013). Given Wehn Then. URL: http://martinfowler.com/bliki/GivenWhenThen. html.
Securities and Exchange Commission (2013). Administrative Proceeding File No. 3-15570. URL:
Seven, Doug (2014). Knightmare, A DevOps Cautionary Tale. URL: https : / / dougseven . com / 2014/04/17/knightmare-a-devops-cautionary-tale/.
Berstein, David Scott (2015). Beyond Legacy Code: Nine Practices to Extend the Life (and Value) of Your Software. Pragmatic Bookshelf. ISBN: 978-1-68050-079-0. URL: http:// pragprog. com/ book/dblegacy.
Cucumber implementations. URL: https://cucumber.io/docs\#cucumber-implementations.
Gherkin Reference. URL: https://cucumber.io/docs/reference#gherkin.
by Vlad But
by Delia Mircea
by Ovidiu Mățan
by Vlad Ciurca
by Ovidiu Mățan