3

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?

2 Answers 2

1

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 = "[email protected]";
    }

    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)
    {
        // ....
    }
}
12
  • In your code sample, the sendsxtInfo() method is made public, and the four methods within it could be private (ideally, if it is possible), or public, so once the test code calls sendsxtInfo(), it also TESTs all four methods. Is this the none one-to-one correspondence Uncle Bob is talking about. Is it the only reason? That is basics and what we are practising. But I think other methods like the sendxInfo() method that are public have one-to-one correspondence, except that it is called by other methods, which is tested indirectly.
    – Pingpong
    Commented Jul 22, 2017 at 10:31
  • I don't think this is a facade pattern like used to expose system services, but is a bit similar.
    – Pingpong
    Commented Jul 22, 2017 at 10:31
  • users_cant_register_without_providing_contact_info does not test the methods of the facade. It tests one property of the Register New User use case. I have left out access modifiers s.a. public/private because they would be just noise here. UserFacade/AdminFacade etc classes form a test facade so you need not directly work with the application interface. CommandInterface, BackEnd etc are probably facades themselves. For instance CommandInterface.handle(RegisterNewUser) probably delegates to sthg like RegistirationService.Register(newUser) etc. Commented Jul 24, 2017 at 5:56
  • Sorry, I am lost here. Do you mean UserFacade and AdminFacade are test code, not production code? Another question is that, the users_x_info() method is calling sendxInfo() method in UserFacade class, which is testing UserFacade class. Is it correct?
    – Pingpong
    Commented Jul 24, 2017 at 18:54
  • 1
    6. Yes. RegisterNewUser would correspond to a request model in Uncle Bob's clean architecture: in this diagram CommandInterface, ViewInterface would correspond to boundary interfaces on the same diagram. The following books go beyond the usual toy cases: book 1 book 2. Commented Jul 28, 2017 at 10:30
1

It is hinted on in the article, but not clearly explained.

The core idea here is that code changes. So if you follow ideology that tests should have one-on-one structure to the code it is testing, then if structure of the code changes, so needs structure of tests.

But that is exactly what you don't want. You want to be able to rename methods or extract classes without worrying about tests and have assurance that tests will report if the change didn't break anything. And changing the tests increases risks that those changes would break the tests. Also, having to change the tests along with code needs more time.

In my experience, the best way is to build a wrapper or facade around the tested code. This facade makes the tests more clearly describe intent and makes it easy to make structural changes to the code under test without major changes to the tests themselves. Only change is really needed within the wrapper.

Example that is close to what I have in mind are BDD tests. The wrapper around the code provides the text based API so tests are easier to write and understand, and usually this text-based API doesn't expose the structure of the implementation. But the wrapper doesn't have to go as far as providing BDD-styled text API. It can use simple class-based "domain specific language" that is using the language's features instead of BDD test framework.

7
  • It's not clear from your answer, do you suggest implementing facade exclusively for tests?
    – Oleksii M
    Commented Jul 20, 2017 at 10:19
  • @AlexeyMilogradov Yes, exactly.
    – Euphoric
    Commented Jul 20, 2017 at 10:57
  • 1
    Agree with the first three paragraphs, but why is it better to change the wrapper than the tests? If interacting with the code under test is insufficiently intentful, maybe that's a problem with the code, and hiding it isn't helping. What about the code's other collaborators, would they be clearer if using the wrapper? It sounds like sensible implementation hiding, but outside the production code. Do you have any examples?
    – jonrsharpe
    Commented Jul 20, 2017 at 13:18
  • @jonrsharpe It is better to change SINGLE wrapper than DOZENS of tests. I would say the main idea is that class can be interacted drastically differently when used in production than when used in test. I did have few experiences on my project with this kind of approach, but I just can't, right now, imagine a good, illustrative, example. The real issue for me here is that it really starts making sense if you have complex code with lots of complex tests. Can't really show it on simple example.
    – Euphoric
    Commented Jul 20, 2017 at 14:42
  • @Euphoric Does your wrapper/facade's public methods still have one-to-one correspondence with test code? Facade is normally a service layer used for external party, which is only part of an app. The rest of an app cannot all use the same wrapper/facade, e.g. domain model, app layer.
    – Pingpong
    Commented Jul 20, 2017 at 23:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.