During the next review of Thick Pull Request, I came across Unit Test's with incorrectly naming test cases. The discussion of the wording in the test cases turned out to be similar to the conversation of Janicara and Legkostupov in the movie "72 meters" ("if I was so lucid at school ..."). In the conversation, the thought was sounded that in Russian-speaking resources it is difficult to find an intelligent guide precisely by textual formulations. I decided to look for myself in Russian (usually I use only English-language sources). On Habré, I found several manuals about unit tests, but they all bypass the details of the wording in the test cases. Under the cut, my attempt to fill this gap.
There is a chance that I was looking bad / too diagonally read. Here is an example of how the topic of this article is covered in those articles that caught my eye.
At the request of colleagues who are not comfortable reading manuals in English, I decided to translate and compile English manuals.
The basis for the article took these two materials:
I also have to note that in some examples of tests I had to do a partial translation into Russian. The wording in the blocks "describe" deliberately remain in English, because most likely they will contain the name of functions, JS modules or other entities in the code, but the text is already translated in read-only blocks in "it" blocks.
My personal preference is that everything in the code should be in English.
The name of the test should describe its purpose as briefly and explicitly as possible. The name and description of the test is the first thing that should indicate the cause of the fault. The test result in the console must be read correctly in terms of grammar. Third-party developers should not solve rebuses in their heads, trying to guess what the test author was thinking. Tests are part of the program documentation and must also be written correctly.
BAD example:
describe('discoveryService => initDiscoveries', () => { it(' discoveries ( , ..)', () => { // ... }); }); describe('MyGallery', () => { it('init ( , - )', () => { }); // ... });
From the examples above, it is difficult to understand what specific action (actions) is performed and to which specific result the action should lead.
GOOD EXAMPLE:
describe('discoveryService => initDiscoveries', () => { it(' discoveries', () => { // ... }); it(' discoveries', () => { // ... }); }); describe(' Gallery', () => { it(' ', () => { }); it(' ', () => { }); // ... });
Note trans. # 1: note, the block of text in it begins with a lowercase, because is a continuation of the sentence that began in descibe .
Note trans. # 2: in the examples above, "discoveryService => initDiscoveries" is more correct to break into two descibe blocks (one is nested in the other).
Note trans. # 3: note, in the examples about discovery above there is no second part of the description of the test case; it implies the text of the form "when it is called", which is not very good in terms of clarity; in simple cases, copy-paste "when it is called" is not particularly profitable, IMHO.
The description of elementary work (Unit of Work, UoW) is usually placed in the describe block. The statement in the it block should continue the pattern " unit of work - scenario / context - expected behavior ", which began in the describe:[ ] [ / ] ( | ) [ ]
or in the form of code:
describe('[unit of work]', () => { it(' [ ] / [/]', () => { }); });
If several test groups follow the same scenario or fit into the same context, then you can use describe nested blocks.
describe('[unit of work]', () => { describe('// [scenario/context]', () => { it('/ [expected behaviour]', () => { }); }); }); describe(' Gallery', () => { describe(' ', () => { it(' ', () => { }); it(' ', () => { }); }); // ... });
Each test should focus on one specific scenario in the operation of the application. A test responsible for one particular aspect is able to identify the specific cause of the fault. The more specific the test, the less likely it is that there may be several reasons for incorrect behavior. Try to place only one expect block in one block.
BAD example:
describe('isUndefined function', ()=> { it(' true or false undefined', () => { expect(isUndefined(undefined)).toEqual(true); expect(isUndefined(true)).toEqual(false); }); });
The it block contains two expect blocks. This means that the developer, seeing a negative result of this test, will not be able to determine exactly what is specific in his code incorrectly and how to fix it.
GOOD EXAMPLE:
describe('isUndefined function', ()=> { it(' true, undefined', () => { expect(isUndefined(undefined)).toEqual(true); }); it(' false ', () => { expect(isUndefined(true)).toEqual(false); }); });
Each test in the example above evaluates one particular problem. In addition, the test description clearly describes in which case it will be passed. In both cases in the console, the developer will read in the form of a list which results under what actions / conditions are expected from the user functionality being tested.
Look at the picture, do not look at the strokes. Test custom script / behavior, not implementation details. Then changing the details of the implementation will not affect the test results. A negative test result should indicate whether the program behaves correctly or not from the user's point of view. The test should not control / limit implementation details.
BAD example:
it(' discovery ', () => { discoveriesCache.addDiscovery('57463', 'John'); expect(discoveriesCache._discoveries[0].id).toBe('57463'); expect(discoveriesCache._discoveries[0].name).toBe('John'); });
What is bad here? First, the two blocks expect , but this is not important. Secondly, it is not the behavior that is tested, but implementation details. The implementation details will change (private fields are renamed) - the test will become invalid and will need to be rewritten.
GOOD EXAMPLE:
it(' discovery ', () => { discoveriesCache.addDiscovery('57463', 'John'); expect(discoveriesCache.isDiscoveryExist('57463', 'John')).toBe(true); });
This example tests the public API, which should be as stable as possible.
"Onegin was a pedant ..." I get the impression that most developers are not paying enough attention to the accuracy and readability of test names. I often observe quite lengthy discussions of the form “What does this code do” or “Why does this code do”. This applies to both the main code in JS (unclear, fuzzy names of modules, services, functions, and variables) and tests (fuzzy cases, testing implementation details, fuzzy descriptions). All this leads to the fact that the code does not exactly what is expected.
In one of his interviews, David Heinemeier Hansson (creator of the Rails framework) said something like the following:
"Unit tests only show what your program does in the expected way%: o."
He meant that it is necessary to test the behavior, not the code units. And textual formulations should have a behavioral pattern. Those. "Essence A must behave this way under such and such conditions." A chain of the type describe [- describe ] - it - expect should turn into such a folding formulation.
Thanks for attention!
Source: https://habr.com/ru/post/454884/
All Articles