TDD and Encapsulation Priority Conflict

I just started practicing TDD in my projects. I am developing a project using php / zend / mysql and phpunit / dbunit for testing. I was just a little distracted by the idea of ​​encapsulation and a test-based approach. My idea of ​​encapsulation is to hide access to several functionalities. To make it more understandable, private and protected functions are not directly tested (unless you create a public function to call it).

So, I end up turning some private and protected functions into public functions in order to test them. I really violate the principles of encapsulation to give way to checking microfunctions. Is it correct?

+4
source share
3 answers

TDD circles have a pretty standard answer. If a class has functionality that you both want to hide and test directly, you must break through the class with this functionality. This is a great example of how TDD enhances your design.

In the original class, this extraneous functionality disappeared, wrapped in a sprouted class, so the design of the source class is simpler and better corresponds to the Single Responsibility Principle . In the sprouted class, the extracted functionality is its raison d'etre, so it must be publicly available for it, and therefore it can be tested without modification for testing purposes only.

+8
source

Regarding Karl Manaster’s exact answer, there are some flaws that you should at least consider before embarking on the path suggested by Karl.

The most significant of these is the following: we use encapsulation to minimize the number of potential dependencies that carry the greatest likelihood of changes propagating. In your case, you have encapsulated private methods inside your class: they are not available for other classes and, therefore, there are no potential dependencies on them: the cost of any changes you make to them is minimized and has a low probability of propagating to other classes.

It seems that Karl suggests moving some private methods from your class to a new class and making these methods public (so you can test them). (By the way, why not just make them public in the original class?)

Thus, you remove the barrier to dependencies of other classes, forming dependencies on these methods, which will potentially increase the cost of tracking these methods if any other class takes them to use.

You can judge this small secondary and worthwhile price to pay for the opportunity to test your personal methods, but at least know about it. In a small number of cases, this can really be useful, but if you implement it in your entire code base, you will sharply increase the likelihood that these dependencies will form, increasing the cost of your service cycle to an unknown degree.

For these reasons, I disagree with Karl that his suggestion is: "... a great example of how TDD improves your design."

In addition, he states: "In the original class, this extraneous functionality disappeared, wrapped in a sprouted class, so the design of the original class is simpler and better in line with the principle of shared responsibility."

I would say that roaming functionality is not at all "external." In addition, "Simpler" is not clearly defined: of course, it may be that the simplicity of a class is inversely proportional to its size, but this does not mean that the system of the simplest possible classes will be the simplest possible system: if in this case all classes will contain only one method, and the system will have a huge number of classes; removing this hierarchical layer from several methods within classes, one could argue, would make the system much more complicated.

The principle of single responsibility (SRP), in addition, is, as you know, subjective and completely depends on the level of abstraction of the observer. Not at all that removing a method from a class automatically improves its SRP compliance. A printer class with 10 methods has sole responsibility for printing at the class abstraction level. One of its methods may be checkPrinterConnected (), and may be checkPaper (); at the method level, these are clearly separate responsibilities, but they do not automatically assume that the class should be split into other classes.

Carl concludes: "In the sprouted class, the extracted functionality is raison d'etre, so it should be publicly available, and therefore it can be tested without modifications for testing purposes only." The value of functionality (this is raison-detre-ness) is not a basis for the relevance of its publicity. The basis for the acceptability of public functionality is to minimize the interface open to the client, so that the functionality of the class is available for use, while the independence of the client from the implementation of the functionality is maximized. Of course, if you only move one method to a sprouted class, then it should be publicly available. However, if you are moving multiple methods, you must make these methods publicly available, which is important for a client to successfully use the class: these public methods can be much less important than some private methods from which you want to protect your client. (In any case, I am not a fan of this, "Raison-d'etre", the phrase as the importance of the method is also not defined.)

An alternative approach to Karl suggests that it depends on how big your system is for growth. If it grows to less than a few thousand classes, you might think of a script copying the source code to a new directory, changing all the events, "private" to, "public" in that copied source, and then write your tests against the copied source . This has the lack of time required to copy the code, but the advantage of preserving the encapsulation of the original source, but all methods are checked in the copied version.

Below is the script used for this purpose.

Hi,

Ed kirvan

! / Bin / bash

rm -rf code-copy

echo Creating a copy code ...

mkdir code-copy

cp -r ../ www code-copy /

for I'm in find code-copy -name "*php" -follow ; to do

 sed -i 's/private/public/g' $i 

to do

php run_tests.php

+7
source

I just read a great article on how to let you pretend you're creating a design:

http://www.mockobjects.com/files/usingmocksandtests.pdf

When Carl says: “You have to germinate a class with this functionality,” the author of this article will explain how your tests can help you using mock objects, how you can create your class so that you 1) do not have to worry about what you don’t You can test private details and, more importantly, 2) how it will improve your design (I will rephrase Karl’s quote), opening co-authors and roles with the right responsibility.

The author leads you step by step to make it very understandable.

Here's another article with the same approach:

http://www.methodsandtools.com/archive/archive.php?id=90

Quote:

Many who start the fight against TDD gain control over dependencies. To check the object, you perform some and then check whether the object is in the expected state. Because OO design focuses on behavior, the state of an object is usually hidden (encapsulated). It may be possible to check whether the object is conducting as expected, sometimes you need access to the internal state and introduce special methods for this state, like a getter method or a property that retrieves the internal state.

In addition to unwilling objects to clutter up their interfaces and exposing them to private parts, we do not want to introduce unnecessary dependencies with such additional getters. Our tests will become too rigid focused on the details.

The UK agile software team was also struggling with this in 1999. They had to add additional getter methods to check the state of objects. Their manager did not like all this encapsulation violation and announced: I do not want the code! (Mackinnon et al., 2000 & Freeman et al., 2004)

The team came up with the idea of ​​focusing on interactions, not the state. They created a special facility to replace employee-controlled facilities. These are special objects that contain specifications for expected method calls. They called these objects mock objects or mock them for short. The initial ideas were clarified, resulting in several mock objects for all common programming languages: Java (jMock, EasyMock, Mockito), .NET (NMock, RhinoMocks), Python (PythonMock, Mock.py, Ruby (Mocha, RSpec), C ++ (mockpp, amop) See www.mockobjects.com for more information and links.

+3
source

Source: https://habr.com/ru/post/1306612/


All Articles