Contradiction: Test Everything, but not Accessors?

At the Agile Developer Skills course at the Raikes School, I commented that we don’t usually test accessors. But we test everything. Is this a contradiction?

Last week, Chet, Cheezy and I presented a CSM class and an Agile Developer Skills class at the Raikes School at the University of Nebraska (Lincoln). Great fun. At the end, a concern was mentioned and I’ve decided to write about it.

As part of the class, we review participants’ code on the screen. Naturally, we have them trying to do TDD. A number of teams had written a bunch of accessors on their classes, and had dutifully written a test for each one. I commented that I don’t generally write tests for accessors. I tried to explain that the accessors are covered by other tests, but apparently I didn’t get that across, because there was a comment about the “contradiction” at the end. So I decided I’d try to explain a little more fully here.

To make what I do familiar to the class participants, I’ll do my example using the payroll stories we used in the class. To make it interesting to me, I’ll do it in Scala. I’m sure readers will have no trouble following along.

Let me call my shot. Here’s my plan: I’m going to implement a few of our payroll stories, all in one go, with a design that will require a number of accessors. I will write no accessor tests, and at the end of the exercise, all the accessors will be tested! No accessor tests, but the accessors tested? How is this possible? Read on …

My proposed design is to have a “Timesheet” object and an “Employee” object, and to use them in a “Payer” object. In the final system, I imagine a Payroll object looping over employees, and providing an employee and her timesheet to the Payer, but for now, that will be done under control of the test. The three main objects should be enough to show why I don’t test accessors directly.

My proposed test is a bit more of a reach than I would recommend to a TDD beginner … or even to myself. As a side project, let’s see if I get into trouble. I propose that Kate Oneal makes $150 an hour and that she works 40 hours regular time, ten hours time and a half, and 5 hours double time. (Kate would never really do that: she’s too smart.) Anyway, here’s our test:

import org.scalatest.Spec

class PayrollTests extends Spec {
  describe ( "Payroll Testing" ) {

    it ( "should calculate Kate base pay" ) {
      val employee = new Employee("Kate", 150)
      val timesheet = new Timesheet(40,10,5)
      val payer = new Payer(employee , timesheet )
      expect(9750)(payer.grossPay)
    }
  }
}

Not much to see here, really. We create an Employee, Kate, with a pay rate of 150. We build a time sheet with her hours. We create a payer on those two objects, and ask it for Kate’s gross pay. (I hope I calculated that correctly. Did you know that the Windows 7 calculator allows parentheses? It does.

This code does not compile, of course. So I’ll create the classes. This is quick, well within my ten minutes between running tests rule … I hope!

class Employee(private val _name: String, private val _payrate:Int) {

}

class Timesheet(
  private val _regularHours:Int,
  private val _ot1Hours:Int,
  private val _ot2Hours:Int) {

}

class Payer(private val _employeee:Employee, private val _timesheet:Timesheet) {

}

These three classes are almost enough to let the program compile. Just one more thing: we need a grossPay method:

class Payer(private val employeee:Employee, private val timesheet:Timesheet) {
  def grossPay = 666
}

This suffices to compile and run the test. We get, of course, “Expected 9750, but got 666″. We can continue to implement. We know, of course, that the grossPay result should be rate*regular hours + rate * 1.5 * ot1 hours + rate * 2 * ot2 hours. We type that in:

  def grossPay = _timesheet.regularHours*_employee.payrate +
                 _timesheet.ot1Hours*_employee.payrate*1.5 +
	         _timesheet.ot2Hours*_employee.payrate*2

This will not compile (no surprise) because we cannot access those values from the employee and timesheet objects. So one by one, we implement the accessors, until the compiler is quiet:

class Employee(private val _name: String, private val _payrate:Int) {
  def payrate = _payrate
}

class Timesheet(
  private val _regularHours:Int,
  private val _ot1Hours:Int,
  private val _ot2Hours:Int) {

  def regularHours = _regularHours
  def ot1Hours = _ot1Hours
  def ot2Hours = _ot2Hours
}

At this point we run the test again … and it is green!

That was a pretty big bite …

Three classes, a bunch of accessors, and one method, all from one test. That’s more than I’d normally do, and more than I’d suggest to people beginning with TDD. It all went well, but it might not have, and it would likely have taken ages to debug. In this case, I had very clearly in mind what I’d do … and I was lucky. Try this at home if you like … and be aware, when it goes wrong, that smaller steps are probably better.

However, we got to our desired end point:

Test everything … and don’t test accessors!

Note the result: all the accessors are tested … but we never wrote a test for an accessor. Instead, we had a test for the operation of the feature, which required that we write certain accessors: it could only execute correctly if the accessors were present and correct. ┬áSo the test for function drove us to implement the accessors (and a bit more operational code) in order to make the test run.

When you find yourself wanting to test-drive an accessor, and you have no other failing test, write a test that can only be made to work if you have that accessor. Make it a test that actually advances the application, not just a simple check of a value.

This is not just a trick of wordplay. Whenever we find ourselves thinking we need an accessor, we hold back until the code tells us that we need it, via a failing test. Then, and only then, we write the accessor … and our existing failing test tests the accessor along the way. Working this way keeps our eye on advancing the application, not just writing simple tests for code we may someday need.

Posted on:

Written by: Ron Jeffries

Categorization: Articles, Practices

Recent Articles

Issues with SAFe

Recent Twitter conversations inspire me to pull out some of my concerns with SAFe and talk about them.

SAFe – Good But Not Good Enough

I recently took the SAFe SPC training. My bottom line assessment is that it will be a marketing success, organizations trying it will see improvement, and some will see great improvement. And I don't like it.