OBJECTIVES:
- Everything that used to work still works.
- The new behavior works as expected.
- The system is ready for the next change.
- The programmer & their colleagues feel confident in the above points.
STEPS:
-
Write a list of the test scenarios you want to cover.
-
Turn exactly one item on the list into an actual, concrete, runnable test.
- 3A (Bill Wake):
1. Arrange Create some objects. 2. Act Stimulate them. 3. Assert Check the results.
- 3A (Bill Wake):
-
Change the code to make the test (& all previous tests) pass (adding items to the list as you discover them).
-
Optionally refactor to improve the implementation design.
-
Until the list is empty, go back to #2.
- Rule 1: You are not allowed to write any production code unless it is to make a failing test pass.
- Rule 2: You are not allowed to write anymore of a unit test than is sufficient to fail; and compilation failures are failures.
- Rule 3: You are not allowed to write anymore production code than is sufficient to pass the one failing unit test.
Development Cycle (Wikipedia)
- Add a small / incremental test for a new feature by discovering specifications from user stories / use cases / requirements.
- Run all tests to verify the new test actually fails, for expected reasons, and that new code is actually needed.
- Write the simplest code that passes the new test and nothing more, even if it's inelegant or hard coded.
- All tests should now pass or else revise the new code, until they do, to ensure new code meets test's requirements and doesn't cause regressions (ie. break existing features).
- Refactor, for readablility / maintainability, while running tests to ensure functionality is preserved
- Move code to where it logically belongs.
- Remove hard-coded test data.
- Remove duplicate code.
- Make names self-documenting.
- Split methods into smaller pieces.
- Re-arrange inheritance hierarchies.
- Repeat cycle for each new piece of functionality.
*Commit often and undo / revert new code which fails any tests, rather than debugging excessively.
- What should it do? (verbalize the goal; be clear & precise)
- How will you know it did it? (expected outcome; observable)
- Write test for code yet-to-be.
- (Fails to compile.)
- Write least amount of code to compile.
- Predict how test will fail. (precisely)
- Run test.
- (Fails in predicted way.)
- Write least amount of code to pass.
- Predict it will pass.
- Run test.
- (Passes!)
- Dispelling Myths About TDD - Agile Institute
- How to debug small programs - Eric Lippert
- What's in a Story? - Dan North
- Introducing BDD - Dan North
- Spike Solutions - James Shore
- Spikes in Scrum: The Exception, not the Rule - Scrum Alliance
- Structure and Interpretation of Test Cases - Kevlin Henney (youtube video)
- Unit Testing Best Practices - Microsoft
- Use Cases are Essential - Ivar Jacobson & Alistair Cockburn
- IntegrationTest - Martin Fowler
- XP Design Rules - Martin Fowler
- Focus on Branch Logic - GeePaw
- Assertions: Java; Python
- Glossary of Software Testing Terms - ISTQB
- Cost of Poor Quality Software - CISQ
- Refactoring to Design Patterns: preface; catalog
- Wikipedia
- Scrum Guide (official)
- Scrum Guide Revision History (official)
- Changed Words in Scrum Guide 2020
- Removed Words in Scrum Guide 2020
- 2017 Scrum Guide (official) (old)
- 2016 Scrum Guide (official) (old)
Git; Signing Your Work; Better Commit Messages; Git Aliases
Github Flow; Gitflow - Vincent Driessen's branching model
Github Markdown; Stackoverflow Markdown; Github Task Lists
git [command] -h # Help in console git <command> --help # As man page (Linux); in browser (Windows) git init . git add . git add -u # stage tracked (modified & deleted) files only git status git commit -m "Commit message" git log [--oneline] [--author=name] git tag 1.0.0 <commit-id-from-log> git restore --staged <file> git restore <file> git reset --hard git stash [-u | --include-untracked] git stash list git stash branch <new_branchname> git stash pop git checkout -b <new_feature_branch_name> git diff [--cached] git checkout <master | main> git branch # List branches; Highlight current git merge --squash <new_feature_branch_name> git branch [-d | --delete | -D] <new_feature_branch_name> git clone [--no-single-branch] [email protected]:veganaiZe/TDD.git git clone [--depth=50] https://github.com/veganaiZe/TDD.git git remote add origin <original_upsteam_repo_server> git pull --rebase git push
- Constantly check/consider code against design principles:
- KISS: keep it short & simple; do the simplest thing that could possibly work, first.
- DRY: don't repeat yourself (more than a very few times -- ie. get WET first!).
- POLA: principle of least astonishment --measured by f-words per minute.
- YAGNI: you ain't gonna need it.
- "Never let perfect be the enemy of good enough." (for now)
- "As little as possible; As much as necessary."
- SOLID:
- Determine the next desired method/interface: from the perspective of the application's code.
- Create a new development branch, and switch to it:
git checkout -b new-branch-name
- Write just enough TEST CODE, for a desired interface (in "Act" section), to observe the TEST FAILING against the application's missing interface code.
- Write just enough APPLICATION CODE, implementing an empty method (returning the correct type), to observe the TEST PASSING against the application's incorrect return value.
- Write just enough TEST CODE, including an error detail message (in "Assert" section), to observe a runtime ASSERTION FAILING as expected against the application's incorrect return value.
- Write just enough APPLICATION CODE, to observe the runtime ASSERTION PASSING, by correcting the return value; first iteration being hard-coded.
- Refactor to remove (hard-coded) duplication.
- Commit the small & focused change into the upstream branch/repo:
git checkout master git merge --squash new-branch-name git commit -m "Commit message ..." git branch -D new-branch-name
- When on the fence over whether or not to put some code into its own method: do it, especially if it has any (branch) logic in it. And TDD it into existence!
- When on the fence over whether or not to pre-process data, or process it at runtime, spike/tdd the common functionality before deciding.
- The
main
function is not TDDed; The application'smain
code is replaced by the unit tests'main
code, and vice versa; The application'smain
code is tested at the user interface level (top) of the test pyramid.