Разработка через тестирование в JavaScript с использованием Qunit 31.05.2011

Разработка через тестирование — это один из способов разработки программного обеспечения, который состоит из множества повторяющихся итераций, включающих:

  1. написание теста, покрывающего желаемые изменения
  2. написание кода, который пройдет тест
  3. проведение рефакторинга

Сегодня я хочу вам показать этот метод на примере JavaScript и QUnit.

Немного о QUnit

QUnit — это  мощный, простой фреймворк для тестирования JavaScript. Его разрабатывают те же ребята, что делают jQuery. QUnit особенно полезен для интеграционного и регрессионного тестирования. Исходники можно взять на гитхабе http://github.com/jquery/qunit, а документацию почитать на http://docs.jquery.com/Qunit

Для быстрого старта, создайте такую страницу:

  1. <!DOCTYPE html>
  2.     <meta charset="UTF-8" />
  3.     <title>QUnit Test Suite</title>
  4.     <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
  5.     <script type="text/javascript" src="qunit/qunit.js"></script>
  6.     <script type="text/javascript">
  7.         module('Module 1');
  8.         test('test', function(){
  9.             ok(true);
  10.         });
  11.     </script>
  12. </head>
  13.     <h1 id="qunit-header">QUnit Test Suite</h1>
  14.     <h2 id="qunit-banner"></h2>
  15.     <div id="qunit-testrunner-toolbar"></div>
  16.     <h2 id="qunit-userAgent"></h2>
  17.     <ol id="qunit-tests"></ol>
  18.     <div id="qunit-fixture">test markup</div>
  19. </body>
  20. </html>

В итоге у вас должно получить нечто подобное

JQuery test suite

Техническое задание

Сделать селектбокс, с двумя кнопками «добавить» и «удалить»

  1. При нажатии на «добавить» селектбокс заменяется на текстовое поле, а кнопка «добавить» на «сохранить»
  2. При нажатии на «сохранить» содержимое поля добавляется пунктом в селектбокс, вид возвращается в исходное состояние
  3. При нажатии на «удалить» в режиме добавления возвращаться к исходному виду, в режиме отображение селектбокса удалять выбранный элемент, если список пуст показывать ошибку
  4. Селектбокс должен легко интегрироваться

Приступим

Мы имеем достаточно информации, так что вооружившись порядком действий из начала статьи и техническим заданием, приступим к выполнению. Первый пункт у нас «написание теста, покрывающего желаемые изменения». Определится с желаемыми изменениями нам поможет техническое задание. В нем есть пункт «Селектбокс должен легко интегрироваться», а это нас приводит к идеи о модульной структуре, опишем ее с помощью теста. Вернее сначала подготовим тестовую страницу, а затем напишем тест. Страница:

  1. <!DOCTYPE html>
  2.     <meta charset="UTF-8" />
  3.     <title>QUnit Test Suite</title>
  4.     <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
  5.     <script type="text/javascript" src="qunit/qunit.js"></script>
  6.     <!-- Наш исходник -->
  7.     <script type="text/javascript" src="src/selectbox.js"></script>
  8.     <!-- Наш тест -->
  9.     <script type="text/javascript" src="tests/selectbox.js"></script>
  10. </head>
  11.     <h1 id="qunit-header">QUnit Test Suite</h1>
  12.     <h2 id="qunit-banner"></h2>
  13.     <div id="qunit-testrunner-toolbar"></div>
  14.     <h2 id="qunit-userAgent"></h2>
  15.     <ol id="qunit-tests"></ol>
  16.     <div id="qunit-fixture">test markup</div>
  17. </body>
  18. </html>

тест:

  1. module('Selectbox');
  2.  
  3. test('Module structure'function(){
  4.     ok('selectbox' in window, 'selectbox not defined');
  5.     ok(typeof window.selectbox == 'Function', 'selectbox must be function');
  6. });

запустим на выполнение и получим следующее:

QUnit failed. Yeep, it work

Ура, тест успешно провален, а это значит что первый пункт нашего плана выполнен =) Переходим ко второму: «написание кода, который пройдет тест»

  1. var selectbox = (function(){
  2.     return function(){};
  3. })();

Запускаем тест, и получаем неожиданный результат. Он не прошел!

Test failed. Nooooooo

