js testing. JavaScript Knowledge Test - Basics

A friend of mine once expressed his bewilderment about how JavaScript can be used to write serious enterprise products at all, because it does not have a compiler. In fact, the decisive role in creating high-quality code is by no means played by the fact of having a compiler for a programming language, but by a correctly chosen and well-tuned technical process for creating a software system.

This process should include a set of quality and efficiency control tools for the programmer. Such tools can be: unit and integration testing, continuous integration (Continuous Integration, CI), collection and analysis of various metrics (for example, very long methods in nDepend), checking for compliance with the requirements of JsLint, FxCop, etc.

In this article, I want to tell you how to properly perform automatic unit and integration testing of your product in JavaScript. In fact, JavaScript is no different from Java or C# in this regard.

Agile, TDD and BDD

It is generally recommended to create automatic unit and integration tests for the executed functionality in order to reduce the risk of regression errors when changing the code in the future. In the case of JavaScript, these tests can make it much easier to verify that the system works across different browsers by automating the steps to ensure that it works. In addition, writing a unit or integration test for each closed bug in the product can serve a good service.

There are also programming techniques that require you to start coding logic by writing a unit test: Test-Driven Development (TDD) and Behavior-Driven Development (BDD). They are often used in the Agile process. Let's consider their features in more detail.

Test Driven Development

Test-driven development is an iterative coding process that repeats the following four steps:

Step 1. Before adding a new piece of logic, create a unit test to test that logic;

Step 2. Run the test and make sure it Not passes;

Step 3. Write the simplest code that will make the test pass;

Step 4. Edit the code according to quality requirements, remove code duplication, and make sure that the test passes.

A unit test is a code that tests the operation of some component (module) in an isolated environment. An integration test is a code that tests joint work multiple components. Test doubles are used to test a module in an isolated environment when it depends on other modules.

Test Doubles

The division of auxiliary objects used in unit testing into categories originates from the book xUnit Test Patterns by Gerard Meszaros. These categories are collectively called "test doubles" (test doubles). Doublers are of the following types:

  • fake;
  • Dummy.

Stub output values for which are set in advance. It is used to mimic the interface of a dependent component.

Mock is a helper object behavior which is set in advance. It is used to mimic the interface of a dependent component and test whether it is used correctly during the test.

Spy is a helper object for inspecting called methods and parameters passed to them during the test.

Fake is a helper object that implements the interface of a dependent component in a simplified form. For example, for unit testing purposes, you can have an in-memory database instead of the relational database that is used in the production version of the product.

Dummy is a helper object whose indication or passing is required by the method signature or any other contract, but the actual value is never used.

The difference between Stub and Mock is how the test results are verified. In the case of Stub, the state of the object is checked at the end of the test. In the case of Mock, the test checks that the object is used exactly as described during registration. Details can be found in the note Mocks Aren "t Stubs by Martin Fowler, and I will give here just an example.

Stub Mock
"test connect should start polling": function () ( this.client.url = "/my/url"; sinon.stub(ajax, "poll").returns(()); this.client.connect(); sinon.assert.calledWith(ajax.poll, "/my/url"); ) "test connect should start polling": function () ( this.client.url = "/my/url"; var mock = sinon.mock(ajax) mock.expects("poll") .withArgs("/my/url ").returns(()); this.client.connect(); mock.verify(); )

Behavior Driven Development

Iterative approach to development software through the implementation of functional requirements - this is the already familiar style of test-driven development, focused on results. The following three steps are sequentially followed in the BDD process:

Step 1. Definition of functional requirements for the implemented module in the form of tests;

Step 2. Module coding;

Step 3. Checking that all the wishes of the customer or business analyst () are met by checking the results of running tests.

When writing tests in the BDD style, it is very convenient to use Mock objects due to the fact that they perfectly reflect the functional requirements for the component. Thus, tests in the BDD process can serve as a formalized representation of the task ( user story) in terms Scrum, which allows you to save time on writing technical specifications and documentation for the finished product.

What should be a JavaScript unit testing framework?

A complete JavaScript unit and integration testing tool should consist of the following components:

  • Assertion library (a set of methods for checking the state of the component at the end of each test);
  • Mock library (a tool for generating Mock objects and other "understudies");
  • Test runner (instrument auto start tests with support for most browsers, including iOS and Android browsers);
  • Connection block to popular systems of continuous integration (Continuous Integration).

JavaScript Unit Testing Strategies

