-
-
Save darrencauthon/2641469 to your computer and use it in GitHub Desktop.
require 'minitest/autorun' | |
class Employee | |
attr_reader :first_name, :last_name | |
def initialize(first_name, last_name) | |
@first_name = first_name | |
@last_name = last_name | |
end | |
def full_name | |
"#{@last_name}, #{@first_name}" | |
end | |
end | |
describe "Employee" do | |
before do | |
@first_name = Object.new | |
@last_name = Object.new | |
@employee = Employee.new(@first_name, @last_name) | |
end | |
it "should set the first name" do | |
@employee.first_name.must_equal @first_name | |
end | |
it "should set the last name" do | |
@employee.last_name.must_equal @last_name | |
end | |
end | |
class TestEmployee < MiniTest::Unit::TestCase | |
def test_full_name_is_last_name_plus_first_name | |
Employee.new("John", "Galt").full_name.must_equal "Galt, John" | |
Employee.new("Howard", "Roark").full_name.must_equal "Roark, Howard" | |
end | |
end |
But I think the bigger point, beyond that, is the TestEmployee class. This class contains a unit test that tests the full_name method, verifying that it returns the first and last names in a properly formatted string. I sent two examples through it in the unit test, both to cover the tiny bit of logic that I had to put into the full_name method.
My concern is that with the "describe Employee" spec, there's not much room to fit this type of quick test. Whether I use @employee, or let(:employee){...} or whatever, the additional complexity that RSpec or even MiniTest's describe would make a quick two-line, two-test method into a ceremonial dance... OR.... I just drop the second test and say that it's not worth the trouble.
I'm not saying that's the wrong thing to do in all cases, but there are a couple reasons why I'd want to avoid it:
1.) I don't write tests to satisfy a pair partner or even just to verify that it runs in a specific situation -- I want to flex the code. I want to make it run in many situations because that's what will happen in production. I don't think it takes a lot of flexing for a simple "full_name" method.
2.) The more "orthodox" approach to TDD gives you something special: It tests your tests. When you write simple tests implemented by simple code (even to the point of hardcoding the result), you're pushed into a process where an invalid test won't be able to be satisfied because it will conflict with the assertions you make in other tests. Obviously, you can still make a mistake, but you'd have to make 2 or 3 mistakes in a row for the problem to hit production. Compare that to one.
I was reminded of this when I wrote the test above. I swapped "Howard" and "Roark" in the constructor, and after I implemented the code I thought should work I still got a failing test. The code looked right, so I checked my test... ah, there's the problem.
The way I look at it: If I'm not able to write the correct code without tests, how can I be expected to write the correct test code without tests for it? This isn't a when-do-you-stop situation, it's a just-use-TDD situation.
describe Employee do
it '#full_name should be first name plus last name' do
Employee.new("John", "Galt").full_name.should == "Galt, John"
Employee.new("Howard", "Roark").full_name.should == "Roark, Howard"
end
end
Same number of lines, more descriptive. The let
, subject
etc. are there to DRY up several or dozens of tests, for a single test yeah there's no reason to use them.
Would I keep this within the same scope as the other? With @employee set? Or would this just be another describe block?
BTW :) I don't mean to ask piddling questions -- this issue, and how I've seen it handled by good Ruby/Rails devs, has been hard on my mind for a while.
It's all just convention, do what feels right. I typically only nest describes when I'm writing more than one test for a method. A single test I just do a root-level "it" block.
describe Employee do
describe '#initialize' do
it '#first_name should be set from the first argument' do
Employee.new('Bob', 'Charlie').first_name.should == 'Bob'
Employee.new('Rob', 'Charlie').first_name.should == 'Rob'
end
it '#last_name should be set from the second argument' do
Employee.new('Bob', 'Marley').last_name.should == 'Marley'
Employee.new('Rob', 'Charlie').last_name.should == 'Charlie'
end
end
it '#full_name should be first name plus last name' do
Employee.new("John", "Galt").full_name.should == "Galt, John"
Employee.new("Howard", "Roark").full_name.should == "Roark, Howard"
end
end
We know that names are practically always treated as strings, and we naturally assume that users of the Employee class will pass in strings as the first name and last name as strings. But is there anything specific in my code that requires that they be strings? No. The class constructor will take in any two objects in the constructor, and will return the appropriate one with the first_name and last_name methods.
By testing these methods with an object that could only be instantiated by the test, I can protect myself from my a-hole adversarial pair programming partner! :D (j/k). The simplest way to implement the attr_reader won't be a hardcoded string, it will be the code that we'd want anyway.