-
20
JUL
2014Dave's No Tests Challenge
I've mentioned before my difficulties in the 2014 IPSC. But taking one beating is no reason not to try again. The first loss just showed me that the contest still had more to teach me.
A buddy of mine has spent some time with the crossword problem and told me that he enjoyed it. I didn't try this problem during the actual event, but I was a little familiar with it from my friend's description.
To add to the fun, I decided this would be a great excuse to take up the recent challenge Dave Thomas gave to the Ruby Rogues: "Stop writing tests."
Step 1: Feedback Loops
Without tests to guide me, I really want to see what's going on. One of the biggest advantages of tests, in my opinion, is the feedback loop it provides. So I set out to provide my own feedback.
Since the problem at hand involves filling in a crossword board, the easiest feedback loop I could think of was to see the board as it fills in. The final board is also the required output. Therefor, I decided a good first step would just be to read the board into some data structure and write it back out. Once I had that, I could insert code between those steps to fill it in. And constantly seeing the board evolve would let me eyeball things for obvious mistakes.
-
30
MAY
2014Objectified Beer
I got another thing out of my recent conversation with Katrina Owen: a will-not-let-go itch to try a programming exercise that she mentioned. I'm such a sucker for a challenge.
Katrina spoke of an assignment that her and Sandi Metz have used in their object orientation trainings. She said that they build an OO implementation of the 99 Bottles of Beer song, "mainly removing
if
s and such." There may be more to the actual task than this, but I ran with that brief explanation.As I often say, you'll probably learn more by trying the exercise for yourself before you read through my thoughts about it. Give it a go if you can spare the time.
Diff Driven Development
I decided to throw some scaffolding into the master branch of a Git repository. I figured I could then branch off of that with each idea, to keep trying things out.
I started by constructing a trivial framework for running and verifying the song. That consisted of an executable:
#!/usr/bin/env ruby -w require_relative "../lib/bottles_of_beer" verses = ARGV.first =~ /\A\d+\z/ ? ARGV.shift.to_i : 99 BottlesOfBeer::Song.new(verses).sing($stdout)
-
11
FEB
2012Test Driving an Algorithm (Part 2)
In the last article, I built a quick and dirty solution to PuzzleNode's Hitting Rock Bottom puzzle. I didn't use specs, objects, or even multiple files. In this article, I'll repeat the exercise but using all of those elements. Let's see how that affects the results.
The Disciplined Approach
This time, I'll reign in my desire to push forward and solve the problem more carefully, without having to build a fully formed algorithm all at once.
I still think the input is the right place to start. I need to read in the units. Let's add a spec for that in
spec/parser_spec.rb
:require "minitest/autorun" require "stringio" require "hitting_rock_bottom/parser" describe HittingRockBottom::Parser do let(:units) { 42 } let(:io) { StringIO.new(units.to_s) } let(:parser) { HittingRockBottom::Parser.new(io) } it "reads the units number from the beginning of the stream" do parser.units.must_equal(units) end end
If you haven't seen the standard
stringio
library before, it simply wraps aString
with anIO
interface. That's will allow me to callgets()
and otherIO
methods on it in my implementation. It's perfect for tests like this. -
1
FEB
2012Test Driving an Algorithm (Part 1)
I want to take a look at some of the differences between Cowboy Coding and Test-Driven Development. To do that, let's solve a problem both ways and see what we can learn from the exercise.
A Puzzle
I needed some random problem to solve in this article and the PuzzleNode site is pretty handy for that. Two programmers I've been working with have recently experimented with problem number 11, Hitting Rock Bottom, so I am familiar with it. Let's use that.
You will probably want to read through the challenge before finishing this article. You may even want to try solving it yourself, just so you'll be more familiar with what I am doing here. The short, short story is that this problem is about simulating the flow of water into a cave for a fixed amount of time and then measuring the depth at each point. It doesn't take too long to solve.
Setup
Let's setup a project. I created a few directories and pulled down the data files given with the problem:
$ mkdir -p hitting_rock_bottom/{bin,data,lib,spec} $ cd hitting_rock_bottom/data/ $ for f in simple_cave.txt simple_out.txt complex_cave.txt > do > curl --silent -O \ > "http://puzzlenode.com/puzzles/11-hitting-rock-bottom/attachments/$f" > done $ cd ..
-
1
DEC
2011Dreamy Testing (Part 2)
In Part 1 of this article, I began building out my ideal testing interface, or at least my best attempt at such a thing.
In that article, I worked primarily on the "assertion" interface: a file full of calls to
ok()
with a block that returnstrue
orfalse
to pass or fail tests. I also built some standard test printers to show us familiar output.As I wrapped up, I was running this code in
example/basic_test.rb
:ok("Is true") { true } ok("Is false") { false } ok("Is error") { fail "Oops" }
and seeing these results:
$ ruby -I lib -r ok example/basic_test.rb Running tests: .FE 0) Failure: Is false example/basic_test.rb:2:in `<main>' 1) Error: Is error example/basic_test.rb:3:in `block in <main>' example/basic_test.rb:3:in `<main>' Finished tests in 0.000300s 3 tests, 1 failure, 1 error
Of course, there was still a lot missing in my code. Let's work on adding some of the other must have features and perhaps a nicety or two.
Running Tests
In the first article, I spent a lot of time talking about how all of the references to things other than my code in tests are a distraction. I wanted to remove as much of that as possible. We have done pretty well on that front.
-
21
NOV
2011Dreamy Testing (Part 1)
I want to take a swing at one last rule before I wrap up this Breaking All of the Rules miniseries, at least for now. I'm not the type of guy to come out full on against many things and I won't do that here. But there is one rule I think is on pretty shaky ground for how often I hear it thrown about. Let's analyze it and break it.
Don't Reinvent the Wheel
It should be pretty thoroughly drilled into most programmer's minds that we don't want to waste our time reinventing wheels. Well, let's try to find the why behind that before we accept it as law.
First, what's the not-so-hidden assumption this time? It's that we are wasting our time. If we aren't, should the rule still hold?
As always, there are good reasons that this rule exists. Here are a couple I feel are worth honoring:
- When you are in the middle of a job and you figure out that you need something, it's usually a much better idea to go with an existing, ready-to-use solution. It would take you time to rebuild it and your version isn't likely to be as robust (just due to it being newer).
- If there's an existing solution that is 90% of what you need, it's probably better to contribute the other 10% than to separately build a new 100% solution. Contributing should be faster for you and help others in return.
-
21
APR
2006Unit Testers Get More Chicks
Just recently, a developer I respect very much was caught uttering the this surprising statement: "Unit tests just really aren't my thing." Now, I still respect this developer very much and I can tell you that the person single-handedly created one of my very favorite pieces of software. However, I do think the developer is dead wrong on this point. This is my attempt to change the mind of everyone that thinks similar things about unit testing.
My belief is that unit testing is for everyone and, in fact, I'll go so far as to say that I believe becoming a test-driven developer is the single best change a programmer can make in their day to day routine. Here are just some of the reasons why:
- Though counterintuitive, I swear that it makes you code faster. No one tends to believe me on this, but most test-driven developers come to this realization eventually. The reason is simple: you spend much less time debugging the 935 errors from your two hour code sprees.
- The tests are my memory. My head is too full of all the languages, frameworks, APIs, and family birthdays I am expected to know to remember everything I've ever done on top of that. This week at work I've touched three separate projects all with over 1,000 lines of code. I'm sure I had good ideas when I wrote those lines, but now I doubt I can tell you what they are. My tests can though. They remember so I don't have to. If I go into the code and change something I don't remember was needed or why, my tests will remind me immediately.
- In team development, my test powered memory even travels to the machines of the other developers! How cool is that? When someone goes into the code and says, "Why on Earth did James do this? We don't need this. I'm going to change that…" my tests will look after my interests for me.
- I'm a lot more confident in my software. I use to say things like, "I just finished this, so it hasn't been used much yet and probably still has plenty of issues." Now by the time I finish something my tests have been using the heck out of it. I won't kid you and tell you that my software now springs fully formed from the head of my tests, but it definitely comes out farther along the track.
- Environment changes seldom surprise me anymore. When I move my software to a different box and it can't cope for whatever reason, I not only know the first time I run the tests, I have an excellent idea of exactly where the problem is.
-
15
JAN
2006What Not to Test
I've now seen multiple claims amounting to something like, "I built this Rails application with just testing." I think it's great that people can do that. They are obviously smarter than me. I need a browser to build a Web application.
I do test my Rails projects, of course. I'm a huge fan of testing and I'm always telling people how much it could help with their work. However, I believe there's a time to test and a time not to, if you can imagine that. Yes, you heard me right, there are things I don't test and I'm sure this next revelation will shock you even more:
I don't test Rails views.
Now before everyone fires up their mail client and pours on the hate mail, let's make sure we are very clear about what I just said. I do write thorough unit tests for all of my model classes, of course. I also test the controllers as much as possible. I make sure the data I expect ends up in the right instance variables with the magic
assigns()
. I also use thesession
Hash
to validate what I am remembering about the user. If a controller action is a complex logic branch that can end up in several different places, I will also make sure I add some assertions to verify that the right template handled the response.