TDD – Decouple Test Code from Production Code with Examples

tddunit testing

I try to understand better under what circumstances one-to-one correspondence between test code and production code is not needed, after reading Uncle Bob's TDD Harms Architecture. For example, first:

"a one-to-one correspondence implies extremely tight coupling."

then below:

"If you look at part of FitNesse written after 2008 or so, you’ll see that there is a significant drop in the one-to-one correspondence. The tests and code look more like the green design on the right."

It does not explain why the one-to-one correspondence is dropped.

I want to know why, on the assumption that only public method is tested

  • Is it because the production class or method are covered by other test class/method INDIRECTLY? or
  • Is it because they are so trivial, or the developer is so confident, that the test code is dropped? or
  • Are there general design pattern or rules that can help writing code that decouples test from production code?
  • Any other reason?

Could anyone please explain?

Best Answer

Is it because the production class or method are covered by other test class/method INDIRECTLY?

Basically yes. The tests are there to ensure the system has some property. Usually that the system provides some functionality. (We sometimes write test to reproduce a defective behavior, too, however, for instance when filing a bug report.) So if you write new tests that are less fragile because they are more decoupled from the production code, and also more expressive because they state what functionality the system achieves, and not how the system achieves it; the old direct tests don't provide any additional value, and should be removed because they are a maintenance burden. Indirection enables you to achieve both decoupling and expressiveness.

Are there general design pattern or rules that can help writing code that decouples test from production code?

Uncle Bob's green diagram on the right would be and example of the facade pattern because it hides multiple, complicated services behind a single interface.

To clarify one thing for junior developers do not read Uncle Bob's UML diagram literally. Services implement the facade in a loose sense.

See below example to see how application services implement the test facade. Also to see the difference/relationship between and the application facade and the test facade (as per @jonrsharpe's comment). A tip can be: write your test facade such that it reads like the stories you will demo, or the scenarios in your test plan etc. :

class SomeTestClass
{
    public void users_cant_register_without_providing_contact_info()
    {
        failsWithMissingContactInfoErrorMessage( 
            () -> user.sendsRegistrationFormWithMissingContactInfo());

        admin.doesntSeeInRegisteredUsers(user);
    }

    private failsWithMissingContactInfoErrorMessage(Runnable r)
    {
        failsWithErrorMentioning(r, MissingContactInfo.class, "missing contact");
    }
}

class UserFacade // Can be named after roles
{
    CommandInterface handler;
    ViewInterface view;

    String enteredEmail;
    String enteredPhoneNumber;

    void sendsRegistrationFormWithMissingContactInfo()
    {
        fillsTheForm();
        leavesEmailBlank();
        leavesPhoneNumberBlank();
        sendsRegistrationForm();
    }

    void leavesEmailBlank()
    {
        this.enteredEmail = "";
    }

    void leavesPhoneNumberBlank()
    {
        this.enteredPhoneNumber = "";
    }

    void entersAValidEmail()
    {
        this.enteredEmail = "valid@example.com";
    }

    void entersAValidPhoneNumber()
    {
        this.enteredPhoneNumber = "+1-541-754-3010";
    }

    void entersAnInvalidPhoneNumber()
    {
        this.enteredPhoneNumber = "##123456**";
    }

    void sendsRegistrationForm()
    {
        handler.handle(new RegisterNewUser(enteredEmail, enteredPhoneNumber));
    }

}

class AdminFacade
{
    BackEnd backEnd;

    void doesntSeeInRegisteredUsers(UserFacade user)
    {
        // ....
    }
}
Related Topic