Introduction to Unit Testing and Test Driven Development (TDD)
IT Tips & Insights: A Softensity software engineer covers the benefits of unit testing and test driven development.
By Charles Lanada, Software Engineer
“The only way to go fast, is to go well.”
― Robert C. Martin, Clean Architecture
Why do we test our code?
As professional software developers, we are always involved with some form of testing. Here are some of the popular reasons why we test our code:
- Documentation purposes
- Confidence on your code
- Safety net for refactoring
Ultimately, We test because we want to make sure that the code, module or feature that we are working on works.
There are several interpretations for how tests should be structured, but my go-to model is the testing pyramid.
Unit tests are typically automated tests written and run by software developers to ensure that a section of an application (known as the "unit") meets its design and behaves as intended.
“Unit tests are very low level and close to the source of an application. They consist of testing individual methods and functions of the classes, components, or modules used by your software. Unit tests are generally quite cheap to automate and can run very quickly by a continuous integration server.”
- Sten Pittet, ATLASSIAN
What do we actually test?
- Parameter boundaries
- Expected results
- Expected behavior (can be achieved by setting up mocks)
- Interactions between intra system dependencies
When we focus our test on the behavior instead of the structure, we end up with a flexible code that can be easily extended and changed.
How does Unit Testing deal with dependencies?
In Unit Testing, there are cases where it’s not just about the input and output, but a unit can also have intra system dependencies or interaction with other units. (e.g queries and commands from the database, a certain trigger that needs to be executed from a separate service, etc.)
In that case, we use test doubles as substitutes for these dependencies so that the developer can ensure and assert that the interaction between the system under test and the dependencies are emulated.
- Non production ready in tests
- Fake dependencies in tests
- Such dependency looks and behaves like its release intended counterpart but is actually a simplified version that reduces complexity and facilitates testing.
- The name itself comes from the notion of a stunt double in movies.
5 types of test doubles: (dummy, spy, stub, mock, fake)
Can be grouped into 2 major categories:
- mock (mock, spy) - emulates and examine outcoming interactions
- stub (stub, dummy, fake) - emulate incoming interactions
Popular school of thought / approach for Unit Testing:
- Classical (Boston) - advocates for the replacement of only shared (mutable out-of-process) dependencies.
- Mockist (London) - advocates for replacing all mutable dependencies (collaborators) with mocks.
When does Test Driven Development (TDD) come into play?
TDD is actually not exclusive to unit tests; it can be applied to any type of automated tests. It's a programming technique that allows the developer to think and write their code in such a way that the developer tackles one component behavior at a time.
It achieves this by writing a failing test and writing enough code to make it pass, then deciding if the passing code can be refactored into a better form. Then just repeat the cycle of failing, passing and refactor or in short: red, green, refactor.
The red, green, refactor approach helps developers compartmentalize their focus into three phases:
- Red — think about what you want to develop
- Green — think about how to make your tests pass
- Refactor — think about how to improve your existing implementation
When we do manual tests we make sure that the code we wrote works — it’s a feedback from the software to the developer. However, it costs a lot in terms of the time we spend designing, writing code and environment setups just to have that piece of feedback.
TDD shortens and speeds up the feedback loop so programmers can quickly spot defects and flaws of the design early. Since it’s automated and fast, you can easily run tests that also serves as a safety net if new behaviors are added to the code. Read about more benefits and disadvantages of TDD here.
Unit Testing and TDD do not guarantee bug-free and quality code. In fact, most software systems are not bug free. Code is a liability: the more you add it in your software, the more it is prone to bugs and maintenance.
On the other hand, crafting software with just enough code for it to work makes it easy to change, verifiable/tested, and provides some kind of value to the customer, which makes the code an asset.
So let’s be mindful of the code we are writing, especially if it’s production code. Unit testing and TDD are a great investment for teams that are building high quality software.
Testing in general, and TDD, are a skill. It is something learned through experience and practice. There might be a steep learning curve, but it is a sound investment for developers and teams in the long run.
Hey, I'm Charles Lanada. I live in Bohol, Philippines. I've been practicing unit testing and test driven development for around 3 years now and I'm really loving it. It has changed the way I work as a professional software engineer, and I continue to learn and teach how it benefits developers and product development teams.
Aside from testing and TDD, I'm also proficient in Microsoft technology stack (C#, .Net, Azure, SQL) and front-end development stack (Angular, JS/TS, HTML, CSS). I’m interested in software craftsmanship, pair/mob programming and product development principles.
Thank you for taking some time to read this article. I’m happy to engage with any comments or questions.