Refactoring classes that do too much

In this recipe, we will refactor a class that does not follow the S (Single responsibility) from the SOLID principles.

Getting ready

Let's assume that our system under test is a system that generates a new identity for a given person who can have a name, an age, and siblings, and who sends a JSON message over a web service. Note that the following snippet presents a poorly designed class (please refer to Chapter 1, Getting Started with Mockito, for the TestNG configuration and Chapter 7, Verifying Behavior with Object Matchers, for the AssertJ configuration and the BDDAssertions static imports):

public class GodClassNewPersonGenerator {

    static final String DEFAULT_NEW_NAME = "NewName";

    public Person generateNewIdentity(Person person) {
        String newName = createNewName(person);
        int newAge = createNewAge(person);
        List<Person> newSiblings = createNewSiblings(person);
        Person newPerson = new Person(newName, newAge, newSiblings);
        updatePersonData(newPerson);
        return newPerson;
    }

    private String createNewName(Person person) {
        System.out.printf("Calling web service andcreating new name for person [%s]%n", person.getName());
        return DEFAULT_NEW_NAME;
    }

    private int createNewAge(Person person) {
        System.out.printf("Calling db and creating new age for person [%s]%n", person.getName());
        return person.getAge() + 5;
    }

    private List<Person> createNewSiblings(Person person) {
        System.out.printf("Making heavy IO operations and creating new siblings for person [%s]%n", person.getName());
        return asList(new Person("Bob"), new Person("Andrew"));
    }

    private void updatePersonData(Person person) {
        String json = buildJsonStringToPerformTheUpdate(person);
        System.out.printf("Calling web service to updatenew identity for person [%s] with JSON String [%s]%n",person.getName(), json);
    }

    private String buildJsonStringToPerformTheUpdate(Person person) {
        return "{"name":""+person.getName()+"","age":""+person.getAge()+""}";
    }
}

If you are unsure about whether your class or method does too much, there is a quick solution to your problem. The best way to verify it is to check the name of the class or method name. In our case, the class name is exaggerated for you to remember not to write code this way but, in general, it turns out that this class does plenty of things; for example, it generates objects and updates data via a web service. The system under test does it all by itself, whereas it should delegate these responsibilities to its collaborators (separate classes).

How to do it...

In order to refactor a class, you have to perform the following steps:

  1. Check whether the class follows common programming principles such as the SOLID principles.
  2. Ensure that the functionality you are about to refactor is covered by a test (unit, integration, and so on).
  3. Refactor the code by extracting the additional responsibilities to separate classes.
  4. Write a test that verifies the behavior of the system under test.

Note

Since the class does everything (it contains the implementation of different responsibilities), we can't create a mock of any of its parts. Stubbing private methods is not an option since it violates the principles of object-oriented design and visibility of methods. It is extremely difficult to write a unit test that checks whether a new identity has been created and verifies whether a web service has been called once. In fact, it is much easier to test this system by means of an integration test. We can use an in-memory database (for example, http://www.h2database.com/html/main.html) and a stub of a web service (for example, http://wiremock.org/) to help us with this.

Assuming that we have some tests already covering the functionality that we will refactor, let's refactor the code by extracting the additional responsibilities to separate classes. We need to separate the responsibilities of the class as follows:

public class RefactoredNewPersonGenerator {

    private final NewIdentityCreator newIdentityCreator;

    private final PersonDataUpdater personDataUpdater;

    public RefactoredNewPersonGenerator(NewIdentityCreator newIdentityCreator,PersonDataUpdater personDataUpdater) {
        this.newIdentityCreator = newIdentityCreator;
        this.personDataUpdater = personDataUpdater;
    }

    public Person generateNewIdentity(Person person) {
        String newName = newIdentityCreator.createNewName(person);
        int newAge = newIdentityCreator.createNewAge(person);
        List<Person> newSiblings = newIdentityCreator.createNewSiblings(person);
        Person newPerson = new Person(newName, newAge, newSiblings);
        personDataUpdater.updatePersonData(newPerson);
        return newPerson;
    }
}

The NewIdentityCreator class, shown in the following code, contains the logic for generating new identity:

class NewIdentityCreator {

    static final String DEFAULT_NEW_NAME = "NewName";

    public String createNewName(Person person) {
        System.out.printf("Calling web service andcreating new name for person [%s]%n", person.getName());
        return DEFAULT_NEW_NAME;
    }

    public int createNewAge(Person person) {
        System.out.printf("Calling db and creating new age for person [%s]%n", person.getName());
        return person.getAge() + 5;
    }

    public List<Person> createNewSiblings(Person person) {
        System.out.printf("Making heavy IO operations and creating new siblings for person [%s]%n", person.getName());
        return asList(new Person("Bob"), new Person("Andrew"));
    }

}

The PersonDataUpdater class knows how to communicate and update data via a web service. The JavaScript Object Notation (JSON) message is created by a UpdatePersonJsonBuilder class as follows:

public class PersonDataUpdater {

  private final UpdatePersonJsonBuilder updatePersonJsonBuilder;

  public PersonDataUpdater(UpdatePersonJsonBuilder updatePersonJsonBuilder) {
    this.updatePersonJsonBuilder = updatePersonJsonBuilder;
  }

  public void updatePersonData(Person person) {
        String json = updatePersonJsonBuilder.build(person);
        System.out.printf("Calling web service to updatenew identity for person [%s] with JSON String [%s]%n",person.getName(), json);
    }

}

The UpdatePersonJsonBuilder class is shown in the following code:

class UpdatePersonJsonBuilder {

  public String build(Person person) {
    return "{"name":"" + person.getName() + "","age":"" + person.getAge() + ""}";
  }
  
}

Now, the test looks much better. We create a mock for the collaborators and can verify them if necessary. We will be able to see the test only for RefactoredNewPersonGenerator, but thanks to our refactoring, we can easily write a series of tests to the newly created components, testing them in isolation as follows:

@RunWith(MockitoJUnitRunner.class)
public class RefactoredPersonGeneratorTest {

    @Mock NewIdentityCreator newIdentityCreator;

    @Mock PersonDataUpdater personDataUpdater;

    @InjectMocks RefactoredNewPersonGenerator systemUnderTest;

    @Test
    public void should_return_person_with_new_identity () {
        // given
        List<Person> siblings = asList(new Person("John", 10), new Person("Maria", 12));
        Person person = new Person("Robert", 25, siblings);

        // when
        Person newPerson = systemUnderTest.generateNewIdentity(person);

        // then
        then(newPerson).isNotNull().isNotEqualTo(person);
        then(newPerson.getAge()).isNotEqualTo(person.getAge());
        then(newPerson.getName()).isNotEqualTo(person.getName());
        then(newPerson.getSiblings()).doesNotContainAnyElementsOf(siblings); 
    }
    
}

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.81.43