Supporting learning is necessary feature of any agile transition. I always advocate the creation of one or more study groups.
When my kids were in middle school they learned a technique for reading and discussing a book called a Literature Circle. They taught me the technique and I’ve used it to create a study group work agreement that I’d like to share. This particular work agreement was for a study group that was reading Working Effectively with Legacy Code by Michael Feathers. Feel free to adapt it for your own study group.
Study Group Work Agreement
All meetings will be scheduled for Tuesdays from 12:30 to 2:00. During the first 30 minutes we will eat lunch together. All members of the Study Group are expected to be present by 1:00. If we all arrive earlier we may choose to start the formal part of the meeting prior to 1:00.
To amplify our effectiveness we have chosen to assign the following roles: Discussion Director, Code Connector, Key Concept Illustrator, and Technical Vocabulary Enricher. Roles will be rotated each meeting and may be assigned to more than one participant. We won’t be assigning the typical meeting roles of Facilitator, Timekeeper, and Scribe—it is everyone’s responsibility to discuss the material in a respectful and timely manner.
As Discussion Director your responsibility is to develop a list of questions that your group might want to discuss about the material. Don’t worry about the small details: your task is to help the group talk over the big ideas in the material and share their thoughts. You are expected to post your questions to the study group wiki.
As Code Connector your responsibility is to choose one or more examples of code from your respective project that relates to the material. The Code Connector is also responsible for making positive changes to their project code base using techniques from the material just covered and to present their results at the next meeting.
As Key Concept Illustrator your responsibility is to choose a few special sections of the material to talk over. The idea is to help members of the group remember one or more the important concepts from the material. Once you have made your choices decide how to share the material. You might draw a UML diagram on a white board or tell a relevant story from your own work experience.
As Technical Vocabulary Enricher your responsibility is to be on the lookout for especially important technical words that your group needs to understand such as the Expose Static Method dependency breaking technique. Other examples from Working Effectively with Legacy Code are Sprout Method and Effect Sketches.
In addition to these roles you may also want to assign the role of Snack Supplier. As Snack Supplier your responsibility is to provide your study group members with a small inexpensive snack. For example, during the study group pilot the members of the study group were treated to Maple Syrup Pie and Lindt chocolates.
Learning to write valuable unit tests is tough for any team. The first hurdle is to create consensus on the team as to what exactly counts as a unit test.
Can you believe that I passed up a beautiful August weekend, at a cottage on a pristine lake just outside Algonquin Park, to spend the day back in the hot, humid city writing code for 45 minutes at a time and immediately throwing the code away? I can hardly believe it myself.
Corey needed a place to stay in Toronto so I offered him a place chez moi (lots of room because the rest of my family was, naturally, at the cottage swimming in the pristine lake). Early Saturday morning Corey and I took the Red Rocket from my place in Kensington Market to Platespin.
After some bagels and coffee, Corey and Joe introduced us participants to the structure of the code retreat: work in focused sessions of 45 minutes; use Java because it is a straight forward language that most everyone understands; develop the code in pairs (or triples); switch pairs after each session; pick at least one thing as the learning objective for each session; and, finally, throw the code away after each session.
What? Throw away the code after each session? Delete the code from version control too?
Yep. I found it quite difficult to throw away the first session’s code. However, by the end of the code retreat I was happily throwing away my code.
The goal of a code retreat is to practice programming rather than to accomplish a programming task. The structure of the code retreat cleverly supported this goal in the following ways:
Starting as 8:30 in the morning on a summer Saturday guarantees only keen participants who wanted to learn;
45 minutes is too short to complete an implementation of Conways’s Game of Life;
While one participant characterized Java as a “baby” language, I think the choice was a good one because we didn’t spend a whole lot of time getting past basic understanding which we might have done had we chosen some other language;
Pairing and frequent pair switching are practices that emphasise learning rather than immediate productivity;
Because every pair was asked to choose one learning goal for each session the programming in each session felt like deliberate practice;
Throwing away the code each time was liberating because I could start a session with a new pair without either one of us having to understand the code that we had just written in the previous session.
Here are some of the deliberate learning goals that my pairs and I chose: don’t use if statements, work inside out by implementing the survival rules for a single cell, write tests using mock objects, and work outside in by writing tests using a DSL to describe the transition from one generation to the next.
Most of the code that I wrote with my pair was awkward and not worth keeping. But that is okay. As Chad Fowler writes in his book, The Passionate Programmer, if I sit down to practice coding and nothing but elegant code comes out, I’m probably sitting near the “center” of my current capabilities instead of the edges, where a good practice session should place me.
The code retreat reminded me how important it is to make time for deliberate practice. That is, make time where it is okay to ignore the strong pull of completing tasks and instead give myself a chance to experiment, learn, and practice my craft.
Those books will have to wait because the O’Reilly book that I’m reading right now is Masterminds of Programming. I want to share something that Tom Love wrote on page 248:
I just handed out to some of my friends this week a big pile of buttons with the word REQUIREMENTS and a red slash through it like a European road sign. On the back of the button are 14 acceptable alternatives to that word. Lots of discussions go on between individuals or between groups of the form of “I couldn’t do this work because you didn’t give me the requirements yet,” or “We need to have a group of people that goes out and gathers the requirements for this new system.”
The term is simply too imprecise. You need to have more precise terms as an alternative. On a big project that I’ve been involved in we have imposed a requirements tax. If anybody uses the word “requirements” standalone, they have to add $2.00 to the entertainment fund. If they want to talk about use cases, or if they want to talk about story cards, or they want to talk about performance metrics, or they want to talk about business cases or business process models, those are all acceptable terms. They don’t incur a tax, because now if you say, “I need to have the use cases or the functional specification, or a mockup of the application that needs to be developed,” that’s a precise request.
Let it be known that I will no longer use the word “requirements” standalone. If you catch me, I’ll buy you a beer. Now if you use the word “requirements” standalone, I’m likely to just get angry. Dear reader, you have been warned.
However, the test assertion failed to work when I first executed my test because I needed an equals() method on the Result class that treated Result like a value object. That is, I wanted to test for equality based on the values of all the Result fields. Without thinking through the consequences, I added this method:
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
Nice. Using the Apache CommonsEqualsBuilder.reflectionEquals() method was simple and clear.
End of story. New code tested with an expressive and easy to write unit test. Not quite.
In the same application, during an overnight process that synchronizes changes from a data warehouse, new results are extracted, transformed, and loaded into the application.
I was doing some performance analysis to try to speed up this process and I noticed that most of the time was spent talking to the data warehouse. No surprise there. I scanned down to locate the first method that was not in the JDBC driver or in the JDBC library and found the equals() method on Result. It was being called over 700,000 times. What had previously been a super fast address comparison was now a reflection based equals. Yikes.
Clearly, adding an equals() method to make test writing easier was a bad idea. Gerard Meszaros categorizes this smell as a special case of Test Logic in Production that he has named Equality Pollution.
So, what did I learn? One, no test logic in production code. Two, never do performance tuning without a tool that lets you see what is actually going on.
I want to show what happens when you use mock objects when you should be using test data builders.
This post is a follow-up to a question that came up during a presentation that Jason and I gave at the March XP Toronto meeting.
We want to test the addMember() method of the CareTeamMembershipService. This method makes a Patient a member of a CareTeam.
As you can see from the implementation below there are two business rules to enforce: (1) patients can only be added to care teams at facilities where the patient is registered and (2) patients have to meet the care team membership criteria.
public void addMember(Patient patient, CareTeam careTeam) {
if (!patient.isRegisteredAt(careTeam.getFacility())) {
throw new CareTeamAdminException();
}
if (!patient.meets(careTeam.getMembershipCriteria())) {
throw new CareTeamAdminException();
}
careTeamMembershipDao.create(patient.getId(), careTeam.getId());
}
Here is the test for the addMember() method using mock objects and test data builders:
public void should_permit_add_for_appropriate_care_team() {
final Facility jacobi = aFacility().build();
final Patient patient = aPatient().at(jacobi).age(18).with(DIABETES).build();
final CareTeam careTeam = anAdultCareTeam().at(jacobi).with(DIABETES).build();
context.checking(new Expectations() {{
one(careTeamMembershipDao).create(patient.getId(), careTeam.getId());
}});
sut.addMember(patient, careTeam);
}
Now that I’m quite used to jMock and test data builder syntax, I find that this test reads like a specification.
During our presentation there was one very interesting question from the audience that was never answered directly: Could you use a Mock instead of a Test Data Builder for the Facility instance?
How about we try right now and see what happens?
Use a mock for the jacobi Facility instance.
public void should_permit_add_for_appropriate_care_team() {
final Facility jacobi = context.mock(Facility.class);
final Patient patient = aPatient().at(jacobi).age(18).with(DIABETES).build();
final CareTeam careTeam = anAdultCareTeam().at(jacobi).with(DIABETES).build();
context.checking(new Expectations() {{
one(careTeamMembershipDao).create(patient.getId(), careTeam.getId());
}});
sut.addMember(patient, careTeam);
}
The test fails because Facility is not an interface.
One solution is to create interfaces for all your domain objects. Yech. Alternatively the jMock framework will let you mock concrete and abstract classes although it is discouraged. To enable this feature you have to configure the context in a special way.
Use a ClassImposteriser mockery.
final Mockery context = new JUnit4Mockery() {{
setImposteriser(ClassImposteriser.INSTANCE);
}};
Now the test fails because of an unexpected invocation: facility.register().
Add expectations on the facility mock object.
public void should_permit_add_for_appropriate_care_team() throws Exception {
final Facility jacobi = context.mock(Facility.class);
context.checking(new Expectations() {{
one(jacobi).register(with(any(Patient.class)));
one(jacobi).register(with(any(CareTeam.class)));
}});
final Patient patient = aPatient().at(jacobi).age(18).with(DIABETES).build();
final CareTeam careTeam = anAdultCareTeam().at(jacobi).with(DIABETES).build();
context.checking(new Expectations() {{
one(careTeamMembershipDao).create(patient.getId(), careTeam.getId());
}});
sut.addMember(patient, careTeam);
}
This fix is not so simple because we have to put both of the register expectations before we’ve actually created our patient and careTeam. We also have to specify the expected arguments with matchers rather than the actual arguments.
Now the test fails because of an unexpected invocation: facility.getPatients().
Add another expectation on the facility mock object.
public void should_permit_add_for_appropriate_care_team() throws Exception {
final Facility jacobi = context.mock(Facility.class);
context.checking(new Expectations() {{
one(jacobi).register(with(any(Patient.class)));
one(jacobi).register(with(any(CareTeam.class)));
}});
final Patient patient = aPatient().at(jacobi).age(18).with(DIABETES).build();
final CareTeam careTeam = anAdultCareTeam().at(jacobi).with(DIABETES).build();
context.checking(new Expectations() {{
one(jacobi).getPatients(); will(returnValue(singleton(patient)));
one(careTeamMembershipDao).create(patient.getId(), careTeam.getId());
}});
sut.addMember(patient, careTeam);
}
Now the test finally passes. However, I find it way less readable as a specification. Why does the specification for the addMember() function require expectations on the Facility instance?
Here is the same test where all the test data builders have been replaced by mock objects.
public void should_permit_add_for_appropriate_care_team() throws Exception {
final Facility jacobi = context.mock(Facility.class);
final Patient patient = context.mock(Patient.class);
final CareTeam careTeam = context.mock(CareTeam.class);<
final MembershipCriteria membershipCriteria = context.mock(
MembershipCriteria.class);
context.checking(new Expectations() {{
one(patient).getId(); will(returnValue("id1"));
one(careTeam).getId(); will(returnValue("id2"));
one(careTeam).getFacility(); will(returnValue(jacobi));
one(patient).isRegisteredAt(jacobi); will(returnValue(true));
one(careTeam).getMembershipCriteria(); will(returnValue(membershipCriteria));
one(patient).meets(membershipCriteria); will(returnValue(true));
one(careTeamMembershipDao).create("id1", "id2");
}});
sut.addMember(patient, careTeam);
}
This is what my tests looked like when I used mock objects without test data builders.
I find it really hard to separate the expectations that have to do with the system under test from the expectations that have to do with the data setup. I also have to know lots of details about the internals of the depended-on components: patient, careTeam, membershipCriteria, and facility.
So always use mock objects with test data builders and your tests can look like this:
public void should_permit_add_for_appropriate_care_team() {
final Facility jacobi = aFacility().build();
final Patient patient = aPatient().at(jacobi).age(18).with(DIABETES).build();
final CareTeam careTeam = anAdultCareTeam().at(jacobi).with(DIABETES).build();
context.checking(new Expectations() {{
one(careTeamMembershipDao).create(patient.getId(), careTeam.getId());
}});
sut.addMember(patient, careTeam);
}
It turned out that essentially everyone was using a unit testing framework and most everyone in the audience had had some exposure to a mocking framework. Very few people had read xUnit Test Patterns. No one was sure about Test Data Builders.
It was the perfect audience for our presentation.
The takeaway from our presentation is that you need to use mock objects with Test Data Builders if you want to create maintainable unit tests. Use mock objects when protocol is important and use Test Data Builders when data is important.
I often tell people that in our modern world software is everywhere. I had no idea how much software was in cars. Here are two quotes from Manfred Broy in IEEE Spectrum:
[A] premium-class automobile … probably contains close to 100 million lines of software.
The cost of software and electronics can reach 35 to 40 percent of the cost of a car … with software development contributing about 13 to 15 percent of that cost.
When I first met Su, her Grandmother asked me, “What can you do?” I said I was a programmer. She asked me again, “No. No. What can you do with your hands? What can you make?” From her point of view I was pretty useless. Her husband was a tool and die maker and made all the cupboards in her kitchen. When he got older he made himself a wood splitter that we still use. He could certainly make things.
I never imagined that I would be able to repair a car. I might have been wrong.
Alistair McKinnell has been writing software since the days of punch cards. After reading Kent Beck's Extreme Programming Explained in 1999 he realized he had found his people. Alistair is an active member of the Toronto XP/Agile community.