Arnon Axelrod
Arnon Axelrod

Reputation: 1672

How do you go about splitting a class with TDD?

I feel pretty skilled in TDD, and I'm even consired the "TDD expert" in my company, but nevertheless, there are some cases that I feel I don't know how to handle properly, so I would like to hear other's opinions.

My problems is as follows: Even though in general TDD helps me think of the core responsibility of a class, and extract every other responsibility to dependent classes, there are cases that after some time I realize that one of the classes has multiple responsibilities and it needs to be refactored and split it into 2 classes. This conclusion often comes because the tests of that class start to become complicated or repetitive. I can pretty easily do refactoring to split this class to the design I want (and I do it in small steps, keeping on the green bar). My problem is that I end up with the same complicated and repetitive tests that now tests the 2 classes together, while I would like to have seperate tests for each class. The only (more-or-less safe) manner I could think of for doing that, is to do the following for each test (after I completed the refactoring of the production code):

  1. Duplicate the test case
  2. Change one copy of the test to use a mock instead of the 1st class, and the other copy of the test to use a mock instead of the 2nd class.
  3. Then if I see that an identical test already exists for one of the copies, I delete it.

I think that sometimes its possible to do the following:

  1. start by creating the 2 classes from scratch (using TDD of course)
  2. Change the old tests to use the new classes instead of the old one
  3. Delete the old class
  4. Delete the old tests

Both of these techniques seems pretty cumbersome and time consuming, so I wonder: how do the "real experts" go about this issue?

Upvotes: 3

Views: 628

Answers (4)

Assaf Stone
Assaf Stone

Reputation: 6317

I start with asking myself (as you have) what are the responsibilities of a class. Let's say for example that your class is responsible to aggregate weather data and generate a weather report.

At this point I make three (3) lists:

  • Data aggregation members (attributes, behaviors)
  • Report generation members
  • Common members

The first two are easy, the members that exclusively belong in one class exclusively become part of one of the two new classes. I will keep the original dual-responsibility class as a facade, whose members are a pass-through to the new classes, so that tests and functionality will not be broken while refactoring. Depending on circumstances, I may eventually remove the facade, and refactor the tests and dependent objects to use the new classes.

As for the members that are common to both responsibilities - I will move them to a helper class (usually scoped as internal), that the new classes (and any others may use). The functionality has proven to be reused, and may be reused again. Note that the common members might not necessarily all land in one helper class; the helping functionality might be added to one new class, multiple (depending, of course on responsibilities) classes, and some functionality may be added to existing helper classes, if one fits the bill.

Upvotes: 1

KarlM
KarlM

Reputation: 1662

I can pretty easily do refactoring to split this class to the design I want (and I do it in small steps, keeping on the green bar). My problem is that I end up with the same complicated and repetitive tests that now tests the 2 classes together, while I would like to have seperate tests for each class.

I've gotten to this point as well. Here I start refactoring the tests, using the same techniques as for the non-test code - convert variable to field, move field, extract method, move variable, etc etc. Naming is of course very important and provides a lot of design guidance.

eg http://www.kdgregory.com/index.php?page=junit.refactoring eg http://www.natpryce.com/articles/000686.html eg http://www-public.it-sudparis.eu/~gibson/Teaching/CSC7302/ReadingMaterial/vanDeursenMdenBK01.pdf

That last article has some example smells and refactorings common to refactoring tests specifically.

Upvotes: 1

Dennis Traub
Dennis Traub

Reputation: 51644

Without an actual example I can't be sure I know what exactly you mean. But it sounds like you try to test every class (and maybe even every method) in isolation.

When I get to a point where I want to/have to split a class into multiple classes, I tend to still view the resulting collection of classes as a unit and test it as a whole. Only when they stop building a functional whole and start to become independent units, I test them independently of each other.

Upvotes: 2

SSJ_GZ
SSJ_GZ

Reputation: 777

I wondered about this a while back, and couldn't really find a satisfactory answer. Here are some discussions I found on the topic:

http://tech.groups.yahoo.com/group/testdrivendevelopment/message/27199

and

http://tech.groups.yahoo.com/group/testdrivendevelopment/message/16227

Personally, I've adopted a "hair-trigger" approach to moving responsibilites into dependencies, and while "spinning off" a new dependency before there is a clear need for it smacks of YAGNI, I've found that re-absorbing a dependency that turned out to be too anemic to warrant being a separate class is much easier than the rigmarole involved with splitting out a separate class from a class that already has a significant battery of tests written for it.

Edit:

Oh - and I should probably point out that I'm not at all a "real expert" ;)

Upvotes: 0

Related Questions