This document is intended to guide you through the use of Test-Driven Development (TDD) to implement a specific feature in your project while adhering to the YAGNI (You Ain't Going to Need It) principles as outlined by Martin Fowler. YAGNI emphasizes the practice of not adding functionality until it is necessary, essentially meaning you should only implement something when there is a test that requires it. This approach avoids over-engineering and keeps the design simple and focused on what is needed at present.
The goal of TDD is to produce code that is professional quality. In particular, the code MUST:
-
Correctly implement the desired functionality.
Add automated unit tests to help ensure this goal.
-
Be easy for others to read and understand.
Refactor the code to be "beautiful" production-quality code to help ensure this goal. See Robert Martin's excellent book Clean code: a handbook of agile software craftsmanship.
Write code that is as simple as reasonable, and is highly readable. That code will be easier to change.
Write code that you would be proud to show to a peer.
While your code MUST meet both goals, put more attention on the second goal as maintainability is critical for professional code and most students do not have much practice with producing simple, well-organized, readable code.
Before writing any unit tests or code, read everything in this document, includeing the content linked to by the hyperlinks.
Our project is structured to facilitate iterative development using the red-green-refactor cycle of TDD. Initially, our code and tests are stubbed out to throw a Not Implemented exception, indicating functionality that exists solely in a "red" state, awaiting implementation.
Before you start implementing your feature, create a new branch in Git. This isolates your development work from the main codebase, allowing for easy version control and review processes. You can create a new branch via the following Git command:
git checkout -b your-branch-name
To get familiar with the unit testing framework used in this project, doctest
, refer to the FRAMEWORK.md
file which is located in the same folder as this README.md
. This document provides comprehensive guidelines and is available on your GitHub repository as well.
Implement the IntegerToWordedString
function which converts integers into their respective textual representations. Begin with the first unit test, which should be designed to fail initially (red state), testing the simplest case (e.g., transforming 1 to One). Remember the advice from home-of-tdd on how to generate the first test case!
Plan: Decise on the next tiny change in functionality to work on.
Red: Write a unit test to check that new functionality. Make sure that the text fails, since the associated functionality is not yet implemented.
Green: Implement the necessary functionality to pass the failing unit test.
Commit your changes to Git: Once the test passes, commit your changes to the current branch.
Yellow: Refactor stage. Is the code "beautiful"? Could the design and readability be improved? Apply development principles like DRY (Don't Repeat Yourself) and SOLID. Ensure any refactoring does not break existing tests.
Commit your changes to Git: If you have refactored the code, commit your changes to the current branch.
It might be tempting to stray from this red-green-refactor process, but consistency is crucial for learning! And this process works!
Focus on adding just enough useful test cases so that you are confident that the code works appropriately. Use equivalence partitioning and boundary testing, select a representative value from that group for testing, write the corresponding test, and then refine the code as necessary!
After your first "yellow" state, add one new unit test similar to the existing unit test FirstPositiveIntegerIsOne
that will test a different input and expected result. Make sure that all tests pass except this one new test -- putting the code into the "red" state. Write code to implement the functionality verified in the unit test until the test passes again ("green" state), then commit the changes to git. Refactor if the code is at all messy, and commit the changes to git. Continue this loop for as long as you need for fully implementing the specifications.
Pull Requests and Merging: If you are working on a production codebaser, after completing a cycle of implementations consider merging your branch back into the main branch through a pull request. This practice endorses Continuous Integration, continuously merging small pieces of change into the main branch, which helps catch integration bugs early and improves cohesion in collaborative environments.
Repeat: Continue with the red-green-refactor cycle until the feature is fully implemented based on the comprehensive specifications dictated by your tests.
By adhering to this structured approach, you enable a methodical implementation that adheres closely to specified requirements while maintaining high standards of code quality and coherence.