Введение в Jasmine
Программирование на стороне клиента давно стало нормой, а объем JavaScript кода и его сложность постоянно растут. Часто тестирование применяется только на серверной стороне, но при этом не стоит забывать о тестировании клиентского кода. Для тестирования JavaScript как на стороне клиента, так и для Node.js можно с успехом применять Jasmine.
Jasmine это BDD фреймворк (Behavior-Driven Development — Разработка на Основе Поведений) для тестирования JavaScript кода, позаимствовавший многие черты из RSpec.
Для удобства, будет рассматриваться тестирование в браузере, а для лаконичности примеры приводятся с использованием CoffeeScript (примеры на JavaScript).
Установить Jasmine можно скачав пакет Jasmine standalone. Потребуются файлы:
- lib/jasmine-*/jasmine.js — сам фреймворк
- lib/jasmine-*/jasmine-html.js — оформление результатов в виде HTML
- lib/jasmine-*/jasmine.css — внешний вид результата выполнения тестов
- SpecRunner.html — файл, который следует открыть в браузере для запуска тестов
Основными ключевыми словами при работе с Jasmine являются:
- describe — определение набора тестов, наборы могут быть вложенными
- it — определение теста внутри любого набора тестов
- expect — определяет ожидания, которые проверяются в тесте
Ключевые слова describe и it являются обычными вызовами функций, которым передаются два параметра. Первый — название группы или теста, второй — функция содержащая код. Простой пример для ясности:
Для того чтобы отключить выполнение набора тестов или конкретного теста, необходимо воспользоваться ключевыми словами xdescribe и xit соответственно.
Jasmine имеет стандартный набор ожиданий для проверки результатов:
Для того чтобы избежать повторения при создании/удалении объектов и загрузки фикстур, необходимых для выполнения тестов, используются функции beforeEach/afterEach. Они запускаются перед/после каждого теста в наборе.
Jasmine поддерживает тестирование асинхронных вызовов с помощью функций runs и waitsFor.
- runs — принимает асинхронную функцию для выполнения.
- waitsFor — принимает первым параметром функцию, которая должна вернуть true, если асинхронный вызов сделанный в runs был выполнен, второй параметр — сообщение об ошибке, третий — время ожидания в миллисекундах.
Рассмотрение работы со “шпионами” spies (mock object) и работы со временем (mock clock) оставим для следующей статьи. Если у Вас есть вопросы или замечания, буду рад на них ответить.
Your first suite
The describe function is for grouping related specs, typically each test file has one at the top level. The string parameter is for naming the collection of specs, and will be concatenated with specs to make a spec's full name. This aids in finding specs in a large suite. If you name them well, your specs read as full sentences in traditional BDD style.
Specs
Specs are defined by calling the global Jasmine function it, which, like describe takes a string and a function. The string is the title of the spec and the function is the spec, or test. A spec contains one or more expectations that test the state of the code. An expectation in Jasmine is an assertion that is either true or false. A spec with all true expectations is a passing spec. A spec with one or more false expectations is a failing spec.
It's Just Functions
Since describe and it blocks are functions, they can contain any executable code necessary to implement the test. JavaScript scoping rules apply, so variables declared in a describe are available to any it block inside the suite.
Expectations
Expectations are built with the function expect which takes a value, called the actual. It is chained with a Matcher function, which takes the expected value.
Matchers
Each matcher implements a boolean comparison between the actual value and the expected value. It is responsible for reporting to Jasmine if the expectation is true or false. Jasmine will then pass or fail the spec.
Any matcher can evaluate to a negative assertion by chaining the call to expect with a not before calling the matcher.
Jasmine has a rich set of matchers included, you can find the full list in the API docs There is also the ability to write custom matchers for when a project's domain calls for specific assertions that are not included in Jasmine.
Setup and Teardown
To help a test suite DRY up any duplicated setup and teardown code, Jasmine provides the global beforeEach, afterEach, beforeAll, and afterAll functions.
As the name implies, the beforeEach function is called once before each spec in the describe in which it is called
and the afterEach function is called once after each spec.
The beforeAll function is called only once before all the specs in describe are run
and the afterAll function is called after all specs finish
beforeAll and afterAll can be used to speed up test suites with expensive setup and teardown.
However, be careful using beforeAll and afterAll ! Since they are not reset between specs, it is easy to accidentally leak state between your specs so that they erroneously pass or fail.
The this keyword
Another way to share variables between a beforeEach , it , and afterEach is through the this keyword. Each spec's beforeEach / it / afterEach has the this as the same empty object that is set back to empty for the next spec's beforeEach / it / afterEach .
Note: If you want to use the this keyword to share variables, you must use the function keyword and not arrow functions.
Manually failing a spec with fail
The fail function causes a spec to fail. It can take a failure message or an Error object as a parameter.
Nesting describe Blocks
Calls to describe can be nested, with specs defined at any level. This allows a suite to be composed as a tree of functions. Before a spec is executed, Jasmine walks down the tree executing each beforeEach function in order. After the spec is executed, Jasmine walks through the afterEach functions similarly.
Disabling Suites
Suites can be disabled with the xdescribe function. These suites and any specs inside them are skipped when run and thus their results will show as pending.
Pending Specs
Pending specs do not run, but their names will show up in the results as pending .
Any spec declared with xit is marked as pending.
Any spec declared without a function body will also be marked pending in results.
And if you call the function pending anywhere in the spec body, no matter the expectations, the spec will be marked pending. A string passed to pending will be treated as a reason and displayed when the suite finishes.
Spies
Jasmine has test double functions called spies. A spy can stub any function and tracks calls to it and all arguments. A spy only exists in the describe or it block in which it is defined, and will be removed after each spec. There are special matchers for interacting with spies.
You can define what the spy will do when invoked with and.
The toHaveBeenCalled matcher will pass if the spy was called.
The toHaveBeenCalledTimes matcher will pass if the spy was called the specified number of times.
The toHaveBeenCalledWith matcher will return true if the argument list matches any of the recorded calls to the spy.
You get all of the data that a spy tracks about its calls with calls
Spies: createSpy
When there is not a function to spy on, jasmine.createSpy can create a "bare" spy. This spy acts as any other spy — tracking calls, arguments, etc. But there is no implementation behind it.
Spies: createSpyObj
In order to create a mock with multiple spies, use jasmine.createSpyObj and pass an array of strings. It returns an object that has a property for each string that is a spy.
Matching with more finesse
Sometimes you don't want to match with exact equality. Jasmine provides a number of asymmetric equality testers.
jasmine.any takes a constructor or "class" name as an expected value. It returns true if the constructor matches the constructor of the actual value.
jasmine.anything returns true if the actual value is not null or undefined .
jasmine.objectContaining is for those times when an expectation only cares about certain key/value pairs in the actual.
jasmine.arrayContaining is for those times when an expectation only cares about some of the values in an array.
jasmine.stringMatching is for when you don't want to match a string in a larger object exactly, or match a portion of a string in a spy expectation.
Custom asymmetric equality tester
When you need to check that something meets a certain criteria, without being strictly equal, you can also specify a custom asymmetric equality tester simply by providing an object that has an asymmetricMatch function.
Jasmine Clock
The Jasmine Clock is available for testing time dependent code.
It is installed with a call to jasmine.clock().install in a spec or suite that needs to manipulate time.
Be sure to uninstall the clock after you are done to restore the original functions.
Mocking the JavaScript Timeout Functions
You can make setTimeout or setInterval synchronous executing the registered functions only once the clock is ticked forward in time.
To execute registered functions, move time forward via the jasmine.clock().tick function, which takes a number of milliseconds.
Mocking the Date
The Jasmine Clock can also be used to mock the current date.
If you do not provide a base time to mockDate it will use the current date.
Asynchronous Support
Jasmine also has support for running specs that require testing asynchronous operations. The functions that you pass to beforeAll , afterAll , beforeEach , afterEach , and it can be declared async .
Jasmine also supports asynchronous functions that explicitly return promises or that take a callback. See the Asynchronous Work tutorial for more information.
This spec will not start until the promise returned from the call to beforeEach above is settled. And this spec will not complete until the promise that it returns is settled.
By default jasmine will wait for 5 seconds for an asynchronous spec to finish before causing a timeout failure. If the timeout expires before done is called, the current spec will be marked as failed and suite execution will continue as if done was called.
If specific specs should fail faster or need more time this can be adjusted by passing a timeout value to it , etc.
Тестируем JavaScript с Jasmine
Andrew Burgess Last updated Aug 4, 2011
Все мы знаем, что код нуждается в тестировании, но на деле мы часто забываем об этом. Я уверен, будет справедливо сказать, что большинство из нас в девяти случаях из десяти откладывает тестирование, поскольку это означает изучение ещё одной концепции. В этом уроке, я познакомлю вас с небольшим, но великим фреймворком для легкого тестирования JavaScript кода.
Шаг 0: Понимание BDD
Сегодня мы собираемся изучить Jasmine — BDD фреймворк для тестирования. Но мы немного остановимся здесь, чтобы поговорить кратко о BDD и TDD. Раскроем определение каждой из этих аббревиатур для тех, кто ещё не сталкивался с ними: BDD — это разработка, основанная на поведении, а TDD — это разработка через тестирование. В данный момент я ещё занимаюсь изучением всех особенностей каждой из этих технологий на практике, но уже могу выделить некоторые из основных отличий:
BDD и TDD . означают Разработку, основанную на поведении (функционировании) и Разработку, основанную на тестировании.
TDD в простой форме можно описать следующим образом:
- Пишешь тесты
- Отслеживаешь причины, по которым тесты не проходят
- Добиваешься успешного прохождения тестов
- Рефакторишь
- Повторяешь
Это довольно просто понять, не так ли?
BDD немного сложнее: насколько я понимаю это в данный момент, я не думаю, что бы вы или я как разработчик, работающий в одиночку, могли бы использовать его в полной мере, это больше командная вещь. Вот некоторые практические особенности BDD:
- Отлаживание целей различных заинтересованных сторон необходимых для реализации видений.
- Вовлечение заинтересованных сторон в процесс внедрения через внешнюю разработку программного обеспечения
- Использование примеров для описания поведения всего приложения или частей кода.
- Автоматизация этих тестов для обеспечения быстрой обратной связи и регрессионого тестирования
Для более близкого ознакомления вы можете прочитать обширную статью на Википедии (откуда были взяты перечисленные здесь пункты).
Подведем итоги ко всему выше сказанному, в то время как Jasmine представляет собой BDD фреймворк, мы собираемся использовать больше как TDD. Это не значит что мы будем использовать его неправильно. После того, как мы закончим обучение, вы с легкостью сможете проверить ваш JavaScript код . и я рассчитываю, что вы это сделаете!
Шаг 1: Изучаем синтаксис
Jasmine многое перенял от Rspec.
Если вы знакомы с Rspec, в рамках BDD фреймворка, то вы увидите, что Jasmine многое от него перенял. В Jasmine тестах можно выделить 2 важных части: describe блоки и блоки it . Давайте посмотрим как это работает.
Мы обязательно рассмотрим близкие к реальным ситуациям тесты, но пока разберем более простые:
Обе функции describe и it имеют 2 параметра: строку и функцию. Большинство тест фреймворков пытаются делать максимально удобными для чтения на английском, Jasmine не стал исключением. Обратите внимание, что строки, переданные в describe и в it формируют следующее предложение :»JavaScript оператор сложения складывает 2 числа вместе». Теперь рассмотрим как это работает.
Внутри блока it вы можете указать все настройки кода, который собираетесь протестировать. С этим вроде все понятно, приводить снова пример не будем. Когда вы соберетесь протестировать ваш код, вы начнете с того, что передадите его в функцию expect . Обратите внимание на такое предложение в примере:»expect 1+2 to equeal 3″.
Но я забегаю немного вперед. Как я уже сказал, любое значение, переданное в expect будет протестировано. Вызываемый метод, который возвращает expect , будет определен запускаемым тестом. Эта группа методов называется ‘вычислителями’, и мы рассмотрим некоторые из них сегодня. В этом случае мы используем вычислитель toEqual , который проверяет, что значение, переданное в expect совпадаем со значением переданным в toEqual .
Я думаю, вы готовы перейти на следующий этап, так давайте настроим простой проект используя Jasmine.
Шаг 2: Настройка проекта
Jasmine можно использовать сам по себе или интегрировать его в Rails проект. Мы воспользуемся первым вариантом. В то время, как Jasmine может работать вне браузера, мы можем получить действительно хороший небольшой шаблон с загрузки.
И так, перейдите на страницу загрузки и скачайте последнюю версию. Вы должны получить что-то вроде этого:
Актуальные файлы Jasmine фреймворка вы найдете в парке lib . Если вы предпочитаете структурировать ваши проекты по-другому, то, пожалуйста, мы же собираемся придерживаться пока такой структуре.
В этом шаблоне проекта есть пример кода. «Фактический» JavaScript (код, который мы хотим проверить) можно найти в подкаталоге src ; мы скоро поместим наш код. Код с тестами — спецификации — находится в папке spec . Не беспокойтесь о файле SpecHelper.js ; мы еще вернемся к нему.
Этот файл SpecRunner.html запускает тесты в браузере. Откройте его (и установите флажок «пройденный» в верхнем правом углу), и вы увидите что-то вроде этого:
Это показывает нам, что все тесты для нашего проекта проходят. После того, как вы пройдете этот учебник, я рекомендую вам открыть файл spec/PlayerSpec.js и ознакомиться с этим кодом. Но прямо сейчас давайте попробуем эту тестовую запись.
- Создайте convert.js в папке src .
- Создайте convertSpec.js в папке spec ,
- Скопируйте файл SpecRunner.html и переименуйте его SpecRunner.original.html .
Удалите ссылки на образцы файлов проекта в SpecRunner.html и добавьте следующие строки:
Теперь мы готовы создать мини-библиотеку, которая будет конвертировать данные между различными единицами измерения. Начнем с написания тестов для нашей мини-библиотеки.
Шаг 3: Написание тестов
Итак, давайте напишем наши тесты, не так ли?
Мы начнем с этого; мы тестируем нашу библиотеку Convert . Вы заметите, что здесь мы вставляем describe выражения. Это совершенно законно. На самом деле это отличный способ проверить отдельные фрагменты функциональных возможностей одной и той же базы кода. Вместо двух отдельных describe вызовов для библиотеки Convert и конвертации расстояний, а так же конвертации объемов, у нас может быть более описательный набор тестов, подобных этому.
Теперь фактические тесты. Я буду повторять внутренние describe вызовы здесь для вашего удобства.
Вот наши тесты для преобразования расстояний. Здесь важно заметить что мы еще не написали код для нашей библиотеки Convert , поэтому в этих тестах мы делаем больше, чем просто проверяем, работает ли это: мы фактически решаем, как мы будем пользоваться нашей библиотекой (и, следовательно то, как она должна быть реализована). Вот как мы решили сделать наши конвертации:
Да, я понимаю, как Jasmine выполнил свои тесты, но я думаю, что это хороший формат. Итак, в этих двух тестах я сам сделал конвератции (нормально, с калькулятором), чтобы узнать, каковы будут результаты наших вызовов. Мы используем toEqual matcher, чтобы проверить, проходят ли наши тесты.
Вот тесты для объемов:
И я собираюсь добавить еще два теста в наш вызов describe верхнего уровня:
Они проверяют ошибки, которые должны быть выброшены, когда неизвестные единицы передаются либо в функцию Convert , либо в метод to . Вы заметите, что я обертываю фактическое преобразование в функцию и передаю ее функции expect . Это потому, что мы не можем вызывать функцию в качестве параметра expect ; нам нужно передать ему функцию и дать ей вызов самой функции. Поскольку нам нужно передать параметр в функцию to , мы можем сделать это таким образом.
Важно отметить, что я представляю новый matcher: toThrow , который принимает объект ошибки. В ближайшее время мы рассмотрим еще несколько помощников.
Теперь, если вы откроете SpecRunner.html в браузере, вы получите следующее:
Отлично! Наши тесты терпят неудачу. Теперь давайте откроем наш файл convert.js и проделаем определенную работу:
Мы не собираемся обсуждать это, потому что здесь мы изучаем Jasmine. Но вот основные моменты:
- Мы делаем преобразования, сохраняя конвертацию в объекте; конвертации классифицируются по типу (расстояние, объем, добавьте свои собственные). Для каждого поля измерения мы имеем базовое значение (метры или литры, здесь), к которому все преобразовывается. Поэтому, когда вы видите yards: 0.9144 , вы знаете, что это количество ярдов в метрах. Затем, чтобы преобразовать ярды, скажем, в сантиметры, мы умножаем yards на первый параметр (чтобы получить количество метров), а затем разделим произведение на cm , количество метров в сантиметре. Таким образом, нам не нужно сохранять коэффициенты конверсии для каждой пары значений. Это также упрощает добавление новых значений позже.
- В нашем случае мы ожидаем, что переданные единицы будут такими же, как ключи, которые мы используем в таблице преобразования. Если бы это была настоящая библиотека, мы бы хотели поддерживать несколько форматов — « in», « inch» и « inches» — и поэтому мы хотели бы добавить некоторую логику, чтобы соответствовать fromUnit .
- В конце функции Convert мы сохраняем промежуточное значение в betweenUnit , которое инициализируется значением false . Таким образом, если у нас не будет fromUnit , betweenUnit будет false в методе to , и, следовательно будет выброшена ошибка.
- Если у нас нет toUnit , будет выброшена другая ошибка. В противном случае мы разделим по мере необходимости и вернем преобразованное значение.
Теперь вернитесь к SpecRunner.html и перезагрузите страницу. Вы должны увидеть это (после установки флажка «Показать успешные»):
Вот так! Наши тесты проходят. Если бы мы здесь разрабатывали настоящий проект, мы бы написали тесты на определенную часть функционала, заставляли их проходить, потом написали бы тесты для другой проверки, заставили бы их проходить и т.д. Но так как это был простой пример, мы только что сделали все это одним махом.
И теперь, когда вы видели этот простой пример использования Jasmine, давайте рассмотрим еще несколько функций, которые он нам предлагает.
Шаг 4: Изучение матчеров
До сих пор мы использовали два матчеры: toEqual и toThrow . Есть, конечно, и многие другие. Вот некоторые из них, которые вы, вероятно, найдете полезными; вы можете увидеть весь список в вики.
toBeDefined / toBeUndefined
Если вы просто хотите убедиться, что переменная или свойство определено, для этого есть совпадение. Также можно подтвердить, что переменная или свойство undefined .
toBeTruthy / toBeFalsy
Если что-то должно быть истинным или ложным, эти помощники сделают это.
toBeLessThan / toBeGreaterThan
Для всех математиков. Вы знаете, как они работают:
toMatch
Есть ли какой-нибудь выходной текст, который должен соответствовать регулярному выражению? Матчер toMatch готов для этого.
toContain
Этот очень полезен. Он проверяет, содержит ли массив или строка элемент или подстроку.
Есть еще несколько помощников, которые вы можете найти в вики. Но что, если вы хотите свой матчер? На самом деле, вы должны иметь возможность делать что угодно с некоторым кодом настройки и матчерами Jasmine, но иногда лучше отвлечь часть этой логики, чтобы получить более читаемый тест. Серьезно (ну, на самом деле нет), Jasmine позволяет нам создавать свои собственные матчи. Но для этого нам сначала нужно кое-что узнать.
Шаг 5: Выполнение до и после
Часто — при тестировании базы кода — вы хотите выполнить несколько строк кода настройки для каждого теста в серии. Было бы больно и многословно копировать это для каждого it вызова, поэтому у Jasmine есть удобная небольшая функция, которая позволяет нам назначать код для запуска до или после каждого теста. Давайте посмотрим, как это работает:
В этом надуманном примере вы можете видеть, как до запуска каждого теста состояние obj установлено в «clean». Если мы этого не сделали, изменения, внесенные в объект в предыдущем тесте, сохраняются до следующего теста по умолчанию. Конечно, мы могли бы сделать что-то подобное с функцией AfterEach :
Здесь мы создаем объект для начала, а затем исправляем его после каждого теста. Если вы хотите функцию MyObject , чтобы попробовать этот код, вы можете получить его здесь, на GitHub.
Шаг 6: Создание пользовательских матчеров
Как мы уже говорили ранее, клиентские помощники, вероятно, иногда могут быть полезны. Так что давайте напишем один. Мы можем добавить матчер либо к вызову BeforeEach , либо к вызову it (ну, я думаю, вы могли бы сделать это в вызове AfterEach , но это не имело бы большого смысла). Вот как вы начинаете:
Довольно просто, не так ли? Мы вызываем this.addMatchers , передавая ему параметр объекта. Каждый ключ в этом объекте станет именем матчера, и связанная с ним функция (значение) будет выполняться. Предположим, мы хотим создать матчер с проверкой, чтобы узнать, находится ли одно число между двумя другими. Вот что вы должны написать:
Мы просто берем два параметра, проверяем, что первый из них меньше второго, и возвращаем логическое выражение, которое выполняется в true, если выполняются наши условия. Здесь важно заметить, как мы получаем значение, которое было передано функции expect : this.actual .
Это то, что делает файл SpecHelper.js ; у него есть вызов beforeEach , который добавляет совпадение tobePlaying() . Проверьте это!
Заключение: повеселитесь!
С Jasmine вы многое можете сделать: функциональные тесты, шпионы, асинхронные спецификации и многое другое. Я рекомендую вам изучить вики, если вы заинтересованы. Есть также несколько сопутствующих библиотек, которые облегчают тестирование в DOM: Jasmine-jQuery и Jasmine-fixture (которая зависит от Jasmine-jQuery).
Поэтому, если вы до сих пор еще не тестируете свой JavaScript, сейчас самое подходящее время чтобы начать. Как мы видели, быстрый и простой синтаксис Jasmine делает процесс тестирование довольно простым. Просто уже не осталось причин, чтобы не начать писать тесты, не так ли?
Мануал по запуску тестов при помощи Karma и Jasmine
Karma — это консольный инструмент для запуска тестов, который умеет следить за изменениями исходного кода и отображать процент покрытия кода тестами. Настраивается с помощью конфигурационного файла karma.conf.js, в котором нужно указать пути к файлам, которые будут тестироваться, и пути к файлам, содержащие тесты.
Консольные команды
karma init — создаёт базовый шаблон файла конфигурации. Его можно также скачать с репозитория.
karma start — запуск
Jasmine
Пример теста
Установка и настройка компонентов
Скачивать компоненты мы будем с помощью пакетного менеджера npm, который входит в состав Node.js. Он также необходим для работы Karma.
После установки Node.js, переходим в корень проекта, вызываем консоль и поочерёдно выполняем команды. Все скачанные компоненты будут располагаться в каталоге node_modules.
Karma
npm install -g karma-cli
npm install phantomjs -g — эмулятор для тестирования javascript кода.
npm install karma-jasmine karma-chrome-launcher karma-phantomjs-launcher —save — karma-плагины для запуска тестов, написанных на Jasmine, для запуска в браузерах Chrome и PhantomJS.
npm install angular-mocks — библиотека с mock-объектами для сервисов AngularJS. С их помощью мы сможем тестировать работу сервиса $http, не отправляя запросов на сервер.
Jasmine
npm install -g jasmine
Чтобы использовать фреймворк Jasmine, подключите к проекту следующие файлы:
jasmine.js — сам фреймворк;
jasmine-html.js — оформление результатов в виде HTML;
jasmine.css — внешний вид результата выполнения тестов.
Также можно использовать следующие команды:
jasmine init — инициализирует Jasmine проект;
jasmine examples — помещает в Jasmine проект примеры с тестами.
Настройка файла конфигурации Karma
Пример файла karma.conf.js
Параметры karma.config.js:
В скобках () для каждого параметра указано значение по умолчанию:
files([]) — список файлов для загрузки. Массив тех файлов, которые будут подключены в браузер для запуска тестов;
exclude([]) — список исключений для предыдущего пункта;
reporters([‘progress’]) — вариант вывода прогресса;
port(8080) — порт веб-сервера;
runnerPort(9100) — порт клиента;
colors(true) — включение/выключение цветов при выводе лога в консоль;
logLevel(LOG_INFO) — LOG_DISABLE|LOG_ERROR|LOG_WARN|LOG_INFO|LOG_DEBUG;
autoWatch(false) — выполнение тестов при изменении файлов;
browsers([]) — Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS;
captureTimeout(5000) — задание таймаута в миллисекундах;
singleRun(false) — для одноразового запуска;
preprocessors(<>) — список обработчиков, которые будут применены к файлам, до загрузки в браузер.