To share knowledge and discuss various coding methods and practices within the development teams at IceMobile we participate in hands on workshops. Last week the frontend team of Stamps participated in a workshop about Test-driven development (TDD).
A typical way of programming without TDD could be to write a piece of code that has a specific task, and after that write some tests to check if the code actually does what it is supposed to be doing. Writing these tests after development is mostly done, is almost never a task that a developer enjoys working on. Also, it is easy to forget to test specific scenarios or to simply not write any tests for it because of time constraints or other reasons.
With Test-driven development a developer would first write a single test, that tests a task or requirement for a piece of software. And only after the test is created, the code is written that actually performs the task that the test requires the software to do. Every test should test a single task that the code is supposed to do. This way the code is separated into small pieces of functionality that can be individually tested in an isolated scenario.
The developer writing the unit tests should be aware of the inner workings of a method/class but it is not able to access private functions or variables in the class. Though it should test all the code paths and that is done by manipulating the data and parameters passed to the method/class.
A development cycle for TDD works like this:
- Write a new test that tests a single feature of a piece of code
- Run the tests, which should fail because the new code hasn’t been written yet.
- Write the code that actually does (but not more than) the desired task to make the test succeed.
- Run tests again and fix any test that might fail at this point until all tests pass.
- Now that all the tests have passed, clean up or refactor your new code making sure the tests keep passing.
Developing by the principles of Test-driven development might require a change in mindset for developers at first. One might feel that valuable development time is lost having to write all those tests without having even started writing the actual planned functionality. Project managers might have to be convinced of the extra investment that has to be done to spend time on writing the tests. Especially when working with tight time constraints it requires discipline to stick to the principles of TDD and not be tempted to skip the writing of the tests and to quickly add the modifications to the code.
Test-driven development has a number of advantages over some other programming concepts
- Code quality is the starting point when new software is being developed. Because writing the test is the starting point for newly written code, the developer receives early feedback. When something is wrong with the code, or when a change to the code has broken down other parts of the software. This gives the developer more confidence that changes won’t break the software, or quickly fix the code in case the software is broken.
- A test focusses on only one piece of functionality in the code. Therefore, the developer is also only focused on that piece of functionality or requirement. One test could also represent a single functional requirement, by making sure all the functional requirements are added as tests ensures that all the required functionality is created.
- Because unit tests are only able to access public methods and properties of the code a developer has to think about which parts of the code have to be made public. And which code is only to support the inner workings of the class and therefore can be kept private. Also, it forces the developer to think about any dependencies a function has to other parts of the code. Minimizing the amount of dependencies and limiting the responsibility for a function to perform only a single task leads to be readability and predictability of the code.
- Test-driven development leads to a greater test coverage of the code. Code that is covered with tests that are passing, can safely be refactored. Once an issue pops up via a failing test because of refactoring, addressing the bug will take much less time because the failing tests will already point out where to look for the issues to fix.
To summarize, a test is written with the following principles in mind:
Fast: a test should test a single task and run fast because the amount of test will grow accordingly with the size of the project.
Independent: tests should be able to run individually and should never depend on another test, other classes, external storage, networks etc.
Repeatable: a test should be predictable and should have the same behavior no matter where or when the test is running.
Self-checking: A test should check its own state and requirements.
Timely: In TDD tests are the first pieces of code that are written when new functionality is created.
Having to write all the test code before writing the intended functionality can be a time-consuming process. Development time that’s slowly won back by time that would otherwise be lost with random bugs and debugging.
During the writing of tests, if and when small issues begin to pop up, that could otherwise have led to serious bugs, it’s a satisfying idea to know, you have your code covered!
If you would like to read more into the subject of Test-driven development we recommend reading this book: Test Driven Development: By Example written by Ken Beck