Today, there are three strategies for unit testing JavaScript code (for more details, see the third chapter of Christian Johansen's book Test-Driven JavaScript Development):

  • in-browser testing;
  • headless testing;
  • Testing along the way JsTestDriver.

In-Browser testing involves running all unit and integration tests from an HTML page that the developer opens in the required browsers on their own. This approach is simple and intuitive. However, its disadvantage is that it does not provide for the possibility of including such tests in Continuous Integration. Also, manually launching an HTML page in ten or more browsers and constantly pressing "F5" can be tedious for a developer.

Headless testing means that all JavaScript code is tested on an emulator, which can be written in Java, Ruby, JavaScript, C++, etc. The most famous emulator by far is PhantomJS, which is a webkit, launched from command line. Among the advantages of the emulator, it can be noted that it can be easily used in Continuous Integration, and also that it allows you to automate the launch of all tests from the command line. However, this approach has a significant drawback - the code is not tested on real browsers, so there is a risk of missing browser errors that are not reproduced on the emulator. Before the advent of JsTestDriver, you could often see In-Browser testing combined with Headless testing, as they complement each other so well.

Testing is an integral part of the software development cycle. Beginning development teams often underestimate its role and check the performance of the application the old fashioned way - "it works, and that's fine." Sooner or later, this strategy fails and the bug tracker begins to overwhelm the countless army of tasks. In order not to fall into such a trap, I recommend once and for all to deal with the nuances of testing JavaScript code.

JavaScript is no longer a cake!

I guess I don't need to explain to you that today JavaScript is not just a language to spice things up. appearance applications. But I'll explain anyway and make a little introduction, because then I'll get paid more more money! 🙂 So, the times when JavaScript was used for jokes or making menus are gone forever. Now it is an independent language that works equally well on both the client and the server. The role of JavaScript has increased significantly, which means that when writing code, you should not be shy about using practices that have proven themselves in other programming languages.

What do I mean by practices and paradigms? Of course, the MVC (model view controller) architectural pattern and code organization patterns. By following these simple tricks, you will be able to write better code that will not only be easy to maintain, but also have the ability to be automatically tested.

Rules for good tests

  • The test should be as simple as possible. The more difficult the test, the more likely it is to make mistakes.
  • Tests should be grouped into modules so that later it is easier to find bugs and be able to test specific parts of the application.
  • Each test should not depend on other tests.
  • Always write a separate test every time you find a bug.

The mistake of most testers

It's no secret that the most popular way of testing has always been a banal eye test. Its essence is simple to disgrace - I wrote a couple of thousand lines of code, solved the problem and launched my creation. I played around, clicked - everything seems to be working, you can upload it to the combat server. Everything is extremely simple, and with due attention of the developer (ideally, an individual nicknamed "tester"), you can rely on the correct operation of the application.

In practice, everything happens a little differently. As a rule, there is no separate tester. The developer himself tries to check the operability of the program by performing the sequence of actions specified in the technical task. More advanced code forges automate this kind of integration testing with tools like Selenium .

Thus, the programmer gets the opportunity to detect only the most gross errors. Unfortunately, "stupid" and "unforeseen" user actions, as well as tricky moves in business logic in 99% of cases remain behind the scenes.

The presence of a separate tester also solves the problem partially and up to a certain time. Even if we discard his sapper attention to detail, the quality of his testing will tend to zero as the application grows. I will give an example from practice.

Once I was assigned to develop a small program. In terms of functionality, the project resembled the simplest CRM, which I implemented in the shortest possible time. Having received the due remuneration, I handed over all the sources to the customer and forgot about the project for eight months. Then the most interesting began. The customer decided to seriously expand the functionality of the program and called me for help. Naturally, I took it and started sculpting function after function... At first it was not difficult, but when it came to the overall integration of the functionality, a buzzing swarm of bugs rushed in my direction. Pieces of code began to conflict, and I had to spend a lot of time resolving conflicts. “Well, how did you not see that there was a problem with your application?” readers will ask. I will answer: I launched it, but due to the fact that the application has grown, I simply did not have the time and nerves to test all the functionality en masse. I limited myself to testing only individual functions and generously paid for it. Moral of the story: "Think of testing as an integral part of development."

Unit tests are like a silver bullet

Unit testing is the best way to save your nerves and increase the guarantees that individual parts of your application will work. If you have never encountered this terrible beast, then I will explain briefly. Unit tests allow you to automate the testing process and expose each feature of your application to tests.

After completion of development new feature(it is possible to write tests before the start of development) the developer writes a special code to test his code. It needs to simulate different situations and return values. For example, we wrote a function to trim spaces (trim). To test its performance, we must prepare several tests that will allow us to assert that:

  • when passing the string "string" we get "string" as output;
  • when passing the terms "line 9" at the output we get "line 9";

We can also add testing for other input parameters (for example, replace the space character with a tab). In general, the better we cover the code with tests and the more we foresee possible negative options, the higher the chances that at the most crucial moment a little hair will remain on the head.

In the JS world, tests are usually written using specialized frameworks. They have everything you need for this, as well as some tools for organizing test progress reports.

Tests!= extra code

Non-unit testers like to argue that unit testing requires writing and maintaining additional code. They say that the deadlines in real projects are most often compressed and it is simply not possible to write additional code.

When there is no time for tests

In the absence of time, it makes no sense to write tests for simple functions (take the same trim () from the example in the article), it is better to focus on the most critical sections of the code. The same rule should be followed when writing frequently changed code. The terms of reference of a live project often change, and some features have to be constantly updated. Such changes can lead to unpleasant moments - the changed code works well with new data, but does not organically digest the old ones. Here, in order not to catch a feil here, it is better to check such functions immediately. Remember a simple rule: there is no time to cover all the code with tests - cover the most important part of it.


As for the tight deadlines, I agree, but I’m ready to argue about the part of the extra code. On the one hand, yes - tests require additional code, and hence the time to write it. On the other hand, this code plays the role of airbags in a car and will definitely pay off as the application grows.
  • Cristian Johansen Test-Driven JavaScript Development (goo.gl/mE6Is) is one of the few books that looks at JavaScript from a test-writing perspective.
  • John Resing, Beer Bibo JavaScript Ninja Secrets (goo.gl/xquDkJ) is a good book that will be useful primarily for intermediate JS developers. The book discusses in detail the issues of writing effective cross-browser code, the nuances of event handling, and many other goodies.
  • David Flanagan JavaScript. Complete Guide» (goo.gl/rZjjk) - The book has been reprinted six times and each release is a bestseller. Indeed, this is the most detailed guide on JavaScript, which every JS developer must read at least once.
  • PhantomJS + JSCoverage + QUnit or console JS unit tests with coverage calculation (goo.gl/FyQ38) - the author of the article demonstrates the use of a bunch of the above packages to collect statistics and calculate the percentage of code coverage by tests.
  • Useful PhantomJS Use Cases - This page shows a large number of PhantomJS combat uses.

When there is no time and the desire to refuse writing tests is tormenting, think three times. Perhaps, in this case, it would be more appropriate to cover only the most tricky parts of the code with tests, rather than abandon testing completely. Always think with an eye to the future, as if in a month your program could grow to an unprecedented size.

Not all code is tested

Why do I say that you need to think about testing before writing the main code? Yes, because the code that is initially supposed to be covered by unit tests is written in a slightly different style. Not all code can be tested. Code that mixes logic and representations, and even stuffed up I don’t understand where, can’t be tested normally. Here I always advise you to follow a few simple rules:

  • No need to write big functions. Each function should solve one problem, not 100500 possible situations. For example, you do not need to hang up the code for sending data to the server in the function responsible for preparing them.
  • A function with more than ten lines of code is most likely a bad function.
  • Logic and presentation should never go together.

QUnit - a classic of the genre from the creators of jQuery

QUnit is especially popular among JavaScript developers. First, it's well documented and easy to use, and second, it's made by the jQuery authors. The library is suitable for testing both jQuery-based and native JavaScript code.


Download latest version QUnit you can from the official site . The library comes as a single JS and CSS file. Let's assume that you figured out how to load the necessary components, and if so, then it's time to write a trial test. Let's not go far and try to test the trim() function.

To demonstrate the tests, I created a simple project with the following structure:

  • index.html - the main file that will display the test results;
  • qunit-1.12.0.js - QUnit library file;
  • example.js - a file containing the code for testing (in our case, the description of the trim() function);
  • test.js - file with tests;
  • qunit-1.12.0.css - styles for designing a report with tests.

The contents of the index.html and test.js file are shown in Listings 1 and 2. The second listing that interests us the most is the declaration of the function under test (trim()) and the test code to verify that it works. Please note that the trim() function itself can be located anywhere, I put it in the second listing just to save space in the log.
Now let's look at the tests themselves. The QUnit.js library offers us a number of methods:

  • test() - wrapper for test description;
  • ok() - assertion allows you to check the truth of the first parameter. In our example, I pass it a call to the trim() function we defined and compare it to the value I expect to receive. If the condition is true, the test is passed;
  • equal() - the method allows you to check the equality of the first and second parameters. Notice right away that this method performs a non-strict check, so it is only suitable for scalars;
  • notEqual() is the opposite of equal(). Executed if the first value is not equal to the second;
  • strictEqual() - similar to equal() with one difference - it uses strict checking (that is, it also checks the data type);
  • notStrictEqual() - the opposite of strictEqual();
  • deepEqual() - method for recursive statements, used for primitives, arrays, objects;
  • notDeepEqual() - the opposite of deepEqual();
  • raises() is an assertion for testing callback functions that throw an exception.

In the second listing, I clearly showed how to apply these methods in practice. If you run a test case in this form, then all tests will be successfully passed (see the corresponding figure). To see the difference between passing tests and failing tests, I slightly changed the code of one test. I deliberately added an erroneous result to the line with the test using strictEqual() (see the corresponding figure).

Listing 1. Contents of the index.html file Testing with QUnit



With testing of simple functions it seems understood. In any case, I have nothing more to add. Then you need to take the real code and try to write tests yourself. Let's look at another frequently encountered task for JavaScript developers - testing asynchronous functions. An application stuffed with JavaScript code interacts 99% with the server side using AJAX. It is also impossible to leave this code without testing, but writing tests will look a little different. Consider an example:

AsyncTest("myAsyncFunc()", function () ( setTimeout(function () ( ok(myAsyncFunc() == true, "Data passed successfully"); start(); ), 500); ));

The main difference between this example and the previous one is that asyncTest() is used instead of the test() wrapper, thus directly stating that I am interested in asynchronous testing. Next, I start a timeout of 500 milliseconds. During this time, the myAsyncFunc() function should transfer data to the test server and, if everything is OK, return true. Here comes the most interesting moment. When asyncTest() is called, the thread of execution is stopped, and when the test is finished, it must be started by itself. To control the flow of execution, QUnit provides the start() and stop() methods.


Testing asynchronous functions with the QUnit library is fairly straightforward. The last example I'd like to take a look at is writing a test that performs multiple asynchronous checks. The main question that arises in such tasks is the optimal place to start the thread of execution. The official doc suggests using something like

AsyncTest("myAsyncFunc()", function () ( expect(3); // Do three tests here ok(myAsyncFunc(), "Making the world better 1"); ok(myAsyncFunc(), "Making the world better 2") ; ok(myAsyncFunc(), "Making the world a better place 3"); setTimeout(function () ( start(); ), 3000); ));

Test for custom actions

You should always remember that a lot of all sorts of interface things are written in JavaScript. For example, a user clicks on a pimp and something should happen in response to his click. There is a huge amount of such “interface” code in projects, and it also needs to be covered with tests. Let's see how we can simulate a user keystroke and write a separate test for this action. Let's imagine that we have some function that logs the keys pressed. I gave her code in the third listing.

Listing 3. Logging keystrokes function KeyLogger(target) ( if (!(this instanceof KeyLogger)) ( return new KeyLogger(target); ) this.target = target; this.log = ; var self = this; this.target. off("keydown").on("keydown", function(event) ( self.log.push(event.keyCode); )); )

Now let's try this function to test. First of all, in the body of the test, we need to emulate the pressed key. The easiest way to do this is with the jQuery library, which allows you to create an event in a couple of lines of code (see Listing 4).

Listing 4. Test code for KeyLogger test("Key recording test", function () ( var event, $doc = $(document), keys = KeyLogger($doc); event = $.Event("keydown"); event .keyCode = 9; $doc.trigger(event); equal(keys.log.length, 1, "Key logged"); equal(keys.log, 9, "Key pressed with code 9 logged"); ));

At the very beginning of the listing with the test, I prepare an event to emulate a keystroke - "keydown". We will be interested in pressing the Tab key (code 9). Then, using the trigger() method, I send the prepared event, after which I can start testing. First, we check the big picture - whether a key was pressed, and then its code.

DOM under the guise of tests

Since Qunit.js allows you to test custom actions, writing tests for the DOM shouldn't be a problem either. This is true, and the example below will confirm my words. I will not comment on it, just take a look at the code and everything will become clear:

Test("Adding a new div element", function () ( var $fixture = $("#qunit-fixture"); $fixture.append("

This is the new div
"); equal($("div", $fixture).length, 1, "New div added successfully!"); ));

PhantomJS - run tests from console


Writing tests using the QUnit.js library is convenient and simple, but sooner or later you will be visited by the desire to somehow automate the launch, testing and collection of results. For example, I have a separate virtual machine in DigitalOcean for this business, which I can only manage using the console.

The PhantomJS project solves this problem elegantly enough. This is not just another framework for writing unit tests, but a full-fledged console version of the WebKit engine. To put it simply, this application emulates a browser. With the help of PhantomJS, it’s really not just to automate the verification of test execution, but also to solve a lot of tasks that sooner or later arise before a developer: getting the results of rendering pages to a file (PNG, JPG), network monitor functions (load speed, overall performance, etc.), emulation of user actions and so on. I recommend not to be lazy and read the official documentation on this project, you will definitely find something interesting for yourself.

PhantomJS can be compiled for different platforms (*nix, OS X, Windows). If you develop everything under Windows, then there are no problems - merge the binaries, and go ahead. Minor difficulties with launching may arise if you have two video adapters installed, one of which is NVIDIA. In this case, you will have to use the hack described in the sidebar.


Let's try to get acquainted with PhantomJS in practice. To run the tests prepared in the last section through PhantomJS and get the results of the execution in the console, we need a special loader script - run-qunit.js . Open the console (I work on Windows, so I use cmd) and type the command in the format

phantom.exe<путь к run-qunit.js> <путь к странице с тестами>

In my case, the launch command turned out like this:

E:\soft\phantomjs>phantomjs.exe E:\temp\testjsforx\qunit\run-qunit.js file:///E: /temp/testjsforx/qunit/index.html

The result of its execution:

Tests completed in 2592 milliseconds. 9 assertions of 9 passed, 0 failed.

All tests passed

It is definitely necessary to cover the code with tests, and it doesn’t matter what scale you are creating the application. Once again, I remind you: even the smallest programs turn into clumsy monsters that need to be supported and completed with functionality. Well-tested code is the key to success and quality. Yes, it’s not easy to start writing code suitable for automated tests right away, but believe me, all this torment will more than pay off in the future. That's all for today, good luck!

PhantomJS issues on Windows

It just so happened, but I tested all the examples for this article not on Linux, but under the good old Windows 7. It turns out that PhantomJS has some problems when working on systems that use several video adapters. On my laptop, in addition to the integrated video chip, NVIDIA also hangs out, and because of this, PhantomJS categorically refused to respond to the phantom.exit () command. As a result, after the script was executed, the PhantomJS process did not end its work and continued to hang in memory. The terminal window also stopped responding to exit commands ( did not help).

If you are facing a similar problem and are planning to use PhantomJS on Windows, then get ready to do the following hack. open panel NVIDIA control. Find the "3D Settings" item in the tree. On the right side, the option "Preferred graphics adapter". By default. its value is set to Autoselect. We need to change it to "High Performance NVIDIA Processor" or "Integrated Graphics Hardware". After this simple trick, PhantomJS began to behave obediently.

Creating effective test cases can be critical to major projects, in case the behavior of parts of the application may change for various reasons. Perhaps the most common problem is when a large group of developers are working on the same or related modules. This can lead to an unplanned change in the behavior of functions written by other programmers. Or working under tight deadlines leads to an inadvertent change in critical parts of the application.

Testing a web application usually consists of a visual assessment of the page elements and an empirical assessment of the performance of the functionality. In other words, in navigating through sections and performing actions on dynamic elements.

Over time, the project is filled with new functionality, which lengthens and complicates the process of checking its work. Unit testing is used for automation.

There are 2 approaches to building test scenarios:

  • white boxtesting– writing tests is based on the implementation of the functionality. Those. we check for the same algorithms on which the work of the modules of our system is based. This approach does not guarantee the correct operation of the system as a whole.
  • black boxtesting– scripting is based on specifications and system requirements. This way you can check the correctness of the results of the entire application, but this approach does not allow you to catch small and rare errors.

What to test

It may seem like a good idea to test every feature you implement. This is not entirely true. Writing tests takes a developer's time, so to optimize the process of working on creating an application, it is worth preparing tests only for complex, critical, or those functions that depend on the results of other system modules. Cover tests with ambiguous logic that could potentially contain errors. It is also worth creating tests for those parts of the code that are planned to be optimized in the future, so that after the optimization process you can make sure that they are executed correctly.

In general, it is extremely important to evaluate the costs of testing against the tightness of the development time. Of course, if you are not limited in time, you can let each function be covered with tests. But as a rule, development is carried out in a tight time pressure, so the task of an analyst or an experienced developer is to understand where testing is needed. In addition, writing tests increases the cost of the project.

Thus, we can formulate 3 cases when the use of unit testing is justified:

1) If the tests make it possible to identify errors faster than with their usual search.

2) Reduce debugging time

3) Allow you to test frequently changed code.

Of the 3 main components of the frontend (HTML, CSS, JavaScript), perhaps only the JavaScript code needs to be tested. CSS is only validated visually when the developer/tester/customer views GUI in various browsers. HTML - markup is checked by the same method.

How to test

When designing test scenarios, you should be guided by the following principles:

  • Your tests should be as simple as possible. Then it will be more likely that the bug that you are trying to repeat will affect the results of its implementation.
  • Decompose tests of large modules. Grove to find the specific location of the error.
  • Make tests independent. The result of one test should in no case depend on the results of another.
  • Test results should be completely repeatable and expected. Each time you run the test again, the result should be the same as the last time.
  • For any error in the execution of the application, a test script must be created. This way you will be sure that the bug is really fixed and does not appear to users.

How to test

There are several libraries for unit testing js code. Perhaps the most common is QUnit. To conduct unit tests using this library, we need to create a "sandbox" - a simple html page in which the testing library will be connected, the code to be tested, and the tests themselves.

Functions for tests:

(function() ( window.stepen = function(int) ( var result = 2; for (var i = 1; i< int; i ++) { result = result * 2; } return result; } window.returnFunc = function() { return "ok"; } })();

Test listing:

Test("stepen()", function() ( equal(stepen(2), 4, "2^2 - equal method"); ok(stepen(3) === 8, "2^3 - ok method" ); deepEqual(stepen(5), 32, "2^5 - deepEqual method"); )); asyncTest("returnFunc()", function() ( setTimeout(function() ( equal(returnFunc(), "ok", "Async Func Test"); start(); ), 1000); ));

As you can see, QUnit supports 3 functions for comparing the results of code execution with the expected one:

  • ok()- considers the test successful if the return result = true
  • equal()- compares the result with the expected
  • deepEqual()- compares the result with the expected one, checking its type

Execution result:

As you can see, the QUnit library tests the code for several browsers at once.

There are a number of other libraries for unit tests. However, the concept of building test scripts in them is the same, so having dealt with one, it will not be difficult for you to switch to another.

Important to remember

A feature of modern js code is the asynchrony of its execution. Libraries for testing usually have the ability to conduct asynchronous tests. But for example, if you are trying to test a function that, say, sends a get request to the backend and returns a response from it, then to conduct tests, you will have to stop the thread with the stop() function, start the function under test, and then restart the thread with the start() method , "wrapping it" in setTimeout(). Those. you have to set some time interval during which the execution of the function should be completed. One must carefully choose the duration of this segment, .k. on the one hand, the long work of the method can be either a feature or even a necessity for a specific implementation of the application functionality, or incorrect behavior.

Testing Backbone Applications

For an example of testing applications written using Backbone.js, we will use the project described in .

You can check with unit tests:

  • Correctness of creation of models and controllers
  • Correctness of data in models
  • Execution of controller methods (for this they must return a result)
  • View loading success

Test code:

Test("Backbone.js", function() ( ok(sample, "Namespace check"); ok(sample.routers.app, "Router check"); ok(sample.core.pageManager.open("chat") , "Page opening test (Controller method call)") ok(sample.core.state, "Model check"); equal(sample.core.state.get("content"), "sintel", "Model data get test "); stop(); ok(function() ( $.ajax(( url: "app/templates/about.tpl", dataType: "text" )).done(function(data) ( self.$el. html(data); return data; )) ), "Template loading check"); setTimeout(function() ( start(); ), 1000); ));

The result of working with testing errors:

Test run automation

Typically, deploying an application is a task that needs to be done quite often during intensive development. Therefore, this operation is usually automated. We use Jenkins, a continuous integration tool, in our work. The idea is to combine deployment through Jenkins with automated tests.

QUnit tests run in the browser. Phantomjs, software that emulates the browser, will help us get around this feature. The phantomjs developers have already provided a script for executing QUnit tests, but it had to be slightly modified for it to work correctly.

/** * Wait until the test condition is true or a timeout occurs. * Useful for waiting * on a server response or for a ui change (fadeIn, etc.) to occur. * * @param testFx javascript condition that evaluates to a boolean, * it can be passed in as a string (e.g.: "1 == 1" or * "$("#bar").is(":visible")" or * as a callback function. * @param onReady what to do when testFx condition is fulfilled, * it can be passed in as a string (e.g.: "1 == 1" or * "$("#bar").is (":visible")" or * as a callback function. * @param timeOutMillis the max amount of time to wait. If not * specified, 3 sec is used. */ function waitFor(testFx, onReady, timeOutMillis) ( var maxtimeOutMillis = timeOutMillis - timeOutMillis: 3001, //< Default Max Timout is 3s start = new Date().getTime(), condition = false, interval = setInterval(function() { if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) { // If not time-out yet and condition not yet fulfilled condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code } else { if(!condition) { // If condition still not fulfilled // (timeout but condition is "false") console.log(""waitFor()" timeout"); phantom.exit(1); } else { // Condition fulfilled (timeout and/or condition is //"true") console.log(""waitFor()" finished in " + (new Date().getTime() - start) + "ms."); typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it"s supposed to do once the // condition is fulfilled clearInterval(interval); //< Stop this interval } } }, 100); // repeat check every 250ms }; }; if (phantom.args.length === 0 || phantom.args.length >2) console.log("Usage: run-qunit.js URL"); phantom.exit(); ) var page = new WebPage(); // Route "console.log()" calls from within the Page // context to the main Phantom context (i.e. current "this") page.onConsoleMessage = function(msg) ( console.log(msg); ); page.open(phantom.args, function(status)( if (status !== "success") ( console.log("Unable to access network"); phantom.exit(); ) else ( waitFor(function() ( return page.evaluate(function()( var el = document.getElementById("qunit-testresult"); if (el && el.innerText.match("completed")) ( return true; ) return false; )); ), function()( var failedNum = page.evaluate(function()( var el = document.getElementById("qunit-testresult"); console.log(el.innerText); try ( return document.getElementsByClassName("fail" ).innerHTML.length; ) catch (e) ( return 0; ) return 10000; )); phantom.exit((parseInt(failedNum, 10) > 0) ? 1: 0); )); ) ));

To display results messages in the console, you need to add the logging function to the test script.

Now the site is available testing for knowledge of the following topics: HTML, css, JavaScript, PHP, SQL.

Each test consists of 10 questions on a specific topic. In each question, I tried to touch on the most diverse areas of application of a particular language in order to check your level of knowledge as thoroughly as possible.

Of course, everything tests are free and anyone can go through.

Test procedure:

  1. Follow the link " Start testing" of the corresponding test.
  2. Answer the questions by choosing the only one correct option.
  3. Upon completion of testing, you will see your score, number of mistakes, and analysis of each question from the test.

Attention! Returning to the previous question will not work, so before answering, think.

Tests currently available

  1. HTML

    • Total test passed: 75424 people
    • Average score: 2.83 out of 5 points.

    Basic knowledge test HTML. You will need to know basic HTML tags and their proper use. It is also necessary to understand the features of the standard XHTML 1.1.

  2. css

    • Total test passed: 32828 people
    • Average score: 3.37 out of 5 points.

    The test tests knowledge of the basics css. To successfully pass the test, you must know the basic types of selectors (their syntax), know the basic properties and their possible values, and also know the purpose of the most popular pseudo-elements.

  3. JavaScript

    • Total test passed: 24845 people
    • Average score: 3.31 out of 5 points.

    This quiz tests your knowledge of the JavaScript language. Questions from the test cover different areas of application given language. There are a lot of questions about understanding "small" nuances. Otherwise, you are required to know basic things: working with variables, basic JavaScript functions, operator priorities, and so on.

  4. PHP

    • Total test passed: 33239 people
    • Average score: 3.03 out of 5 points.

    This quiz tests your knowledge of the PHP language. You are required to know basic PHP constructs, work with variables, sessions, redirect implementation and other standard things.
    Convincing request: The test contains many questions like: "What will the script output?". A big request, do not copy it and check it. Be honest with yourself.

  5. SQL

    • Total test passed: 18014 people
    • Average score: 3.28 out of 5 points.

    This test tests your knowledge of the language SQL queries. The questions cover only the most basic knowledge of this language, without any deepening. You will need knowledge of the most basic SQL queries, as well as their competent use.

On the example of a simple calculator application on Node.js. We will test using the Mocha framework.

What our application should be able to do:

  • Add, subtract, divide and multiply any two numbers;
  • Show a warning and exit if something other than a number was entered;
  • There must also be a command line interface so that the end user can use the application.

What we need:

  • Node.js and npm
  • Knowledge of JavaScript: syntax and code structure, data types, mathematical operations and conditional expressions.

With the goals sorted out, you can start setting up the environment for testing and development.

Setting up the environment

Since we are using Node.js, we need to create a local environment for files and dependencies.

Create new folder calc. On the command line, change to this directory and create a new project with npm init , which will create new file package.json for our program.

You will be prompted to enter the package name, version, description, and other information about the package. You can enter a name calc.js and keep pressing Enter to assign default values. When you get to test command , type mocha - this is the testing framework we'll be using:

test command: mocha

After entering all the information, the script will create a file package.json, which looks something like this:

( "name": "calc.js", "version": "1.0.0", "description": "Simple calculator in Node.js", "main": "index.js", "scripts": ( " test": "mocha" ), "author": "", "license": "ISC" )

The last step in this step is installing Mocha. Enter the following command to install:

npm install --save-dev mocha

After applying this command, a folder will appear node_modules, file package-lock.json, and in the file package.json the following lines will appear:

"devDependencies": ( "mocha": "^4.0.1" )

Create a file test.js. We will use the built-in module in Node.js assert to check if true and true are true. Since it's true, the test should pass:

Const assert = require("assert"); it("should return true", () => ( assert.equal(true, true); ));

Now run the test from the command line:

$ npm test > mocha ✓ should return true 1 passing (8ms)

The test went as expected, so the environment setup is done. Remove from test.js everything but the line const assert = require("assert"); .

We will use the file test.js throughout the application development process. Create two more files: operations.js for arithmetic and validation functions and calc.js for the application itself. We use so many files that they don't get too long and complicated. Here is our current list of files:

  • calc.js;
  • node_modules;
  • operations.js;
  • package-lock.json;
  • package.json;
  • test.js;

Let's add the first real test for our application.

Adding math operations

First of all, our application must be able to add, subtract, divide, and multiply any two numbers. So, for each of these operations, we must create a separate function.

Let's start with addition. We will write a test that uniquely obtains the expected sum of two numbers. In the code below, we check if the sum of 1 and 3 is equal using the add() 4 function:

Const assert = require("assert"); it("correctly finds the sum of 1 and 3", () => ( assert.equal(add(1, 3), 4); ));

After running the test with the npm test command, we see the following:

> mocha 0 passing (9ms) 1 failing 1) finds the sum of 1 and 3 correctly: ReferenceError: add is not defined at Context.it (test.js:5:16) npm ERR! test failed. See above for more details.

The test failed with a ReferenceError: add is not defined . We are testing the add() function, which does not yet exist, so this result is quite expected.

Let's create an add() function in a file operations.js:

Const add = (x, y) => (+x) + (+y);

This function takes two arguments x and y and returns their sum. You may have noticed that we write (+x) + (+y) instead of x + y . We use the unary operator to cast the argument to a number, in case the input is a string.

Note This uses an ES6-added arrow function and an implicit return.

Since we're using Node.js and splitting the code into multiple files, we need to use module.exports to export the code:

Const add = (x, y) => (+x) + (+y); module.exports = ( add )

At the beginning of the file test.js we import code from operations.js with require() . Since we are using the function through the operations variable, we need to change add() to operations.add() :

Const operations = require("./operations.js"); const assert = require("assert"); it("correctly finds the sum of 1 and 3", () => ( assert.equal(operations.add(1, 3), 4); ));

Let's run the test:

$ npm test > mocha ✓ correctly finds the sum of 1 and 3 1 passing (8ms)

We now have a working function and the tests are passing successfully. Since the functions of the other operations work in a similar way, adding tests for subtract() , multiply() , and divide() is easy:

It("correctly finds the sum of 1 and 3", () => ( assert.equal(operations.add(1, 3), 4); )); it("correctly finds the sum of -1 and -1", () => ( assert.equal(operations.add(-1, -1), -2); )); it("correctly finds the difference between 33 and 3", () => ( assert.equal(operations.subtract(33, 3), 30); )); it("correctly finds the product of 12 and 12", () => ( assert.equal(operations.multiply(12, 12), 144); )); it("correctly finds the quotient of 10 and 2", () => ( assert.equal(operations.divide(10, 2), 5); ));

Now let's create and export all the functions to test.js:

Const add = (x, y) => (+x) + (+y); const subtract = (x, y) => (+x) - (+y); const multiply = (x, y) => (+x) * (+y); const divide = (x, y) => (+x) / (+y); module.exports = ( add, subtract, multiply, divide, )

And run new tests:

$ npm test > mocha ✓ correctly finds the sum of 1 and 3 ✓ correctly finds the sum of -1 and -1 ✓ correctly finds the difference between 33 and 3 ✓ correctly finds the product of 12 and 12 ✓ correctly finds the quotient of 10 and 2 5 passing (8ms)

All tests pass successfully, so now we can be sure that the main functions of our application will work correctly. Now we can do some additional validation.

Adding Validation

At the moment, when the user enters a number and selects the desired operation, everything works fine. However, what happens if you try to find the sum of a number and a string? The application will try to perform the operation, but because it expects a number, it will return NaN .

Instead of returning some strange values, it's time to do the second task - to make the application show a warning and exit if the input argument is not a number.

First you need to write a function that will check if the input is a number or not. The application should only work with numbers, so we will handle three situations:

  1. Both inputs are numbers.
  2. One input is a number and the other is a string.
  3. Both inputs are strings.
it("reports an error when using a string instead of a number", () => ( assert.equal(operations.validateNumbers("sammy", 5), false); )); it("reports an error when using two strings instead of numbers", () => ( assert.equal(operations.validateNumbers("sammy", "sammy"), false); )); it("success when using two numbers", () => ( assert.equal(operations.validateNumbers(5, 5), true); ));

The validateNumbers() function will validate both parameters. The isNaN() function checks if the parameter is not a number, and if not, returns false . Otherwise, it returns true , which means successful validation.

Const validateNumbers = (x, y) => ( if (isNaN(x) && isNaN(y)) ( return false; ) return true; )

Don't forget to add validateNumbers to module.exports at the end of the file. Now you can run new tests:

$ npm test 1) reports an error when using a string instead of a number ✓ reports an error when using two strings instead of numbers ✓ success when using two numbers 7 passing (12ms) 1 failing 1) reports an error when using a string instead of a number: AssertionError : true == false + expected - actual -true + false

Two tests passed, but one failed. The test for two numbers was successful, as was the test for two strings. What can not be said about checking for the input of a string and a number.

If we look at our function again, we can see that both parameters must be NaN for the function to return false . If we want to achieve the same effect when at least one of the parameters is NaN , we need to replace && with || :

Const validateNumbers = (x, y) => ( if (isNaN(x) || isNaN(y)) ( return false; ) return true; )

If you run npm test again after these changes, then all tests will pass:

✓ reports an error when using a string instead of a number ✓ reports an error when using two strings instead of numbers ✓ success when using two numbers 8 passing (9ms)

We have tested all the functionality of our application. Functions successfully perform mathematical operations and validate input. The final stage is the creation of the user interface.

Creating an interface

We already have the necessary functions, but the user still cannot use them in any way. So we need an interface. For our application, we will create a command line interface.

At the moment the file calc.js should be empty. This is where our application will be stored. First you need to import functions from operations.js:

Const operations = require("./operations.js");

The interface itself will use Node.js' built-in Readline CLI module:

Const readline = require("readline");

After importing everything you need, you can start creating the application. To create the interface, we will use readline , available through the rl variable:

Const rl = readline.createInterface(( input: process.stdin, output: process.stdout ));

The first thing the user should see after starting the program is a welcome message and instructions for use. To do this, we'll use console.log() :

Console.log(` Calc.js You have opened a Node.js calculator! Version: 1.0.0. Usage: the user must enter two numbers and then choose what to do with them. `);

Before we get into the calculator functions themselves, let's check that console.log() works as it should. We will make the program print a message and exit. To do this, add a call to the rl.close() method at the end.

To run the application, enter node followed by a filename:

$ node calc.js Calc.js You've opened the Node.js calculator! Version: 1.0.0. Usage: The user must enter two numbers and then choose what to do with them.

The program displays a welcome message and exits. Now we need to add user input. The user is required to select two numbers and one operation. Each input will be requested by the rl.question() method:

Rl.question("Enter the first number: ", (x) => ( rl.question("Enter the second number: ", (y) => ( rl.question(` Choose one of the following operations: Add (+) Subtract (-) Multiplication (*) Division (/) Your choice: `, (choice) => ( // more code will appear here rl.close(); )); )); ));

The variable x is assigned the first number, y the second, and choice is the selected operation. Now our program asks for input, but does nothing with the received data.

After the third input, you need to check that only numbers have been entered. To do this, we will use the validateNumbers() function. Using the NOT operator, we will check if numbers have been entered, and if not, exit the program:

If (!operations.validateNumbers(x, y)) ( console.log("Only numbers are allowed! Please restart the program."); )

If everything is entered correctly, now you need to run the corresponding operation method created earlier. To process the four possible choices, we'll use a switch statement and output the result of the operation. If a non-existent operation was selected, the default block will be executed, telling the user to try again:

If (!operations.validateNumbers(x, y)) ( console.log("Only numbers can be entered! Please restart the program."); ) else ( switch (choice) ( case "1": console.log(`Sum $(x) and $(y) equals $(operations.add(x, y)).`); break; case "2": console.log(`Difference of $(x) and $(y) equals $( operations.subtract(x, y)).`); break; case "3": console.log(`The product of $(x) and $(y) equals $(operations.multiply(x, y)).`) ; break; case "4": console.log(`Private $(x) and $(y) equals $(operations.divide(x, y)).`); break; default: console.log("Please restart the program and select a number between 1 and 4."); break; ) )

Note The console.log() functions here use template strings that allow expressions.

/** * A simple Node.js calculator that uses the calculator app that uses * the built-in Readline command line interface. */ const operations = require("./operations.js"); const readline = require("readline"); // Use readline to create an interface const rl = readline.createInterface(( input: process.stdin, output: process.stdout )); console.log(` Calc.js You've opened a Node.js calculator! Version: 1.0.0. Usage: The user must enter two numbers and then choose what to do with them. `); rl.question("Enter the first number: ", (x) => ( rl.question("Enter the second number: ", (y) => ( rl.question(` Choose one of the following operations: Add (+) Subtract (-) Multiplication (*) Division (/) Your choice: `, (choice) => ( if (!operations.validateNumbers(x, y)) ( console.log("You can only enter numbers! Please restart the program. "); ) else ( switch (choice) ( case "1": console.log(`The sum of $(x) and $(y) equals $(operations.add(x, y)).`); break; case "2": console.log(`The difference between $(x) and $(y) is $(operations.subtract(x, y)).`); break; case "3": console.log(`The product of $( x) and $(y) equals $(operations.multiply(x, y)).`); break; case "4": console.log(`Private $(x) and $(y) equals $(operations. divide(x, y)).`); break; default: console.log("Please restart the program and select a number between 1 and 4."); break; ) ) rl.close(); )); )) ; ));

Now our application is ready. Let's check his work in the end. Enter 999 and 1 and select the subtraction operation:

$ node calc.js Enter the first number: 999 Enter the second number: 1 Your choice: 2 The difference between 999 and 1 is 998.

The program successfully completed its work, displaying the correct result. Congratulations, you've written a simple calculator using Node.js and learned the basics of TDD development.



Loading...
Top