Dear Uncle Bob,
First I want to send you a big hug for all the stuff you’ve been teaching me since I first met you at the RailsConf2010 in Baltimore.
I guess all the mistakes are mentioned yet and I fear there is no green band left for me anyway. So I took an hour off from my regular work to draft my version of a SortableName class. I will wonder what you and your audience will think about this approach which is highly inspired by Sandi Matz' talk at the latest RailsConf2013 in Portland.
1) Of course I did follow the GRY-cycle in very small steps, just as you told me. I guess this is the essence of Episode 19/1.
But then, I left the way and I’m willing to take your admonishment if I’m completely wrong.
2) I did it in Ruby (because my last Java-experience was long ago and I had no time to refresh my memories this afternoon. This shall make no difference because it’s about the way rather than the tool.
3.) start by setting up my environment in order to see green for
expect(true).to be_true
4.) I made the decision that I will definitely do it in a class. (I took the time for a cigarette and my conclusion was, that this is not too early. Not for the decision, neither for some nicotine and coffee)
4a) green for the initializer
4b) red for no parameter
4c) red/green for expecting an exception-error if the param is nil (I like to fail early and I thought it was time for a kinda joke ,)
5) here I began to leave your way by thinking, ok, the initializer will not do the work, neither can I expect it to do anything but remember the input string.
5a) I wrote a test to make sure a (private) message, responsible to format the output, is called before I output any instance of my class as a string.
6) I took your advise that it’s ok to do more than one physical assert in one test as long as it is all about the same logical assertion. So, I named my test ‘It should format all defined input-variations to match the expected output’.
I started with an empty array of Test-pairs and made sure the test compiles. It was even green because the array was empty and the inner assertion was never called. The test reads as
for pair in pairs do
expect(SortableName.new(pair.first).to eq(pair.last)
end
7.) I filled in the first pair ['',''] The test failed with a nice message telling me: '' is not nil. Of course, my format-function (tested just to be called in 5a) still returns nil.
8) I remembered that gorgeous talk by Sandi Matz, mentioning that one should not test private methods if they are tested through the public interface. So I wrote no more tests for my class, since all the stuff I will do from now on will be done in the private block.
I just return the @name instance-var of my class and the test passed.
9) I found nothing worth a refactor and my RGY-cycle for ['',''] was done.
10.) I added the pair [' ',''] to my definitions.
10a) test failed
10b) added a .strip to the output of my formatting method. -> passes
10c) moved the strip to the initializer thus the format method will have not to care about it.
11.) added ['Name','Name'] to my definition. Passes, the same way [' Name ','Name'] does.
Notice that I’m using SimpleCov to make sure all LOC are covered whenever I run the tests (even the private methods of course).
12.) ['First Last','Last, First'] failed.
12.a) added the split and reverse until green.
12b) cleaned up the mess
And so on….
You can do so with any case of input one may come up with. Without writing a new test. Just add the test-pair and get back to your work. I love writing tests tho. Writing clean production code is even more fun.
At the end I implemented two more Integration-test just to see if it’s possible to get the name formatted for sorting and unformatted as passed when initializing an instance.
Finally I cleaned up everything until I was happy with the code (you can see it on Github).
IMHO I do not violate any of your rules, master, by following this strategy. But please, proof me wrong or even an idiot if I just didn’t see the truth.
Have a nice evening, it's time to sleep here in Austria.
- Andreas
POSTNOMINALS = %w(PhD. BS SR JR esq sen. jun. I II III JEG2).map!(&:upcase)HONORIFICS = /Mr?s?\./
Thank you for your helpful response.
Of course, understanding the spec without knowing the implementation is important. I never thought about this because I'm used to work in very small teams or even on my own. I will do so from now on and I understand that it will be helpful even in a single developer-setup. I remember a few situations where I stuck and had to figure out why a spec doesn't work when it should. Maybe this leak of cleanness was the reason.
I came up with this solutions:
Making the definition-pair a definition-triple or adding a comment to each pair would help to understand what the definition is about to test.
For the problem where the POSTNOMINALS are defined as a constant within the implementation, I think the best way would be to add an extra spec, "all test-pairs should work with appended postnominals". This spec would iterate the test pairs again by appending the postnominals from the constant to every test-input. Doin' so makes it clear that there is a constant and a name on the input can have a POSTNOMINAL as a suffix without the need to know which values are defined inside the constant. Also this would shrinken the number of lines of test-pair-definitions (which is a bit long in my opinion, anyhow)
Changes: https://github.com/iboard/sortable_name_class/commit/345aa24dc17c7e5eb9144e799764713a6db25eb9
No need to reply - I don't want to stress this issue either ;-)
Looking forward for part II
best
- Andreas