Is this a holiday? What, know you not,
Being mechanical, you ought not walk
Upon a labouring day without the sign
Of your profession? Speak, what trade art thou?"
-- Shakespeare, Julius Caesar
Act I, Scene I, Lines 1-5
The Leap Year kata is interesting. The real trick to the kata is not the leap year algorithm. No, it is picking the test cases in such a way as to not cause you to have to write the whole thing at once.
Let us look at an implementation in Clojure.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns leap-year-checker) | |
(defn leap-year? [year] | |
(let [divisible-by (fn [n] #(zero? (mod % n)))] | |
(cond | |
(> 1584 year) false | |
((comp not (divisible-by 4)) year) false | |
((comp not (divisible-by 100)) year) true | |
((comp not (divisible-by 400)) year) false | |
:leap-year true))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns leap-year-checker.tests | |
(require | |
[clojure.test :refer [deftest testing are run-tests]] | |
[leap-year-checker :as sut])) | |
(deftest leap-year?-tests | |
(testing "Given a leap year it must return true" | |
(are [year] (true? (sut/leap-year? year)) | |
1600 2000 2400)) | |
(testing "Given a year not divisible by 400 it must return false" | |
(are [year] (false? (sut/leap-year? year)) | |
1700 2100 2300)) | |
(testing "Given a leap year not divisible by 100 it must return true" | |
(are [year] (true? (sut/leap-year? year)) | |
1584 2004 2016)) | |
(testing "Given a year not divisible by 4 it must return false" | |
(are [year] (false? (sut/leap-year? year)) | |
1585 2005 2015)) | |
(testing "A year before 1584 cannot be a leap year thus it must return false" | |
(are [year] (false? (sut/leap-year? year)) | |
1581 4 800))) | |
(run-tests) |
We see that the key to picking test cases is to have an idea of the order in which you plan on writing your code. How so? In the first test case we pick leap years divisible by 400, then we pick non-leap years divisible by 100, then leap years divisible by 4, and then years which are not leap years.
Does setting up your test case with the algorithm in mind violate TDD? I say no. In fact I think Uncle Bob would say no.
TDD does not mean no architecture. TDD does not mean no forethought. No, TDD means no production code is written without a test and that only as much production code is written as what is needed to pass the current failing test.
In other words, TDD would want you to think about the leap year algorithm a head of time and pick test cases which will move along the algorithm.