Давайте разбираться, первая часть теста пройдена, значит наш модуль экспортирован, но что же лежит в этой переменной? Функция! Но тест не пройден. Ответ кроется в нашем тесте, который написан с ошибкой =). Вы ведь заметили ее при первом прочтении? Тип нашего window.selectbox на самом деле function, а не Function. Вот так вот, в тестах тоже могут быть ошибки, по этой причине их рекомендуется делать как можно проще. Что же, исправим и порадуемся результату.

QUnit, one half path

Остался последний, очень важный пункт: проведение рефакторинга. Почему он важен? Мы собираемся писать высококачественный код, а одной характеристик такого кода является выдержанность стиля и соответствия стандартам (спецификациям, конвенциям, устным соглашениям).

  1. var selectbox = (function(){
  2.     var selectbox = function(){}
  3.  
  4.     return selectbox;
  5. })();

 

Итерация номер 2

И теперь все повторятся, нам снова нужно написать тест. Определимся с желаемыми изменениями: мы хотим вывести селектбокс в нужный нам контейнер. Напишем тест:

  1. test('Render'function(){
  2.     var wrapper = document.createElement('div');
  3.     var select = new selectbox();
  4.     select.render(wrapper);
  5.  
  6.     ok(wrapper.childNodes.length0);
  7. });

Этот тест не только не работает, но и бросает исключение, к счастью QUnit перехватывает его, и выводит как ошибку.

Пишем код

  1. // source
  2.  
  3. var selectbox = (function(document){
  4.     var selectbox = function(){
  5.         this._initDOM();
  6.     }
  7.  
  8.     selectbox.prototype._initDOM = function() {
  9.         this.selectbox = document.createElement('selectbox');
  10.  
  11.         this.buttonSave = document.createElement('button');
  12.         this.buttonSave.innerHTML'Save';
  13.  
  14.         this.buttonDelete = document.createElement('button');
  15.         this.buttonDelete.innerHTML'Delete';
  16.     }
  17.  
  18.     selectbox.prototype.render = function(wrapper) {
  19.         wrapper.appendChild(this.selectbox);
  20.         wrapper.appendChild(this.buttonSave);
  21.         wrapper.appendChild(this.buttonDelete);
  22.     }
  23.  
  24.     return selectbox;
  25. })(document);

После рефакторинга получим что-то похожее на это:

  1. var selectbox = (function(document){
  2.     var selectbox = function(){
  3.         this._initDOM();
  4.     }
  5.  
  6.     selectbox.prototype._initDOM = function() {
  7.         this.selectbox = document.createElement('selectbox');
  8.  
  9.         this.buttonSave = document.createElement('button');
  10.         this.buttonSave.innerHTML'Save';
  11.  
  12.         this.buttonDelete = document.createElement('button');
  13.         this.buttonDelete.innerHTML'Delete';
  14.  
  15.         this.container = document.createElement('div');
  16.         this.container.appendChild(this.selectbox);
  17.         this.container.appendChild(this.buttonSave);
  18.         this.container.appendChild(this.buttonDelete);
  19.     }
  20.  
  21.     selectbox.prototype.render = function(wrapper) {
  22.         wrapper.appendChild(this.container);
  23.     }
  24.  
  25.     return selectbox;
  26. })(document);

 

Итерация номер #

Что бы вас не утомлять полным процессом, я просто приведу список изменений, который я хотел внести и результат, который получил. В качестве тренировки, вы можете сделать все сами, а затем сравнить с моим результатом. Не забывайте про 3 этапа, и не заскакивайте вперед. Помните — всему свое время =)

  1. При нажатии на «добавить» селектбокс заменяется на текстовое поле, а кнопка «добавить» на «сохранить»
  2. При нажатии на «сохранить» содержимое поля добавляется пунктом в селектбокс, вид возвращается в исходное состояние
  3. При нажатии на «удалить» в режиме добавления возвращаться к исходному виду
  4. При нажатии на «удалить» в режиме отображение селектбокса удалять выбранный элемент, если список пуст показывать ошибку

Результат http://dron.by/files/qunit/page.html 
Тесты http://dron.by/files/qunit/test.html

PS: этот код не работает в IE, что можно легко выяснить с помощью тестов. Второе домашнее задание: сделать все кроссбраузерно.



Похожие статьи


  1. oleg:

    интересно. спасибо

Добавить комментарий