test.it - not again, but again
- Tutorial
Good afternoon, Habr. An eternity week has passed
after my article on test.it. And as I did not plan to extend this period for at least a month, but the time has come for a new publication. A picture to attract attention: Since that time, the library (thanks in large part to the Habrachians) has gained new functionality. And since the syntax given in the previous article does not fully work in the current version, I have no right to postpone this article for another 3 weeks. Who does not like a lot of words - A site where you can see the code in action, GitHub , Wiki Chain calls have appeared
and a new nesting mechanism
A new way to display errors
AND two additional methods test.typeof () and test.trace () .
And also 3 Wiki pages.
So. Let’s take a look at the example given in the wiki :
Before we start the tests, we will use the test.typeof () method .
It can distinguish between: Array , Boolean , Date , Error ( EvalError , RangeError , ReferenceError , SyntaxError , TypeError , UriError ), Function , NaN and Number , Object , RegExp , String , Window , HTML , NodeList . He also defines an empty variable as 'undefined'but unlike the standard typeof, it cannot receive an undeclared variable as an argument. 'Undefined' will answer for that non-existent property of the declared and non-empty object. But this is the specificity of the language.
If we refresh the page, a line will appear in the console:
Now take a look at the test.trace () method .
Actually, this is not real trace () (which, unfortunately, is not available in JavaScript) , because all references to calls inside the library were cut out of it.
The output to the console will now be added:
Hereinafter, the insignificant parts of the console output in the screenshots will be omitted so as not to increase the size of the images meaninglessly.
First, create an object for tests, a variable with an integer value and an empty variable.
Let's go further.
The next step is the first test.
Everything is as before.
test.done () - completes the tests and displays the result in the console.
Further it will be assumed that test.done () is the last line of our code. In the examples I will omit it.
In the console we see:
Where:
If you expand this group, you can see a list of tests / groups in it.
Before disassembling our only test, let's reveal it:
And so:
The most basic case is adding a comment:
This code could be written as:
Now root (opened automatically because it contains an unsuccessful test) looks like this:
In the counters in the first line, you can notice an increase in the second number from 0 to 1, which means an increase in the number of failed tests / groups.
Let us turn our attention to the test opened by default (because it was not passed) .
It differs from the previous one only in the fail status - meaning that the test failed, and the comment “i badly taught algebra at school”, which we added.
Obviously in test.it (value)You can pass not only strings, but also variables, and expressions, and function calls, etc. In principle, whatever we pass, first it will be completed, the result will be obtained, and this result will already go to the test. That is JavaScript.
We will check what has just been said. Test the expression:
Until we have left for the jungle of chain calls, and we will definitely leave, look at another test.it () test variant .
Let's compare the previously declared variable with a different value.
In the console, these 2 tests will look like this:
Nothing unusual, but you should pay attention to the description of the first (failed) test. " arguments has different types " - this text contains a hint explaining why the test failed - the arguments passed are of different types.
Now let's try more complex chains.
Let's do some action, depending on the result of the test.
In this code, we check Family for non-falsity, and depending on the result, we display different phrases in the console.
The console output now looks like this:
True, it is preferable to perform such a task using another chain call:
As a result, we see in the console:
This design is preferable because it does not end the chain.
Now create the first group.
But since there were passed tests, the group is created empty, but with the pass status - because it does not have a single failed test.
Before taking a screenshot, I opened the group - this can be seen by turning the arrow down, and two gray pixels, signifying its end. But since the group is empty, it looks almost like a closed one.
Well. We proceed to more meaningful actions. Create a group with two tests inside and a comment.
Here it is only worth paying attention to the comment added by us unite! - in the group header.
Now let's check the feint with our ears, and add a few tests to the already created group. And not just tests, but tests that initiate new tests depending on their result.
Add the following code:
Do you see the title? 3 passed, 2 failed - only 5.
It's time to take a look at a couple of unusual tests. To begin with, in the “here comes strange tests” group, create the following two tests:
test.types (values [, type]) - like test.them (values) it checks the elements of the array passed by the first argument. But it checks that they match types, and if the second argument of type is passed , it takes this type as a sample.
This test has a simplified analogue, but about it a little later.
This is how they look in the console:
I opened the tests along with their arrays of arguments for clarity, but something seems to me, the clarity of this only decreased.
And here is another chain magic:
Let me explain - the first test, I checked the value of Family.members for non-falsity. Since this is an array of two elements, the test is passed.
arguments () [0] == Family.members . Therefore, the numberOfMembers variable contains the number of elements in the Family.members array , that is 2. The second test fails due to the fact that 2 is not more than 5.
Do you still remember that we are in the " here comes strange tests " group ?
Add another group here, and immediately use the for construct to create several tests of the same type.
Now this new group " Members age " is located in the old " here comes strange tests ".
Add another test to the same group " Members age ":
In the error header:
Then comes the result of test.trace () to make it easier to find it in the code. And the error object itself, in this case RangeError , is if someone wants to delve deeper into it.
Let's go back to root level .
Just in case, let me remind you that the " here comes strange tests " group now looks like this:
There is another group " Members age " in it. Here we will add a test to it now.
Here is the last thing we just did. Now in the console we see:
And lastly, to consolidate all of the above:
Oh yes. test.root is still in place. It can also be used to create new options for displaying results. It was slightly simplified (for groups, the counters stopped separating groups and tests).
An empty root looks like this:
I'd love to thank:
There are still disadvantages given in the last article. But there are already very interesting thoughts on the methods of their solutions.
The site where you can see all the above code in action, GitHub , Wiki
after my article on test.it. And as I did not plan to extend this period for at least a month, but the time has come for a new publication. A picture to attract attention: Since that time, the library (thanks in large part to the Habrachians) has gained new functionality. And since the syntax given in the previous article does not fully work in the current version, I have no right to postpone this article for another 3 weeks. Who does not like a lot of words - A site where you can see the code in action, GitHub , Wiki Chain calls have appeared
test.it(some)
.comment('comment to test')
.callback(function(){alert('test has been passed')})
.arguments(); // -> [some]
test.group('group', function(){ ... })
.comment('comment to group')
.result(); // -> true/false
and a new nesting mechanism
test.group('first group',function(){
...
test.group('second group', function(){
...
});
...
});
test.group('first group').group('second group',function(){ ... });
A new way to display errors
AND two additional methods test.typeof () and test.trace () .
And also 3 Wiki pages.
And now about all this in more detail.
So. Let’s take a look at the example given in the wiki :
Before we start the tests, we will use the test.typeof () method .
console.log(
test.typeof(1)
,test.typeof("text")
,test.typeof([1,2,3])
,test.typeof({a:1,b:2})
,test.typeof()
,test.typeof(document)
,test.typeof(document.getElementsByTagName("body"))
,test.typeof(window)
,test.typeof(/yes it is RegExp/) // and many many more ...
);
test.typeof () - determines the type of value passed to it. It can distinguish between: Array , Boolean , Date , Error ( EvalError , RangeError , ReferenceError , SyntaxError , TypeError , UriError ), Function , NaN and Number , Object , RegExp , String , Window , HTML , NodeList . He also defines an empty variable as 'undefined'but unlike the standard typeof, it cannot receive an undeclared variable as an argument. 'Undefined' will answer for that non-existent property of the declared and non-empty object. But this is the specificity of the language.
If we refresh the page, a line will appear in the console:
Now take a look at the test.trace () method .
(function firstFunction() {
(function secondFunction() {
(function lastFunction() {
console.log(test.trace());
})();
})();
})();
test.trace () - returns a list (assembled into lines separated by "\ n") lines of code that were executed to call this method. Actually, this is not real trace () (which, unfortunately, is not available in JavaScript) , because all references to calls inside the library were cut out of it.
The output to the console will now be added:
Hereinafter, the insignificant parts of the console output in the screenshots will be omitted so as not to increase the size of the images meaninglessly.
Let's get down to testing.
First, create an object for tests, a variable with an integer value and an empty variable.
var Family = {
name: "Desiderio",
pet: {
type: "dog",
name: "google"
},
members: [
{
name: "Titulus",
age: 23
},
{
name: "Dude",
age: Infinity
}
]
}
var myIQ = 100;
var Nothing;
I think questions regarding this code should not arise. Let's go further.
The next step is the first test.
Non-falsity test.
Everything is as before.
test.it("hello world");
test.done();
test.it (value) - creates a new test, checking value for non-falsity. test.done () - completes the tests and displays the result in the console.
Further it will be assumed that test.done () is the last line of our code. In the examples I will omit it.
In the console we see:
Where:
- root is the name of the zero level group.
- pass - the status of the group, meaning that all tests / groups in it passed successfully.
- 1/0/0 - the number of respectively passed / failed / failed tests / groups.
- (9 ms) - time in milliseconds spent on tests.
If you expand this group, you can see a list of tests / groups in it.
Before disassembling our only test, let's reveal it:
And so:
- pass - test status means that it passed
- argument is not false - description of the type of test and its result
- ["Hello world"] - an array of arguments passed to the test
Let's try to use the chaining mechanism .
The most basic case is adding a comment:
test.it(2+2==5)
.comment("i badly taught algebra at school");
.comment (text) - adds a comment to the test / group in whose chain it was called. At the same time continuing the chain, but more on that later. This code could be written as:
test.it(2+2==5).comment("i badly taught algebra at school");
But, for cases with longer chains, writing to a column is more convenient and visual. Now root (opened automatically because it contains an unsuccessful test) looks like this:
In the counters in the first line, you can notice an increase in the second number from 0 to 1, which means an increase in the number of failed tests / groups.
Let us turn our attention to the test opened by default (because it was not passed) .
It differs from the previous one only in the fail status - meaning that the test failed, and the comment “i badly taught algebra at school”, which we added.
Obviously in test.it (value)You can pass not only strings, but also variables, and expressions, and function calls, etc. In principle, whatever we pass, first it will be completed, the result will be obtained, and this result will already go to the test. That is JavaScript.
We will check what has just been said. Test the expression:
test.it(Infinity>Infinity-1)
.comment("philosophically is not it?");
You can think of this expression with a glass of coffee, we are here for another. The test result looks like the result of the previous one, which is obvious. Until we have left for the jungle of chain calls, and we will definitely leave, look at another test.it () test variant .
Equality test
Let's compare the previously declared variable with a different value.
test.it(myIQ,"genious")
.comment("is I'm genious?");
test.it(myIQ,(1+10)*12 - 34 + 5*5*5 - 123)
.comment("check my IQ to be a normal");
test.it (value1, value2) - checks the equality of two values passed to it. In the console, these 2 tests will look like this:
Nothing unusual, but you should pay attention to the description of the first (failed) test. " arguments has different types " - this text contains a hint explaining why the test failed - the arguments passed are of different types.
Now let's try more complex chains.
Let's do some action, depending on the result of the test.
if (test.it(Family)
.comment("Is Family exist? Is it not empty?")
.result()) {
console.info("by if: ","Yep! Here it is!");
} else {
console.warn("by if: ","ALARM! there are no Family");
}
.result () - ends the chain and returns the test result. In this code, we check Family for non-falsity, and depending on the result, we display different phrases in the console.
The console output now looks like this:
True, it is preferable to perform such a task using another chain call:
test.it(Nothing)
.comment("Is Nothing exist? Is it not empty?")
.callback(
function(){console.info("by callback: ","Yep! Here it is!");}
,function(){console.warn("by callback: ","ALARM! there are no Nothing");});
.callback (function () {/ * funcIfpass * /} [, function () {/ * funcIffail * /} [, function () {/ * funcIferror * /}]]) - performs one of three functions, depending on the result of passing a test or group. While continuing the chain. As a result, we see in the console:
This design is preferable because it does not end the chain.
Groups
Now create the first group.
test.group("Empty group",function(){});
test.group (name, function () {...}) - creates a new group or refers to an existing one , and fills it with tests that are in the function passed by the second argument. But since there were passed tests, the group is created empty, but with the pass status - because it does not have a single failed test.
Before taking a screenshot, I opened the group - this can be seen by turning the arrow down, and two gray pixels, signifying its end. But since the group is empty, it looks almost like a closed one.
Well. We proceed to more meaningful actions. Create a group with two tests inside and a comment.
test.group('Family tests',function(){
test.it(Family.name,"Zukerberg")
.comment("Are we test Zukerberg's family?");
test.it(Family.name,"Desiderio")
.comment("Or Desiderio's?");
}).comment("unite!");
You see - nothing complicated! Here it is only worth paying attention to the comment added by us unite! - in the group header.
Now let's check the feint with our ears, and add a few tests to the already created group. And not just tests, but tests that initiate new tests depending on their result.
Add the following code:
test.group("Family tests",function(){
test.it(Family.pet)
.comment("Do we have a pet?")
.callback(function(){
// I add test in your test, so you can test while you testing
test.it(Family.pet,{type:"dog", name:"google"})
.comment("Is it right pet?");
});
test.it(Family.house)
.comment("Do we have a House?")
.callback(function(){
// next test will not be executed
test.it(Family.pet,{type:"Huge", color:"green"})
.comment("Is it right House?");
});
});
Considering the last 2 tests in this group and, just described, 4 more tests, there will be 5 in total (sic!). You can check on the calculator. Do you see the title? 3 passed, 2 failed - only 5.
New tests
It's time to take a look at a couple of unusual tests. To begin with, in the “here comes strange tests” group, create the following two tests:
test.them([Family.pet, Family.members])
.comment("There must be memebers with pet, to call it a 'Family'");
test.types([Family.pet.name, Family.name],"string")
.comment("Is names are string type");
test.them (values) - an analogue of test.it (value) , only takes the array as the first argument, in which it already checks the elements for non-falsity. test.types (values [, type]) - like test.them (values) it checks the elements of the array passed by the first argument. But it checks that they match types, and if the second argument of type is passed , it takes this type as a sample.
This test has a simplified analogue, but about it a little later.
This is how they look in the console:
I opened the tests along with their arrays of arguments for clarity, but something seems to me, the clarity of this only decreased.
And here is another chain magic:
var numberOfMembers = test.type(Family.members,"Array")
.comment("Is it a several members, nor a single member?")
.arguments()[0].length;
test.it(numberOfMembers>5)
.comment("Is it big family?");
.arguments () - ends the chain of calls and returns the arguments passed to the test ( not to the group! ). Let me explain - the first test, I checked the value of Family.members for non-falsity. Since this is an array of two elements, the test is passed.
arguments () [0] == Family.members . Therefore, the numberOfMembers variable contains the number of elements in the Family.members array , that is 2. The second test fails due to the fact that 2 is not more than 5.
Nesting
Do you still remember that we are in the " here comes strange tests " group ?
Add another group here, and immediately use the for construct to create several tests of the same type.
test.group("Members age",function(){
for (i=0;i25)
.comment("Is "+Family.members[i].name+" older then 25?");
}
});
Now this new group " Members age " is located in the old " here comes strange tests ".
Mistakes
Add another test to the same group " Members age ":
test.it()
.comment("yep, here is error");
Such code will lead to an error, because test.it () expects to receive from 1 to 2 arguments. In the error header:
- RangeError - type of error
- at least one argument expected - a description that helps to understand the cause of its occurrence.
Then comes the result of test.trace () to make it easier to find it in the code. And the error object itself, in this case RangeError , is if someone wants to delve deeper into it.
Group Links
Let's go back to root level .
Just in case, let me remind you that the " here comes strange tests " group now looks like this:
There is another group " Members age " in it. Here we will add a test to it now.
test.group("here comes strange tests").group("Members age",function(){
test.it("bye")
.comment("good");
});
test.group (name) - returns a link to a group, after which it can be used as the beginning of a chain, to add a new group of tests or to add tests to an existing subgroup. Here is the last thing we just did. Now in the console we see:
And lastly, to consolidate all of the above:
full listing with all console output
console.log( // look how do test.typeof() work
test.typeof(1)
,test.typeof("text")
,test.typeof([1,2,3])
,test.typeof({a:1,b:2})
,test.typeof()
,test.typeof(document)
,test.typeof(document.getElementsByTagName("body"))
,test.typeof(window)
,test.typeof(/yes it is RegExp/));
(function firstFunction() { // look how do test.trace() work
(function secondFunction() {
(function lastFunction() {
console.log(test.trace());
})();
})();
})();
var Family = { // Here some complex object
name: "Desiderio",
pet: {
type: "dog",
name: "google"
},
members: [
{
name: "Titulus",
age: 23
},
{
name: "Dude",
age: Infinity
}
]
}
var myIQ = 100; // and value
var Nothing; // and empty value
test.it("hello world"); // Let"s add some simple tests
test.it(2+2==5).comment("i badly taught algebra at school"); // with comment
test.it(Infinity>Infinity-1).comment("philosophically is not it?"); // with expression
// check equalence
test.it(myIQ,"genious").comment("is I'm genious?");
test.it(myIQ,(1+10)*12 - 34 + 5*5*5 - 123).comment("check my IQ to be a normal");
// try some chain staff
if (test.it(Family).comment("Is Family exist? Is it not empty?").result()) {
console.info("by if: ","Yep! Here it is!");
} else {
console.warn("by if: ","ALARM! there are no Family");
}
// do it again in better way
test.it(Nothing).comment("Is Nothing exist? Is it not empty?").callback(
function(){console.info("by callback: ","Yep! Here it is!");}
,function(){console.warn("by callback: ","ALARM! there are no Nothing");});
test.group("Empty group",function(){}); // try to make a group
test.group('Family tests',function(){ // let's unite it!
test.it(Family.name,"Zukerberg").comment("Are we test Zukerberg's family?");
test.it(Family.name,"Desiderio").comment("Or Desiderio's?");
}).comment("unite!");
test.group("Family tests",function(){ // and add some test after
test.it(Family.pet).comment("Do we have a pet?")
.callback(function(){
// I add test in your test, so you can test while you testing
test.it(Family.pet,{type:"dog", name:"google"}).comment("Is it right pet?");
});
test.it(Family.house).comment("Do we have a House?")
.callback(function(){
// next test will not be executed
test.it(Family.pet,{type:"Huge", color:"green"}).comment("Is it right House?");
});
});
test.group("here comes strange tests",function(){
// test existance of most important Family properties
test.them([Family.pet, Family.members])
.comment("There must be memebers with pet, to call it a 'Family'");
// test types of names
test.types([Family.pet.name, Family.name],"string")
.comment("Is names are string type");
// here some magic
var numberOfMembers = test.type(Family.members,"Array")
.comment("Is it a several members, nor a single member?")
.arguments()[0].length;
test.it(numberOfMembers>5).comment("Is it big family?");
// So if we know how many members there, lets check their age
test.group("Members age",function(){
for (i=0;i25)
.comment("Is "+Family.members[i].name+" older then 25?");
}
test.it().comment("yep, here is error"); // add some error to see the trace
});
});
// add final test deep in group
test.group("here comes strange tests").group("Members age",function(){
test.it("bye").comment("good");
});
test.done();
root
Oh yes. test.root is still in place. It can also be used to create new options for displaying results. It was slightly simplified (for groups, the counters stopped separating groups and tests).
An empty root looks like this:
{
"type": "group",
"name": "root",
"time": 0,
"result": {
"pass": 0,
"fail": 0,
"error": 0,
"total": 0
},
"stack": []
}
Conclusion
I'd love to thank:
- camelos and zorro1211 for ideas of chain calls
- camelos separately for .callback idea
- Anonym for the idea of accessing the group from the outside. including from other files.
There are still disadvantages given in the last article. But there are already very interesting thoughts on the methods of their solutions.
The site where you can see all the above code in action, GitHub , Wiki