---
title: "Making your tests withstand design and interface changes - remove code duplication"
url: "https://osherove.com/blog/2005/4/10/making-your-tests-withstand-design-and-interface-changes-rem.md"
canonical: "https://osherove.com/blog/2005/4/10/making-your-tests-withstand-design-and-interface-changes-rem.html"
published: "2005-04-10T23:29:00"
updated: "2005-04-10T23:29:00"
tags: ["Agile Related", "Testing Guidelines"]
author: "Roy Osherove"
description: "One of the biggest pitfalls developers writing unit tests face is in the face of a re-design. They write hundreds of unit tests for various classes, and suddenly a design change breaks many of them - a default…"
usage: "Licensed under CC BY 4.0 — reuse, quote and adapt with attribution (Roy Osherove, link to the original page)."
license: "https://creativecommons.org/licenses/by/4.0/"
---

# Making your tests withstand design and interface changes - remove code duplication

By Roy Osherove April 10, 2005 In [Agile Related](https://osherove.com/blog/category/agile-related), [Testing Guidelines](https://osherove.com/blog/category/testing-guidelines)

**Historical article** · Originally published April 10, 2005; technical details may be outdated.

One of the biggest pitfalls developers writing unit tests face is in the face of a re-design. They write hundreds of unit tests for various classes, and suddenly a design change breaks many of them - a default constructor is removed, parameters are added or removed from interfaces, various default conditions change - havoc rears its ugly head.

The poor developer is then faced with "fixing" hundreds of tests just because a method was added to a constructor.

To remove this obstacle from the developer one must take the precaution in writing the tests. There are several rules you should abide by that will save your hiny on most events where such a change occurs:

here are some guidelines:

- **Encapsulate object creation code** in helper methods inside your test fixture. If just your fixture is creating these objects - these methods should reside as private methods on the fixture. If more classes use them, they should be refactored out into a separate helper class with static helper methods for this creation (also known as the "Object Mother" pattern).

   for example:

      **\[Test\]**

      **public void Sum\_ReturnsZeroByDefault()**

      **{**

         **Calculator calc = new Calculator();**

         **int result = calc.Sum();**

         **...**

      **}**

should be refactored into:

        **\[Test\]**

      **public void Sum\_ReturnsZeroByDefault()**

      **{**

         **Calculator calc = setup\_CreateCalculator();**

         **int result = calc.Sum();**

         **...**

      **}**

      **private Calcualtor setup\_CreateCalcualtor()**

      **{**

         **return new Calculator();**

      **}**

notice the following:

- After this change, any changes to the Calculator's public constructor should only propagate to a small "fix" in one private method in the fixture, used by many tests.
- The helper method has a special prefix "setup\_" to denote that it is a helper method used to setup object instances for test cases. It also makes your test more readable.

If multiple test fixture need to use the Calculator object, the code might turn into:

  \[Test\]

      public void Sum\_ReturnsZeroByDefault()

      {

         Calculator calc = setup\_CreateCalculator();

         int result = calc.Sum();

         ...

      }

      private Calcualtor setup\_CreateCalcualtor()

      {

         return **CalctulatorTestHelper.CreateDefaultCalculator();**

      }

         **public class CalctulatorTestHelper**

      **{**

               **public static Calculator CreateDefaultCalculator()**

         **{**

                     **return new Calculator();**

         **}**

      **}**

notice the following:

- We still keep the original setup\_ helper method so that we change as little code as we need to in the old fixture. We just delegate the work into the CalculatorHelperobject. This level of indirection might not be needed but might help us in the future if we find we need to actually make a change to the CalculatorHelper class.
- None of this code sits in production, but is only related to the testing project

- **Encapsulate complex or lengthy object initialization code**  or complex interaction code into helper methods in your fixture or helper objects. If you find that you have two tests that initialize an object's state to the same state - refactor that code into a different method that returns the object with the known state. This helps future changes to the various object methods and default state behavior not break your tests and you bursting into tears.

for example:

      **\[Test\]**

      **\[ExpectedException(typeof(Exception),"User cannot be added twice")**

      **public void AddUser\_AddingSameUserTwiceThrowsException()**

      **{**

         **LoginManager lm = setup\_CreateDefaultLoginManager();**

                  **lm.AddUser("a","b");**

         **//exception should be thrown here**

                  **lm.AddUser("a","");**

      **}**

      **\[Test\]**

      **public void ChangePassword\_AddingSameUserTwiceThrowsException()**

      **{**

         **LoginManager lm = setup\_CreateDefaultLoginManager();**

                  **lm.AddUser("a","b");**

         **lm.ChangePassword("a","c");**

                  **bool isLoginOk = lm.IsLoginOK("a","c");**

                  **Assert.IsTrue(isLoginOk,"Login should have been accepted for user with the new password"**

      **}**

      **private LoginManager setup\_CreateDefaultLoginManager()**

      **{**

         **return new LoginManager();**

      **}**

   should be replaced with something along the lines of:

      \[Test\]

      \[ExpectedException(typeof(Exception),"User cannot be added twice")

      public void AddUser\_AddingSameUserTwiceThrowsException()

      {

         **LoginManager lm = setup\_CreateLoginManagerIwhtOneDefaultUser()**

         //exception should be thrown here

                  lm.AddUser("a","");

      }

      \[Test\]

      public void ChangePassword\_AddingSameUserTwiceThrowsException()

      {

         **LoginManager lm = setup\_CreateLoginManagerIwhtOneDefaultUser**

         lm.ChangePassword("a","c");

                  bool isLoginOk = lm.IsLoginOK("a","c");

                  Assert.IsTrue(isLoginOk,"Login should have been accepted for user with the new password"

      }

      **private LoginManager setup\_CreateLoginManagerIwhtOneDefaultUser()**

      **{**

                  **LoginManager lm = setup\_CreateDefaultLoginManager();**

                  **lm.AddUser("a","b");**

         **return lm;**

      **}**

some points on this:

- You could also use various helper methods that take parameters (for example, a login manager with N users already added to it to check various counts
- You should use this method whenever you find duplication in your tests. **Don't do it until** you see duplication in your test code. Your primary concern is to make the test work, then refactor the test code as well as the production code.
- If you continue on with this method your tests will become very robust and be able to adept to new requirements in your code with little effort.
- Note that this method helps when the design change does not affect the **logic** behind your test. For example, the method of adding a user to LoginManager might change, but Changing a password for the user (as depicted above) should still be tested the same. If the design affects your test logic - there's no escape but changing the test. You should be very wary of changing your tests just to make them pass. Sometimes they break because they are telling you that you broke something in your logic.

## Lessons on this topic

- [A Refactoring Dillemma](https://courses.osherove.com/courses/tdd-pairing-sessions-building-a-go-game/a-refactoring-dillemma/) · TDD Pairing Sessions - Building a Go Game
- [Advanced RSpec and Cleaning Up The Specs](https://courses.osherove.com/courses/tdd-and-bdd-in-ruby/advanced-rspec-and-cleaning-up-the-specs/) · TDD and BDD in Ruby

## More in Agile Related

[All posts in Agile Related](https://osherove.com/blog/category/agile-related)

- Apr 11, 2005

    ### [Test maintainability: consider creating a test fixture "per method" instead of "per class"](https://osherove.com/blog/2005/4/11/test-maintainability-consider-creating-a-test-fixture-quotpe.html)

- Apr 11, 2005

    ### [Enterprise library -- Test usability and "runnability"](https://osherove.com/blog/2005/4/11/enterprise-library-test-usability-and-quotrunnabilityquot.html)

- Apr 10, 2005

    ### [What should you write in a test Assert message?](https://osherove.com/blog/2005/4/10/what-should-you-write-in-a-test-assert-message.html)

- Apr 10, 2005

    ### [Agile Israel news and updates](https://osherove.com/blog/2005/4/10/agile-israel-news-and-updates.html)


---

## About the author

**Roy Osherove**: Senior Solutions Architect at AWS working on AI for production and AI-driven development; author of The Art of Unit Testing and Elastic Leadership, with about 20 years of consulting and training on unit testing, TDD, team leadership and continuous delivery.

### Sites

- [RobotPaper](https://robotpaper.ai/): This blog: AI patterns and anti-patterns, AI and coding agents, and engineering leadership.
- [osherove.com](https://osherove.com): Roy's personal site. Author of The Art of Unit Testing and Elastic Leadership; consulting and training on unit testing, TDD, team leadership and continuous delivery, plus about 20 years of blog posts.
- [The Art of Unit Testing](https://www.artofunittesting.com): The Art of Unit Testing, 3rd Edition (Manning): readable, maintainable, trustworthy unit tests, plus TDD workshops in C#, JavaScript, Java and Kotlin.
- [5whys.com](https://www.5whys.com): Roy's writing on software team leadership: self-organizing teams, surviving chaos, learning mode and the Elastic Leadership model.
- [Pipeline Driven](https://pipelinedriven.org): Continuous delivery and DevOps practices: build patterns, pipeline-friendly testing, DevOps metrics and enterprise DevOps.
- [Elastic Leadership](https://www.elasticleadership.com): Elastic Leadership, Roy's framework for software team leaders (survival, learning and self-organizing modes).

### Social

- X: https://x.com/royosherove
- LinkedIn: https://www.linkedin.com/in/osherove/

### Contact

- Contact form: https://forms.gle/jQSx5KoXvrVA1JCK7

### License

Licensed under CC BY 4.0 — reuse, quote and adapt with attribution (Roy Osherove, link to the original page). License: https://creativecommons.org/licenses/by/4.0/
