Lecture
Hi, today we’ll talk about unit testing javascript, I promise to tell everything I know. In order to better understand what unit testing javascript is, I highly recommend reading everything from the category Scripting on the client side JavaScript, jqvery, JS frameworks
A fairly common problem when writing unit tests for client code is that its structure is not suitable for testing. After all, JavaScript code can be written for any page of a site or module in an application, and it can also be directly connected with server logic and with HTML code. In the worst case, the code is completely tied to HTML as built-in event handlers.
Usually this situation occurs when the developer does not use special JavaScript libraries to write the application. Indeed, writing a built-in event handler is much easier than binding it through the DOM API. However, most developers still use special JavaScript libraries, such as jQuery. JQuery allows you to put built-in handlers in separate scripts or on the same page or in a separate js file. But the code placed in a separate file is not a module ready for testing.
What is a module? At best, this is a function that always returns some result for some parameter. Such a module is very easy to test. Of course, to extract such functions from an existing script, most of the time will have to be spent on operations with the DOM tree. However, this approach will help to identify structural units from the code for creating unit tests.
The easiest way to test a script is if you write it from scratch. But the article is not about that. In this article I will try to talk about how to extract and test the most important parts of the script, as well as identify and fix errors.
The process of changing the internal structure of a program without changing its behavior is called refactoring. This is a great way to improve code in a project. But any change in program code can change the behavior of the program, so it is best to run unit tests immediately after refactoring.
When adding tests to existing code, there is a risk of disrupting its operation. Therefore, until you get comfortable with unit tests, try to additionally check the operation of the code manually.
Enough theory! I’ll give you a practical example by testing the specific JavaScript code that is embedded in the html code of the page. The script scans all links with the title attribute. And it uses the value of this attribute to transform the text of links into a more human form: “n minutes ago”, “n hours ago”, “n weeks ago”.
If you run this script, you will see a problem: not a single date has been replaced. Although the code does work. It goes through all the a links and checks to see if they have a title. If it is, the script passes its value to the prettyDate function. If it returns something, then the script replaces the innerHTML links with this result.
The problem with this script is that the prettyDate function for any date older than 31 days returns undefined (using return), leaving the link text unchanged. And the current date is much older than the 31st day. Therefore, in order for the script to change the text of the links, I rigidly indicated the current date:
As a result of the script, links appeared: "2 hours ago", "yesterday" and so on. This is already something, but it does not reach the tested module. Therefore, without further refactoring, all I can do is test the changes in the markup.
In this case, it is necessary to create code with as pure a function (methods) as possible so that the tests are isolated from the environment (database, network, file system, time).
Apparently the script needs 2 changes:
The contents of the prettydate.js file:
Now you can write a unit test.
(Before starting, make sure you have a console turned on, such as Firebug or Chrome's Web Inspector.)
The result is a unit test that outputs the results to the console. This is evidenced by the site https://intellect.icu. This avoids the dependency on the DOM, so the test can be run in a non-browser JavaScript environment, such as Node.js or Rhino.
The script will display the summary information in the console: the total number of tests, the number of failed tests, the number of successful tests.
Upon successful completion of all tests, the result will be as follows:
Of 6 tests, 0 failed, 6 passed.
If one of the tests failed, then it is displayed in the console in the following form:
Expected 2 day ago, but was 2 days ago.
Of 6 tests, 1 failed, 5 passed.
This approach to the development of unit tests is interesting as a proof of concept: it is much more practical to use the existing testing framework, which will provide better performance and more opportunities for writing and organizing tests.
Choosing a framework for testing is a matter of taste.
I prefer to use QUnit because it has many features
Here it is worth paying attention to 3 sections. 3 files are connected here: 2 files are QUnit (qunit.css and qunit.js) and 1 prettydate.js file.
Then comes a block of JavaScript code with test data. The test method is called only once, passing the name of the test as the first argument, and the second argument is the function that actually runs the tests. In this block, the now variable is declared, which is used when the equal method is called. The equal method is one of the methods that QUnit offers. The result of the prettyDate function is passed as the first argument to the equal method. As the second argument, the expected result is passed to the equal method. If these two arguments are the same, then the statement is true, otherwise it is not true.
If the test succeeds, QUnit will output the result as follows:
If during the test there was a false statement, then the result will be of this kind:
Since one of the tests was unsuccessful in the second screenshot, QUnit does not minimize the test log and you can immediately see what went wrong. Together with the output of the expected and actual results, QUnit also shows the difference between them, which is very useful when comparing large strings. Here it’s pretty obvious what went wrong.
Test data is not enough, because I am not testing the option n weeks ago. But first, I will do the refactoring again. The prettyDate function is called for each statement and the now argument is passed each time. Now I will fix it:
Now the prettyDate function is called inside the new date function and the now variable is no longer created, its value is hard coded as a parameter.
I have already done quite a good job with the prettyDate function. Now let's pay attention to the initial script. In the script, some DOM elements were selected, and they were updated depending on the result of the prettyDate function. Applying the same principles as before, I will refactor and test it as it should) In addition, I will create a module for these 2 functions, and to avoid errors with the global namespace, I will give them more meaningful names.
Content prettydate2.js:
From the base script, I extracted the new prettyDate.update function, however the now argument is still passed to prettyDate.format. The basic QUnit test for this function starts by selecting all elements a from the div block with the identifier # qunit-fixture. A new element has appeared in the HTML code
<div id = “qunit-fixture”> ... </div>. It contains the extracted markup from the initial example needed for testing. Put it in # qunit-fixture so you don’t have to worry about the effect of one test on the result of another, as QUnit automatically resets the markup after each test.
What happens the first time prettyDate.update is tested.
First, links from <div id = "qunit-fixture"> ... </div> are selected, then they are checked for the contents of dates. PrettyDate.update is then called, passing in a fixed date (the same as in previous tests). Then the text of the same links is checked for the desired values “2 hours ago” and “Yesterday”, because prettyDate.update should have replaced this text.
The next test is prettyDate.update, one day later does almost the same thing, except that it passes a different date to prettyDate.update and therefore returns a different result for 2 references.
It's time for another refactoring! It is necessary to remove duplication!
In this example, I created a new function, domtest, which encapsulates the logic of two previous calls for verification and passes such arguments as the name of the test, a date in a string format, and two expected output lines. This function is called twice.
We should see what happened in the end)
As a result of refactoring, many improvements have been made compared to the first example. And thanks to the prettyDate module, I can add even more conflict-free functionality to the global namespace.
When testing JavaScript code, the question arises not only in which one to choose a framework or write a module yourself. Usually you have to make big structural changes to apply them to a script that was previously checked manually. I really hope that you understand at least something. For example, how to change the structure of an existing module for testing and how to use a more functional framework to obtain more useful results.
The QUnit framework has a lot more features than I described. For example, there is support for testing asynchronous code: AJAX and events, timeouts. Visualization of the results helps debug the code, facilitates the re-run of specific tests, and provides a call stack for failed statements and caught exceptions. For further study of the QUnit framework, follow the QUnit Cookbook link.
Обычно я пишу свои JS-модули, используя шаблон модуля Revealing Module, т. Е. Выставляю только одни методы, оставляя другие внутренними для объекта. Я пытаюсь интегрировать написание тестов (в частности, jasmine.js) в свой рабочий процесс разработки, но до сих пор не могу понять, как применять тесты к моему частному коду. Был ли у вас опыт с этим? Во всех примерах, которые я видел, все тестируемые функции общедоступны.
PS. Пожалуйста, обновите ссылку для QUnit Cookbook (должно быть http://qunitjs.com/cookbook/ )
В настоящее время я участвую в модульном тестировании графического интерфейса, и с такими тяжеловесными вещами, как Java Swing, я в порядке, но мне тоже нужно что-то для мира HTML5 / Ajax / JS, и этот QUnit, кажется, является хорошим началом для меня. Идея хорошо понятна и позволяет даже новичку начать модульное тестирование кода JS. Отличная работа. Там только одна записка. ИМХО, «Рефакторинг, Стадия 1 скорее скрывает смысл, чем облегчает его понимание. Эта функция «дата не дает никакой реальной ценности, она просто мешает «сейчас куда-то еще. Я не уверен, что я даже ввел бы локальную переменную сейчас , и я почти уверен, что никогда не реорганизовал бы ее. Первая версия гораздо более читабельна для меня, и я предлагаю другим подумать, почему мы должны сделать тесты короче. Чем легче понять тест, тем лучше, особенно если сначала нужно написать тесты, а разрабатывать код только потом (разработка через тестирование или, что еще лучше, разработка через поведение). Насколько я вижу, имеет общее предположение, что он «нечитаемый и, следовательно, трудно поддерживаемый, в основном потому, что многие эксперты JS «сокращают свой код (то есть обфусцируют его).
Существуют и другие структуры для написания модульных тестов: https://github.com/moll/js-must https://github.com/visionmedia/should.js/ http://unitjs.com http: // nodejs .org / api / assert.html и многие другие
Comments
To leave a comment
Scripting client side JavaScript, jqvery, BackBone
Terms: Scripting client side JavaScript, jqvery, BackBone