Is it possible to wet the object under test?

Yes:

  • Class to test

      class Message extends Data
      {
         protected $text = '';
    
         public function check()
         {
            return empty($this->text);
         }
    
         public function setText($text)
         {
            if ($this->check() === true) {
               $this->text = $text;
            } else {
               $this->text .= $text;
            }
         }
      }
    
  • Developers Vasya and Petya , who are arguing how to write a test for setText correctly, do you need to use the check method or not?

Vasya: for mock check

During unit testing, you need to do everything possible except for hidden data (private, protected) to test all scenarios that do not depend on each other. In total, there will be 3 scenarios, where the IOC check returns either true, false or otherwise, for the setText method. And test the check method separately. Also forget the component test, which will test the object (Message) in general, that is, the interface of this object, without touching anything that concerns Message and Data.

Petya: against mock check

You can't mix methods of the class under test (Message) and its parents (Data), only external classes are allowed. If you change the check method, setText will actually break, but the unit will not report it. Themselves the units will show how the class works, it is unnecessary to do component tests on only one object, but the tests themselves will duplicate the check of the same internal dependencies (the check method will be tested 2 times)

Question:

Which of them is right, or how to test correctly?

Noted:

As usual in my questions, this is a training question. There is no problem with the specific code, this is just an example (it is better to understand the examples)

Author: Kyubey, 2015-11-20

3 answers

For the idea of moking the test class, I would immediately shoot. The class under test is a single entity, and should be tested as a single entity. By crawling your dirty hands into the check method, you are assuming a concrete implementation of the setText method. This is not a unit test, this is profanity: you are not testing the external interface of the class, but checking that the operations in it are written exactly the same as in the test.

Where the class interface says that setText should call check, and not cache the state to some variable? Now you can't change the implementation while maintaining the interface and behavior without breaking the unit tests. So why do unit tests that break down from changing the internal implementation? They will only annoy you.

But if some methods of the tested class will be difficult to test (they will climb on external resources, for example), then you can already think about mockups. But in this case, most likely, the logic of climbing on external resources should be allocated to a separate class and wet it already.


I generally support the ideas of Martin Fowler: you only need to wet what is too difficult not to wet: databases, mail distribution, etc. This position he calls " classical TDD ".

Every time you stick an ioc into the code you're testing, you're spoofing the actual behavior, you're assuming the implementation, you're simulating the behavior, and you're making mistakes. Your code is not testing a real system, but a simulation.

Yes, tests with a minimum of mocs break more often armfuls, not singly. But they break down! And if they break, you can immediately see that there is a real error somewhere. If the tests test the implementation, they break one by one, but common errors for several classes are not caught, and failed tests often simply indicate a change in the implementation.

"Mockers" like to produce miniature unit tests for each getter and setter, but they forget that classes do not exist in an airless space, but interact. As a result, everyone has the class coverage is 99%, and together for some reason everything breaks down. We must not forget about integration tests. And unit tests that are "slightly integrative" are a great way to go.

Don't be afraid to call a method from a dozen different places: the more real calls, the more confidence you have in the reliability of the system.


As for your code specifically, your method looks like a setter, but in fact it is not. This is a test failure before writing any tests.

 4
Author: Kyubey, 2015-11-20 16:19:45

In this particular case, I would change the interface. The setText method looks like a normal setter, but it's not really a setter.

It looks very much like it should be broken down into methods clearText and appendText, and take out the logic hidden behind check above.

Vasya's dispute with Petya indirectly indicates problems in the project. Petit's position is stronger (you can't mix the methods of the class under test), because it follows the decomposition rules described in Uncle Bob Martin's book "Clean code".

In short: methods of the same logical level should be located on the same decomposition layer, such code is much easier to read. In your example, it looks like check should be located somewhere higher, where it should be tested. And there you can create a class Message, which is one level lower.

And here, at the Message level, we can test the clearText and appendText methods, possibly by passing some classes below.

P.S. I note that in real projects, there is no it is always possible to correctly position entities by levels, so Vasya and Petya risk discussing this issue until they are hoarse.

P. P. S. Noticed that we are talking about mocks. I follow this rule whenever possible: first, test directly, if it does not work, and you can not reverse engineer, use the stub, if it does not work, use the ioc. Thus, simple validating or transforming methods that work with standard library objects (strings, dates, regular expressions) flow into utility classes, where they are tested.

 2
Author: Mark Shevchenko, 2015-11-21 11:59:42

Your class doesn't have a full API. Add the getText method and test the behavior comprehensively.

Alternatively, you can use the following tests:

  1. On a clean object, make sure that check returns false.
  2. On a clean object, make sure that getText returns ''.
  3. After setText, the check method returns true.
  4. After setText, the getText method returns the correct string.
  5. After repeated setText, the getText method returns the correct string.

A small digression:

Some methods are very difficult to test in isolation from each other, and this is normal. A typical example is a container:

class Container {
    private $data;

    public function get(key) {/* ... */}

    public function set(key, value) {/* ... */}
}

The get and set methods simply cannot be tested separately from each other. In such cases, it makes sense to test the behavior of the object (or a group of methods).

In addition, it is more correct to approach the methods as small "black boxes". Tests in neither in no case should they depend on the implementation of the method. In your example, if you replace the check call with something else, your tests will break, although the behavior of the method will remain the same.

Testing should not be an end in itself. Therefore, it is wrong to use some clever techniques (moki of the object under test, inheritance, in order to disclose the structure, etc.) in most cases. Tests are just a tool to ensure that the code works.

 1
Author: Dmitriy Simushev, 2015-11-20 12:10:30