Test-Driven Development with TypeScript...
April 17, 2022
TDD stands for Test-Driven Development. It means that you write tests before the code. At first sight that doesn’t sound very practical and a waste of time, but if you dig deeper, you will find that there is much more to this approach than meets the eye.
There are a lot of benefits I will list for you below:
- Short feedback cycle. When something is not right, you will notice immediately. That’s because you test your code directly after you have written it.
- Tests are small and do one thing, so the functions you write automatically do the same. That means you force yourself into one of the SOLID principles: The Single Responsibility Principle, which will make your code a lot more readable.
- It improves code quality. As you go, you build up a significant number of automated tests. Some other factors define code quality as well; I will touch upon that later. If you write your code based on tests, it must be easy to understand what to code, which means that difficulties have to be apparent to read. Therefore, when working in a development team, it’s easy to switch work between developers because they can start coding when they understand the test.
- Remember those times you did a big refactor? Nothing seemed to work anymore? Those times belong to the past with TDD, every line of code you change is tested, and therefore tests will fail early, letting you know what goes wrong. It makes refactoring painless.
The laws of TDD
Robert C. Martin(“Uncle Bob”), you might know him from the book Clean Code, defined three laws for TDD. Those laws can make or break the TDD process.
- You are not allowed to write any production code unless it makes a failing unit test pass. Meaning there is no code without a test. You write the test first to specify the behavior.
- You are not allowed to write any more of a unit test than sufficient to fail(Compilation failures are failures) Make sure your tests fail on the most minor step possible and make sure they only do one thing.
3 You are not allowed to write any more production code than necessary to pass the one failing unit test. Meaning writing your code as efficiently as possible, working incrementally, and checking after every line of code you write if you passed the test.
The workflow cycle: Red, Refactor, Green
The names of the steps in this workflow are inspired by the status the tests can have: Red: Means failing tests, also often marked red in your terminal Refactor: This is the step in the process where you write your code. Depending on how far you are in the process, it will be coding or refactoring. The first goal should always be to make the test pass. The second goal is to optimize your code while keeping the tests green. Green: Means the test has passed. Always check if there is room for improvement.
As you can see in the image below, this is a constant cycle until everything is
TDD - Example Practical steps IDE
In this article, we will also create a movie component, the TDD way. Demonstrating to you this is also possible with visual components. Let’s start with the setup and workflow first. We discussed the workflow in the last part. So let’s put that into practice. If we look at that process, the only three things we need are: The tests The code The test results
Even though this is a visual component, we are not interested in the browser. This takes some getting used to. But think about it, wherein the steps above is the browser required? It will only take you out of the flow. The setup below is the one I recommend within your IDE. I’m using VSCode, but this view can be achieved in any IDE with a terminal integrated.
We have an Angular app here using karma and jasmine that come with the generated app. On the left, you see the component and the HTML. On the right, you see the test. On the bottom, you see the failing test. The test suite runs with the –watch flag, meaning it’s watching the changes in my files. As soon as it changes, it will re-run all the tests.
How this would work in practice:
- Red: Write the test on the left and check the terminal containing the failing test
- Refactor: Write your code on the left while looking at the tests on the right
- Green: After saving your changes, the test suite runs again, showing you a green result (If you fixed it, that is)
- Rinse and repeat
The hardest thing about TDD to get used to is not the work itself but the idea that you have to work the other way around. It feels like you are starting from the end of the process and are working back to the beginning. You have to think differently about writing your code, and that’s where the magic happens. Because you are writing tests first, you start thinking in structures. Think of it as a blueprint for the code you are about to write.
TDD in Agile SCRUM
Remember those times when you are working on a user story and find out along the way that half of the requirements are missing? That belongs to the past. Let me tell you why: You write tests in small pieces. When all our tests are green, the feature is done. When something is missing, you will notice it when you write the test, so the implementation can’t happen with missing requirements. You are forced to be nitpicky about your requirements. A great way to go about this is to make sure the requirements you got are in the tests, and you add the missing requirements you found while writing the tests to the user story and discuss it with your product owner. When both of you agree, you can start with the implementation.
Most common pitfalls of TDD
- Not watching your test fail: This is an easy step that is often overlooked and can cause errors in the end. You assume that the test you wrote failed. If the test didn’t fail initially, the implementation is not guaranteed to work.
- Forget the refactoring step: The first focus is to let the tests pass, resulting in code that is not that clean. Therefore it’s crucial to refactor the first implementation of the green tests. You don’t have to be afraid of breaking anything if you work according to the TDD workflow cycle. You change small pieces of code and will see right away if anything fails using failing tests.
- Refactor another piece of code while working on a test: It is easy to get distracted while fixing a test. You see some code that needs refactoring. When working with TDD, the priority should be to make the tests green and focus on refactoring. This way, you are forcing yourself to work iteratively.
- Use bad test names: Developers spend more time reading code than writing code. Every minute spent on in clarity of vaguely written code is a minute wasted. Therefore all code should be readable, meaning it should do what it says. Make sure the tests are written clearly so that a developer knows what needs to be done by looking at the test.
- Do not start from the most straightforward test: When starting at the simplest tests, you build up confidence. It feels good when you make several tests turn green. With all that confidence, you are ready for the more complex tests. Working the other way is demotivating and, therefore, impacts code quality.
- Run only the current failing tests: When the codebase is getting bigger, the amount of tests grows with it. Meaning it will take up more time to run all tests. That makes it tempting just the run the test you are working on. Making that pass might seem like a good idea, but since you are isolating it, you have no clue the impact on the rest of the code.
- Writing a complex test: A test case should be written for a tiny isolated piece of code, making it easy to pass the test. Writing complex tests will result in a hard time understanding it and more lines of complex code to make the test pass. 8.They are not refactoring the test code: Like code, tests will not be written optimally at the first attempt. So make sure that you also refactor your tests, like refactoring your code. (Which may also result in refactoring your code)
- Do not implement the simplest thing that makes the test pass: One guideline of TDD is that the most simplistic code needs to be written to make the test pass. The refactor step will come later. From there, you can make it more and more complex. Starting with more complex code to pass the test may result in untested code and directly impact the overall code quality.
What do TDD and TypeScript have in common?
At first glance, TDD and TypeScript may have nothing to do with each other, but when you look at the process side of things, you will realize they have some fundamental things in common.
- Direct feedback loop: You are guilty until proven innocent The TDD workflow and TypeScript have an immediate feedback loop in their workflow. You write some code, and almost immediately see feedback in the form of failing tests or a red line under code that is not in line with the predefined types.
They improve the quality of your code
- Reliability Pre-defined terms validate the code you write in TypeScript and TDD. You can see it as a quality/requirements contract you agree upon before writing the code.
- Confidence You need to adhere to the quality and requirements contract to make the code work. When the code works, it meets the requirements, is tested, and holds a high-quality standard.
In this article we covered the advantages of test driven development over regular coding principles and discovered the paradigm and mindset switch required to fully understand this way of thinking. Also we discoverd the most common pitfalls developers make when starting with TDD. As a bonus we compared TypeScript to TDD and discussed the similarities between the two. Are you ready to start with test driven development?
I'm fascinated by the power of a strong mindset. I combine this with being a web developer, which keeps me motivated. But how, you may ask? That's what I share on this website. For more information about me personally, check out the about me page: About Me