Test-Driven Development
fleeting- External reference:
- External reference:
- External reference:
- External reference:
- External reference:
- External reference:
- External reference: https://doc.rust-lang.org/book/ch12-04-testing-the-librarys-functionality.html
- External reference: https://en.wikipedia.org/wiki/Test-driven_development#Test-driven_development_cycle
test first programming, with the development not doing anything more that what the tests check. The tests actually drive the development.
This method is supposed to let the design emerge from the action of thinking the tests. In contrast, BDD makes the specification emerge from the tests.
This method stresses the fact that the implementation should fulfil the tests and only the tests. Hence the name “driven”: the development is only driven by the test, nothing else. This avoids over engineering, produce an helper for refactoring and make the system writer think about what the system is about.
It is important to separate writing the test and writing the code so that you separate the time you make the code work and the time you make the code more elegant.
This video give several hints about how to use it https://youtu.be/0wiIckffWFw
https://youtu.be/aebv1z80vSM says that we have biases when we wrote the code prior to writing the test in favor of testing what we know of the code. Writing the test prior to the code avoids such bias.
This method is all about making the design emerge from the code. Writing the tests up front is only a side effect due to the fact we would be biased otherwise. Thus, defining this method by « simply write the test before the code » is defining on a single side effect of the method and missing the whole point.
It looks like dogfooding, in the sense it helps the developer think like the end user, when thinking about the test without making hypothesis about how the code works.
The tests should mostly be about the exposed API.
Writing tests in TDD is like explaining the application to someone else. Given this situation, When I do this or that, It should do this and that. By looking at the tests, one should be able to understand what the program is meant to do. This provides a refactoring barrier, in which you can feel confident changing the implementation and have the test tell you when you broke the story.
Also, writing a test first is a good way to go beyond the blank page syndrome. You have a failing test that you are thriving to make pass. It’s a good place to start.
Making the test pass becomes the short time outcome. (red/green development)
Also, with time and the tests suite increasing. Some of the first tests will become redundant, as you gain in abstraction and write more appropriate tests. You should then delete the redundant tests.
Test-driven development (TDD) process. This software development technique follows these steps:
Write a test that fails and run it to make sure it fails for the reason you expect. Write or modify just enough code to make the new test pass. Refactor the code you just added or changed and make sure the tests continue to pass. Repeat from step 1!
— https://doc.rust-lang.org/book/ch12-04-testing-the-librarys-functionality.html
The following sequence is based on the book Test-Driven Development by Example:[2]
Add a test
The adding of a new feature begins by writing a test that passes iff the feature’s specifications are met. The developer can discover these specifications by asking about use cases and user stories. A key benefit of test-driven development is that it makes the developer focus on requirements before writing code. This is in contrast with the usual practice, where unit tests are only written after code.
Run all tests. The new test should fail for expected reasons
This shows that new code is actually needed for the desired feature. It validates that the test harness is working correctly. It rules out the possibility that the new test is flawed and will always pass.
Write the simplest code that passes the new test
Inelegant or hard code is acceptable, as long as it passes the test. The code will be honed anyway in Step 5. No code should be added beyond the tested functionality.
All tests should now pass
If any fail, the new code must be revised until they pass. This ensures the new code meets the test requirements and does not break existing features.
Refactor as needed, using tests after each refactor to ensure that functionality is preserved
Code is refactored for readability and maintainability. In particular, hard-coded test data should be removed. Running the test suite after each refactor helps ensure that no existing functionality is broken.
Examples of refactoring:
- moving code to where it most logically belongs
- removing duplicate code
- making names self-documenting
- splitting methods into smaller pieces
- re-arranging inheritance hierarchies
Repeat
The cycle above is repeated for each new piece of functionality. Tests should be small and incremental, and commits made often. That way, if new code fails some tests, the programmer can simply undo or revert rather than debug excessively. When using external libraries, it is important not to write tests that are so small as to effectively test merely the library itself, unless there is some reason to believe that the library is buggy or not feature-rich enough to serve all the needs of the software under development.
— https://en.wikipedia.org/wiki/Test-driven_development#Test-driven_development_cycle
from – https://youtu.be/B1j6k2j2eJg
separate the time you make the code work and the time you make the code more elegant
-
External reference:
Because both require some amount of cognitive load, I suggest letting your brain relax for a few hours in between making the code word and making it more elegant.
Also, using dogfooding helps, because you can make first something that “works”, then try it yourself and feel how inelegant it is. This gives you the incentive to make it more elegant.
It’s strange to realize how simple the code can be when you make it elegant and how you could miss that simplicity when you were focused on making it work. Somehow, we don’t have the brain power to do both at the same time.
virtuous circle
In the long run, you tend to apply this mantra for the code but also for the test. There is a kind of virtuous circle where a stable test battery supports making the code elegant and a stable code supports making the test battery more elegant.
tragedy of TDD
When you have a lot of transparency in the team. As soon as the test passes, the person in charge of the product owner role starts thinking that the task is done and the team is pushed strongly towards the next piece of code.
Therefore, the team pretending to apply TDD and still always applying exceptions falls into the feeling good bias.
Notes linking here
- a TDD way of thinking
- behavior-driven development
- Brain Adapted Development
- braindump et tdd
- builder, user or architect
- definition of done driven development
- exploration and then exploitation development
- is TDD about learning the art of writing “wrong code” right?
- is tdd possible in companies?
- naive interpretation fallacy
- tdd is hard but worth it
- tdd vs bdd
- test first programming
- verified/proved promised contracts based programming
- working code, then elegant code