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:

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:
- Using a version control system.
- Competent commenting code.
- 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:
- The main layout gets lost due to the fact that for Qunit to work you need to insert a rather big piece of HTML code into the page. And all test results are displayed there. Yes, this can be fixed with crutches with position: absolute , opacity: .5 and the like, but I don't want to fight the framework, I want to use it
- Tests are ambiguous . I repeatedly came across a case when, by pressing F5, I received a different number of passed and failed tests. And I have neither asynchronous code, nor work with cookies, nor Ajax requests. If I understand correctly, this is due to the killer feature changes in the sequence of tests, depending on the previous result - failed tests are checked first.
- Fail tests are extremely unattractive . qUnit almost always prints toString () one of the passed arguments, ignoring the second. Occasionally it outputs the second one, and there are situations when the diff will work correctly. I understand that diff is a very complicated function, and you should be glad that it at least sometimes works, but what prevents Qunit from displaying arguments for every fail is a mystery to me.
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.itKey Features:
- The console is used to display the result.
- The sequence of tests does not change.
- The results of all tests include an array of arguments received.
- Supports grouping tests without restrictions on the level of nesting.
- If an error occurs within a group (for example, in one of the tests), it is caught and displayed as a result, without disrupting the rest of the code.
Last I looked at Qunit, in this they, of course, great.Currently implemented tests for:
- Equality of two arguments. Analogue ok from Qunit .
- The non-false result of the argument (not NaN, Null, undefined, 0, false, [], '') in other words - the passage of if () . Close analogue of equal from Qunit .
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:

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:

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.

It's obvious that:
- pass - the number of passed
- fail - number of failed
- error - the number of failed execution
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:

The first step is the label that the test is passed. In general, they are of three types:
- pass - passed
- fail - failed
- error - ended with an error that did not cause the rest of the code to fail
(for example, 0 or> 2 arguments were passed)
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:

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:

We can consider the second test in more detail. Expand it, and
Object is the only argument in the array of arguments.

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();

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!

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:

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

Need to go deeper
We will understand in the previously mentioned groups.
Group -
- a set of tests (or groups);
- the test object method that this set creates;
- this set itself is in the form of a JavaScript object.
In addition to the array of tests and groups, it carries the statistics of passed tests, failed and caused an error tests (or groups), as well as the time spent on their execution.
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 :

Here it is our group, the
first group , framed as
root , only the name is what we set.
Open and her.

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

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!
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();

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:
- Not cross-browser. The Console API only supports Google Chrome (and others based on chromium) and the Firebug plugin for firefox. They say more Safari, but I do not have Apple devices to check.
- There is no possibility to run separately one group of tests.
There is a crutch - to put test.done () not at the end of the whole code, but at the end of the group that needs to be tested. But this is a crutch, and it does not have all the necessary functionality.
solved
- At the moment there is no diff, and this is solved .
- There are no tests for ajax, and this again can be solved .
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.itPS The post will be transferred to the hub “I am Piarusya”, as soon as I gather enough karma.