📜 ⬆️ ⬇️

test.it - ​​javascript testing or my bike with nesting and detailed output

Attention! The article contains examples of work before the release (up to v1.0.0) version of the library.
Soon there will be another article. This is for reference only. All necessary information for using the library can be obtained in the README and comments in the code.

Picture to attract attention:
test.it habrahabr
I am a novice web developer. And not so long ago I wanted to learn how to work in the way real programmers do.
By this I understood 3 main elements:
  1. Using a version control system.
  2. Competent commenting code.
  3. TDD or at least a simple unit testing code.

For the first I had to learn the basics of git , and create my first repository on github . For the second, I chose JsDoc , because of which I had to move from notepad ++ to sublime text (only there was a corresponding plugin).
But with the third, unexpectedly for me, serious difficulties arose.


Since I really respect jQuery (by the way, I opened my first repository for writing a plugin for jq), the choice of a framework for unit testing fell on Qunit, which turned out to be a fatal mistake . And faced with the following:

I really didn’t like all this, but the realization that big uncles and aunts worked like that did not let me give up. I hoped that when I completed the development of my plug-in, I would discover some sacred meaning of all that I had to face during its testing. For a week I prickled, cried, but continued to eat cactus , fought with a severe form of the disease I-know-how-to . But my nerves are not iron and, in the end, I burst into tears like a little girl gave up and wrote my bicycle.
')

And now about him in more detail


test.it is a framework for testing JavaScript code.
For those who are not interested in the text - link to the repository on github: test.it
Key Features:
Last I looked at Qunit, in this they, of course, great.

Currently implemented tests for:


Simple start


In order to connect the framework - just add the line
<script src='path/to/testit.js'></script> 
wherever you want at the end of the <body> tag .

And you can start using, obviously, with the first test:
 test.it('first test'); 

