Konubinix' opinionated web of thoughts

Test-Driven Development

fleeting

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://www.youtube.com/watch?v=0wiIckffWFw

https://www.youtube.com/watch?v=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]

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

time text
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=136.319s fifth step is refactoring and improving
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=139.04s the code
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=140s while you have the test harness running
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=142.319s so you make sure that you always have
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=144.319s passing tests while you’re doing that so
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=146.239s this is the main process of test driven
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=148.64s development and you can repeat this
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=150.72s cycle of writing new tests making sure
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=152.72s they fail writing the code making sure
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=154.72s test pass and then refactoring over and
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=156.959s over again and if you’re working in an
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=158.56s agile setting you should do this in
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=160.16s small incremental steps and make commits
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=162.72s very often the process of writing
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=165.28s failing tests first
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=167.04s then write the code that makes those
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=169.2s tests paths and then refactoring the
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=171.76s code to improve it is also called red
https://www.youtube.com/watch?v=B1j6k2j2eJg&t=174s green refactor

from – https://www.youtube.com/watch?v=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.

Notes linking here