Plus it seems that having to
synchronize a comprehensive test suite
with constantly changing code would be
a pain. I understand that the unit
test suite could help tremendously
during maintenance, once the software
is built, stable, and functioning, but
that's late in the game wheras TDD is
supposed to help early on as well.
I do agree that the overhead of having a unit test suite in place can be felt at these early changes, when major architectural changes are taking place, but my opinion is that the benefits of having unit tests far outweigh this drawback. I think too often the problem is a mental one - we tend to think of our unit tests as second class citizens of the code base, and we resent having to mess with them. But over time, as I've come to depend on them and appreciate their usefulness, I've come to think of them as no less important and no less worthy of maintenance and work as any other part of the code base.
Are the major architecural "changes" taking place truly only refactorings? If you are only refactoring, however dramatically, and tests begin to fail, that may tell you that you've inadvertantly changed functionality somewhere. Which is just what unit tests are supposed to help you catch. If you are making sweeping changes to functionality and architecture at the same time, you may want to consider slowing down and getting into that red/green/refactor groove: no new (or changed) functionality w/o additional tests, and no changes to functionality (and breaking tests) while refactoring.
Update (based on comments):
@Cybis has raised an interesting objection to my claim that refactoring shouldn't break tests because refactoring shouldn't change behavior. His objection is that refactoring does change the API, and therefore tests "break".
First, I would encourage anyone to visit the canonical reference on refactoring: Martin Fowler's bliki. Just now I reviewed it and a couple things jump out at me:
- Is changing an interface
refactoring? Martin refers to
refactoring as a
"behavior-preserving" change, which
means when the interface/API changes
then all callers of that
interface/API must change as well.
Including tests, I say.
- That does not mean that the behavior has changed. Again, Fowler emphasizes that his
definition of refactoring is that
the changes are behavior
preserving.
In light of this, if a test or tests has to change during a refactoring, I don't see this as "breaking" the test(s). It's simply part of the refactoring, of preserving the behavior of the entire code base. I see no difference between a test having to change and any other part of the code base having to change as part of a refactoring. (This goes back to what I said before about considering tests to be first-class citizens of the code base.)
Additionally, I would expect the tests, even the modified tests, to continue to pass once the refactoring is done. Whatever that test was testing (probably the assert(s) in that test) should still be valid after a refactoring is done. Otherwise, that's a red flag that behavior changed/regressed somehow during the refactoring.
Maybe that claim sounds like nonsense but think about it: we think nothing about moving blocks of code around in the production code base and expecting them to continue to work in their new context (new class, new method signature, whatever). I feel the same way about a test: perhaps a refactoring changes the API that a test must call, or a class that a test must use, but in the end the point of the test should not change because of a refactoring.
(The only exception I can think of to this is tests that test low-level implementation details that you may want to change during a refactoring, such as replacing a LinkedList with an ArrayList or something. But in that case one could argue that the tests are over-testing and are too rigid and fragile.)