A test is a function (method of the test object) that checks the fulfillment of a condition.
The function test.it ( entity ) checks the existence of an entity and the non-falsity of its value.
Opening the console (Firebug or Chrome, as having maximum support for this API ) we will see the following:
empty console
Not much (:
And all because we did not complete the test call test.done () . Let's do it. Now our code looks like this:
 test.it('first test'); test.done(); 
And in the console, respectively:
root - pass
Which means that all tests passed.

root is the root element, or a zero level test group. He has the same properties as any other group, but they will be more detailed later.
If we reveal the root element, we will see the statistics of the passage of all tests inside it, and the time of their execution.
root
It's obvious that:
Statistics is divided into tests and groups. But for sure in the next releases it will be simplified, and this separation will disappear.

The last line: pass : no comment is our test. Expand it and see more:
first test
The first step is the label that the test is passed. In general, they are of three types:
Further, no comment is a comment that we have not yet specified. Below we look at how this can be done.
The next line “argument exist and not false” is a description of what is necessary for passing the test.
And finally, an array of received arguments, in our case from one element ″ first test ″

Not to see this unpleasant no comment anymore, let's add a comment to our test.
 test.it('first test'); test.comment(' '); test.done(); 
Now the result looks like this:
simple check

But everything that we just wrote, in principle, did not really test anything. Let's fix the situation and add a real test.
 test.it('first test'); test.comment(' '); var Me = {name:'Titulus',lastName:'Desiderio'}; test.it(Me); test.comment(' ?'); test.done(); 
Now in the console:
2 tests

We can consider the second test in more detail. Expand it, and Object is the only argument in the array of arguments.
2 test
As you can see, the test only checks the existence and non-falsity of the value of the only argument passed to it. But this does not interfere with sometimes prying into this object, so that once again remind ourselves of exactly what we transmitted, and get additional, useful in some cases, information.

The previous tests were successfully passed, let's set the task more complicated.
 test.it('first test'); test.comment(' '); var Me = {name:'Titulus',lastName:'Desiderio'}; test.it(Me); test.comment(' ?'); test.it(Me.habr); test.comment('?'); test.done(); 
fail
As you noticed, the failed test and the corresponding failed group (root) were disclosed by default.
By the way, thanks to an array of arguments, we immediately see why the test was failed - the argument passed is not defined.

Now it remains to correct the situation:
 test.it('first test'); test.comment(' '); var Me = {name:'Titulus',lastName:'Desiderio'}; test.it(Me); test.comment(' ?'); Me.habr = '!'; test.it(Me.habr); test.comment('?'); test.done(); 
And again the old picture!
root - pass

Let's complicate the task. Instead of checking for existence and non-falsity, check for the correctness of the value.
 test.it('first test'); test.comment(' '); var Me = {name:'Titulus',lastName:'Desiderio'}; test.it(Me); test.comment(' ?'); Me.habr = '!'; test.it(Me.habr); test.comment('?'); test.it(Me.habr,'habrahabr.ru'); test.comment(' '); test.done(); 
The test.it function ( entity1, entity2 ) - checks the equality between entity1 and entity2 .
Let's look at the console:
Habrahabr!  !  = habrahabr.ru
The test failed, which was to be expected. Looking at the result, the reason for the failure is quite obvious. Having corrected
 Me.habr = '!'; 
on
 Me.habr = 'habrahabr.ru'; 
We get again
root - pass

Need to go deeper


We will understand in the previously mentioned groups.
Group -

Consider the following code:
 test.it(2>1); test.comment('    ?'); test.group(' ',function(){ test.it(2>1); test.comment(' ?'); }); test.done(); 
With test.it (2> 1); and so everything is clear, but what does test.group do?
Function test.group ( groupname , fun ) - creates a new subgroup for tests, other groups and other code. The name is taken from the groupname argument. And the fun function will try to execute, and if an error occurs inside it, it will not interrupt the rest of the code. The error will be placed in the error field of this group.

Open root :
Group
Here it is our group, the first group , framed as root , only the name is what we set.
Open and her.
first group
There are no special differences from root and should not be.
Here it is worth paying attention to statistics in root and in our group.
! Important point: the statistics displays only the results at this level. Suppose we have 2 tests written, but the root in the test statistics shows only one passed.

Add there a couple of tests.
 test.it(2>1); test.comment('    ?'); test.group(' ',function(){ test.it(2>1); test.comment(' ?'); test.it(1, Number(1)); test.comment('    '); test.it(habr); test.comment('      ?'); test.it(2+2,4); test.comment('    '); }); test.it(1<2); test.comment(' ?'); test.done(); 
And we will see
mistake
that the test for 2 + 2 = 4 was not even launched, because the previous one with habr was not performed due to a ReferenceError error, which was carefully derived from the statistics, prior to the tests. But at the same time, the last test for 1 <2 passes, because he was out of the group, with an error.

Error handling is one of the priorities that I set for myself today. So do not be surprised if this example loses its relevance after the next releases. But the basic idea of ​​catching errors without crashing the program will remain.

And the last point regarding groups. Layered nesting!
need to go deeper
 test.group('need',function(){ test.group('to',function(){ test.group('go',function(){ test.group('deeper',function(){ test.it('bye habr'); test.comment('bye bye'); }); }); }); }); test.done(); 
layered nesting

Under the hood


All code is available on github , so you can read it, comment, fork, offer pullrequest, etc. MIT license , although I am thinking about switching to the WTFPL .

I wanted to dwell on test.root - an object corresponding to a zero level group. That it is filled with tests, and then _printConsole () its parsit and brings all this beauty.
test.root for the first group example:
 { "type": "group", "name": "root", "status": "pass", "time": 7, "result": { "tests": { "passed": 1, "failed": 0, "error": 0, "total": 1 }, "groups": { "passed": 1, "failed": 0, "error": 0, "total": 1 } }, "stack": [ { "type": "test", "status": "pass", "comment": "    ?", "description": "argument exist and not false", "time": 0, "entity": [ true ] }, { "type": "group", "name": " ", "status": "pass", "time": 1, "result": { "tests": { "passed": 1, "failed": 0, "error": 0, "total": 1 }, "groups": { "passed": 0, "failed": 0, "error": 0, "total": 0 } }, "stack": [ { "type": "test", "status": "pass", "comment": " ?", "description": "argument exist and not false", "time": 0, "entity": [ true ] } ] } ] } 

By the way, yes. I am very ashamed, but there is a part of third-party code - the deepCompare () function is not accessible from the outside. It compares the two arguments of any type. Took it here .

And thanks a lot to @mkharitonov for the help in implementing multi-level nesting.

but on the other hand


Of course, there are drawbacks. Some serious, some not so much. I hope, once the code opensource, the community will help me to minimize them, or turn them into advantages.
Key:


What's next?


And then there will be the above-mentioned changes in the output of statistics.
Improved handling and output errors.
Improve test output - numbering and hints for failed tests will be added.
New tests will be added test.them , test.type , test.types , test.time . You can read more about them in the README .
The deficiencies mentioned in the previous section will be corrected.
And, of course, optimization.

Once again the link to the repository on github: test.it

PS The post will be transferred to the hub “I am Piarusya”, as soon as I gather enough karma.

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


All Articles