Какая функция должна оборачивать тесты в тест фреймворке jasmine
What You'll Be Creating
Test Driven Development - это практика программирования, которую проповедовали и продвигали все сообщества разработчиков на планете. И все же это рутина, которая в значительной степени игнорируется разработчиком при изучении нового фреймворка. Написание модульных тестов с самого начала проекта поможет вам лучше писать код, выявлять ошибки и поддерживать более эффективный рабочий процесс разработки.
Step 1: Learning the Syntax
Jasmine takes a lot of cues from Rspec.
If you’re at all familiar with Rspec, the de facto BDD framework, you’ll see that Jasmine takes a lot of cues from Rspec. Jasmine tests are primarily two parts: describe blocks and it blocks. Let’s see how this works.
We’ll look at some closer-to-real-life tests in a few, but for now, we’ll keep it simple:
Both the describe and it functions take two parameters: a text string and a function. Most test frameworks try to read as much like English as possible, and you can see this with Jasmine. First, notice that the string passed to describe and the string passed to it form a sentence (of sorts): “JavaScript addition operator adds two numbers together.” Then, we go on to show how.
Inside that it block, you can write all the setup code you need for your test. We don’t need any for this simple example. Once you’re ready to write the actual test code, you’ll start with the expect function, passing it whatever you are testing. Notice how this forms a sentence as well: we “expect 1 + 2 to equal 3.”
But I’m getting ahead of ourselves. As I said, whatever value you pass into expect will be tested. The method you call, off the returned value of expect , will be determined by which test is run. This group of methods is called ‘matchers’, and we’ll be looking at several of them today. In this case, we’re using the toEqual matcher, which checks to see that the value passed to expect and the value passed to toEqual are the same value.
I think you’re ready to take this to the next level, so let’s set up a simple project using Jasmine.
Step 6: Writing Custom Matchers
Like we said earlier, customer matchers would probably be helpful at times. So let’s write one. We can add a matcher in either a BeforeEach call or an it call (well, I guess you could do it in an AfterEach call, but that wouldn’t make much sense). Here’s how you start:
Pretty simple, eh? We call this.addMatchers , passing it an object parameter. Every key in this object will become a matcher’s name, and the associated function (the value) will be how it is run. Let’s say we want to create a matcher that with check to see if one number is between two others. Here’s what you’d write:
We simply take two parameters, make sure the first one is smaller than the second, and return a boolean statement that evaluates to true if our conditions are met. The important thing to notice here is how we get a hold of the value that was passed to the expect function: this.actual .
This is what the SpecHelper.js file does; it has a beforeEach call that adds the matcher tobePlaying() . Check it out!
toBeLessThan / toBeGreaterThan
For all you number people. You know how these work:
Добавление новой пасты
Создайте компонент AddPaste с помощью Angular-CLI. На рисунке ниже изображен дизайн компонента AddPaste.
Логика компонента должна соответствовать следующим спецификациям.
- Шаблон компонента AddPaste должен иметь кнопку Create Paste.
- Нажатие кнопки Create Paste должно отображать модальный блок с идентификатором 'source-modal'.
- Действие click также должно обновить свойство showModal компонента в true . ( showModal - это логическое свойство, которое становится истинным, когда модальное окно отображается и false, когда оно закрывается.)
- Нажатие кнопки save вызовет метод addPaste() службы Pastebin.
- При нажатии кнопки close следует удалить идентификатор 'source-modal' из DOM и обновить свойство showModal в false .
Мы разработали первые три теста для вас. Посмотрите, проходят ли они успешно.
DebugElement.triggerEventHandler() - единственное новое для вас. Он используется для запуска события клика на элемент кнопки, на который он вызывается. Второй параметр - это объект события, и мы оставили его пустым, так как click() компонента не ожидает его.
1. API (application programming interface)
API в Jasmine и Mocha очень схожи. Они оба поддерживают написание тестов используя BDD (Behavior Driven Development) подход. Вы можете спросить: «что такое BDD»? Если кратко, это подход к написанию тестов, который предоставляет возможность описания функциональности на разговорном языке.
Утверждения (assertions), или ожидания(expectations), как их часто называют, различаются в представленых фреймворках. Mocha не имеет встроеной assertion библиотеки. Существует несколько вариантов для использования в среде Node.js и для браузеров: Chai, should.js, expect.js, and better-assert. Большинство разработчиков выберают Chai в качестве assertion библиотеки. Так как ни одной из assertion библиотек нет в поставке с Mocha, вам нужно будет подключить ее в вашу тестовую среду. В Chai сужествует три типа assertions:
1) should (должен)
2) expect (ожидать)
3) assert (утверждать)
Тип expect аналогичен expect который предоставляет нам фреймворк Jasmine. Например если вы хотите написать проверку метода add и ваше ожидание что calculator.add(1, 4) будет равняться 5, то данная проверка будет аналогично выглядить используя как Jasmine так и Chai.
Очень похоже, верно? Если вы переходите с Jasmine на Mocha то путь довольно простой — использывать Chai c типом expect.
Conclusion: Having Fun Yourself!
There’s a lot more you can do with Jasmine: function-related matchers, spies, asynchronous specs, and more. I recommend you explore the wiki if you’re interested. There are also a few accompanying libraries that make testing in the DOM easier: Jasmine-jQuery, and Jasmine-fixture (which depends on Jasmine-jQuery).
So if you aren’t testing your JavaScript so far, now is an excellent time to start. As we’ve seen, Jasmine's fast and simple syntax makes testing pretty simple. There’s just no reason for you not to do it, now, is there?
If you want to take your JavaScript development further, why not check out the range of JavaScript items on Envato Market? There are thousands of scripts, apps and code snippets to help you.
Step 0: Understanding BDD
Today, we’re going to be learning about the Jasmine BDD testing framework. But we’re stopping here for a detour first, to talk very briefly, about BDD and TDD. If you’re not familiar with these acronyms, they stand for Behaviour-Driven Development and Test-Driven Development. I’m in the middle of learning about what each of these is in practice and how they are different, but here are some of the basic differences:
BDD and TDD … stand for Behaviour-Driven Development and Test-Driven Development.
TDD in its simplest form is just this:
- Write your tests
- Watch them fail
- Make them pass
- Refactor
- Repeat
That’s pretty easy to understand, eh?
BDD is a little more complex: as I understand it right now, I don’t think that you or I as a single developer can actually practice it fully; it’s more of a team thing. Here are a few of the practices of BDD:
- Establishing the goals of different stakeholders required for a vision to be implemented
- Involving stakeholders in the implementation process through outside-in software development
- Using examples to describe the behavior of the application, or of units of code
- Automating those examples to provide quick feedback and regression testing
To learn more, you can read the extensive Wikipedia Article (from which those points were taken).
All this to say that, while Jasmine bills itself as a BDD framework, we’re going to be using it in a more TDD-style way. That doesn’t mean we’re using it wrong, though. Once we’re finished, you’ll be able to test your JavaScript with ease … and I expect you to do it!
Step 2: Setting up a Project
Jasmine can be used by itself; or you can integrate it with a Rails project. We’ll do the former. While Jasmine can run outside the browser (think Node, among other places), we can get a really nice little template with the download.
So, head on over to the standalone download page and get the latest version. You should get something like this:
You’ll find the actual Jasmine framework files in the lib folder. If you prefer to structure your projects differently, please do so; but we’re going to keep this for now.
There’s actually some sample code wired up in this project template. The “actual” JavaScript ( the code we want to test) can be found in the src subdirectory; we’ll be putting ours there shortly. The testing code—the specs—go in the spec folder. Don’t worry about the SpecHelper.js file just yet; we’ll come back to that.
That SpecRunner.html file is what runs the tests in a browser. Open it up (and check the “passed” checkbox in the upper right corner), and you should see something like this:
This shows us that all the tests for the sample project are passing. Once you get through this tutorial, I recommend you open up the spec/PlayerSpec.js file and peruse that code. But right now, let’s give this test writing stuff a try.
- Create convert.js in the src folder.
- Create convertSpec.js in the spec folder,
- Copy the SpecRunner.html file and rename it SpecRunner.original.html .
Remove the links to the sample project files in SpecRunner.html and add these lines:
Now we’re ready to create a mini-library that will convert between measurement units. We’ll start by writing the tests for our mini-library.
toBeTruthy / toBeFalsy
If something should be true or false, these matchers will do it.
Настройка Angular-in-Memory-Web-API
Установите angular-in-memory-web-api через npm:
Обновите AppModule до этой версии.
Создайте InMemoryDataService , который реализует InMemoryDbService .
Разработка с использованием тестов в Angular
Angular является полно-функциональной платформой для фронт-енд разработки и имеет собственный набор инструментов для тестирования. В этом учебнике мы будем использовать следующие инструменты:
- Jasmine Framework. Jasmine - популярный фреймворк для тестирования JavaScript. С Jasmine вы можете написать тесты, которые будут более выразительны и понятны. Вот пример для начала.
- Karma Test Runner. Karma - это инструмент, который позволяет тестировать ваше приложение в нескольких браузерах. У Karma есть плагины для браузеров, таких как Chrome, Firefox, Safari и многих других. Но я для тестирования предпочитаю использовать headless браузер. В headless браузере отсутствует графический интерфейс, и таким образом вы можете сохранить результаты теста внутри своего терминала. В этом уроке мы настроим Karma для работы с Chrome и, а также дополнительно с headless версией Chrome.
- Angular Testing Utilities. Утилиты тестирования Angular предоставляют вам библиотеку для создания тестовой среды для вашего приложения. Классы, такие как TestBed и ComponentFixtures , и вспомогательные функции, такие как async и fakeAsync , являются частью пакета @angular/core/testing . Знакомство с этими утилитами необходимо, если вы хотите написать тесты, которые показывают, как ваши компоненты взаимодействуют со своими собственными шаблонами, сервисами и другими компонентами.
В этом уроке мы не собираемся описывать функциональные тесты с помощью Protractor. Protractor - популярный сквозной тестовый фреймворк, который взаимодействует с пользовательским интерфейсом приложения, используя фактический браузер.
В этом уроке мы больше озабочены тестированием компонентов и их логики. Тем не менее, мы напишем пару тестов, которые демонстрируют базовое взаимодействие с пользовательским интерфейсом, используя фреймворк Jasmine.
toBeDefined / toBeUndefined
If you just want to make sure a variable or property is defined, there’s a matcher for that. There’s also one to confirm that a variable or property is undefined .
toContain
This one is pretty useful. It checks to see if an array or string contains an item or substring.
There are a few other matchers, too, that you can find in the wiki. But what if you want a matcher that doesn’t exist? Really, you should be able to do just about anything with some set-up code and the matchers Jasmine provides, but sometimes it’s nicer to abstract some of that logic to have a more readable test. Serendipitously (well, actually not), Jasmine allows us to create our own matchers. But to do this, we’ll need to learn a little something else first.
Step 5: Covering Before and After
Often—when testing a code base—you’ll want to perform a few lines of set-up code for every test in a series. It would be painful and verbose to have to copy that for every it call, so Jasmine has a handy little feature that allows us to designate code to run before or after each test. Let’s see how this works:
In this contrived example, you can see how, before each test is run, the state of obj is set to “clean”. If we didn’t do this, the changed made to an object in a previous test persist to the next test by default. Of course, we could also do something similar with the AfterEach function:
Here, we’re setting up the object to begin with, and then having it corrected after every test. If you want the MyObject function so you can give this code a try, you can get it here in a GitHub gist.
3. Асинхронные тесты (Asynchronous Tests)
Асинхронное тестирование в Jasmine 2.x и Mocha реализуется аналогично.
В данном примере User — функция конструктор у которой есть статический метод get. Метод get внутри себя использует метод fetch который выполняет XHR запрос (request). Я хочу проверить, что когда метод get получит значение, то это значение будет экземпляром (instance) User. Так как я использовал «stub» для метода User.prototype.fetch и указал ему вернуть заранее определенный promise, реальный XHR запрос не выполняется. Покрытие данного кода продолжает быть асинхронным.
Очень просто указать, что callback функция в it конструкции ожидает аргумент (в данном случаи done) и test runner будет ожидать пока выполнится функция до того как закончит тест. Тест будет приостановлен и выводится ошибка, если аргумент не будет вызван в течении определенного времени. Это дает полный контроль над тем, когда тесты закончат выполнение. Тест, написаный выше, будет работать как в Jasmine так и в Mocha.
Если вы работаете с Jasmine 1.3 асинхронное тестирование выглядит не так «чисто».
Пример асинхронного тестирования в Jasmine 1.3:
В данном примере, для завершения асинхронной операции тест будет ожидать максимум 500 милисекунд, в ином случае тест будет провален. Функция waitsFor() постоянно проверяет flag, как только flag станет true выполнение продолжится и будет вызван следующий runs блок.
2. Test Doubles (Дублеры)
Test Doubles заменяют один обьект на другой для тестовых целей. В Jasmine роль test doubles выполняют spies(шпионы). Spy — это функция которая заменет оригинальную функцию, логику которой мы хотим изменить и описывает как данная функция будет использоваться в рамках выполнения теста.
Spies позволяют:
1) Узнать количество раз которые вызывалась spy функция
2) Указать возвращаемое значение для того, чтобы тестируемый код продолжил работать по нужному алгоритму.
3) Указать, чтобы spy функция бросила ошибку.
4) Узнать с какими аргументами была вызвана spy функция.
5) Указать, чтобы spy функция вызвала оригинальную функцию. (Которую она заменила)
В Jasmine создать spy для существующего метода возможно так:
Также возможно создать spy, даже если у вас нет метода который вы хотите подменить.
Mocha не имеет встроеной test doubles библиотеки по этому вам нужно загрузить и подключить Sinon в вашу тестовую среду. Sinon очень мощная Test Doubles библиотека которая предоставляет эквивалентный функционал spies в Jasmine и немного больше. Стоит отметить, что Sinon разбивает test doubles на три разные категории: spies, stubs и mocks, между которыми есть тонкие отличия.
Spy функция в Simon вызывается посредством оригинального метода, в то время как в Jasmine это поведение нужно указывать. Например:
В данном примере, будет вызван оригинальный user.isValid.
Следующий тип test doubles это stubs который заменяет оригинальный метод. Поведение stubs аналогично с поведением по умолчанию spies в Jasmine, в котором оригинальный метод не вызывается.
В данном примере, если будет вызван метод user.isValid, оригинальный метод user.isValid вызван не будет, а будет вызвана его поддельная версия которая должна вернуть результат «true».
Из своего опыта, spies в Jasmine покрывают почти все что требуется для test doubles, по этому в большинстве случаев вам не нужно будет Sinon если вы используете Jasmine, однако, если вы хотите, у вас есть возможность использовать их совместно. Основная причина, по которой я использую Sinon вместе с Jasmine, это его fake server.
Начало работы с компонентами
Компоненты - это самый базовый строительный блок пользовательского интерфейса в приложении Agnular. Angular приложение представляет собой дерево компонентов.
- Angular документация
Как было отмечено ранее в разделе «Обзор», мы будем работать над двумя компонентами в этом уроке: PastebinComponent и AddPasteComponent. Компонент Pastebin состоит из таблицы, в которой перечислены все вставки, полученные с сервера. Компонент AddPaste содержит логику создания новых паст.
Наша цель
Цель этого учебника - создать интерфейс для приложения Pastebin в тестовой среде разработки. В этом уроке мы будем следовать популярной мантре TDD: «красный/зеленый/рефакторинг». Мы будем писать тесты, которые первоначально не выполняются (красные), а затем мы работаем над нашим кодом, чтобы они проходили (зеленые). Мы так же будем рефакторить наш код, когда он начнет вонять, что означает, что он раздувается и становится уродлив.
Мы будем писать тесты для компонентов, их шаблонов, сервисов и класса Pastebin. Изображение ниже иллюстрирует структуру нашего приложения Pastebin. Элементы, которые выделены серым цветом, будут рассмотрены во второй части учебника.
В первой части серии мы сосредоточимся только на создании тестовой среды и написании базовых тестов для компонентов. Angular является фреймворком, основанным на компонентах; поэтому необходимо потратить некоторое время, чтобы познакомиться с написанием тестов для компонентов. Во второй части серии мы напишем более сложные тесты для компонентов, компонентов с входными данными, маршрутизируемыми компонентами и службами. К концу серии у нас будет полностью функционирующее приложение Pastebin, которое будет выглядеть так.
Вид компонента Pastebin Вид компонента AddPaste
Вид компонента ViewPaste
В этом уроке вы узнаете, как:
- настроить Jasmine и Karma
- создать класс Pastebin
- создать каркас для PastebinService
- создать два компонента, Pastebin и AddPaste
- написать единичные тесты
Весь код для учебника доступен на Github.
Клонируйте репозиторий и не стесняйтесь проверить код, если у вас возникнут сомнения на любом этапе этого урока. Давайте начнем!
toMatch
Have some output text that should match a regular expression? The toMatch matcher is ready and willing.
4. Sinon Fake Server
Одна особенность которую имеет Sinon в сравнении с Jasmine это fake server (поддельный сервер). Это позволяет установить поддельные ответы на определенные AJAX запросы.
Jasmine фреймворк включает в себя почти все что необходимо, включая assertions, test doubles (реализован через spies) функциональность. Mocha не обладает такой функциональностью, но предоставлет выбор библиотеки для assertions (самая популярная Chai). Для test doubles Mocha также требует подключения дополнительной библиотеки, в большинстве случаев это sinon.js. Sinon также может быть отличным дополнением, предоставляя свой fake server (поддельный сервер).
Выбрать тестовый фреймворк для JS может быть трудной задачей, надеюсь что данная статья помогла вам сделать правильный выбор. В любом случае, что бы вы ни использовали, вы не ошибетесь. Удачного тестирования!
We all know we should be testing our code, but we don’t actually do it. I guess it’s fair to say that most of us put it off because, nine times out of ten, it means learning yet another concept. In this tutorial, I’ll introduce you to a great little framework for testing your JavaScript code with ease.
By the way, did you know that you can have your JavaScript errors fixed quickly and easily by an expert on Envato Studio?
ThemeManiac, for example, will fix JavaScript errors or browser compatibility issues on your website or web application. The fixes can be completed very fast, based on the complexity and available information. He can also reorganize your scripts and make a completely new user experience. He has completed more than 1,000 jobs on Envato Studio, with 99% of customers recommending him.
Step 3: Writing the Tests
So, let’s write our tests, shall we?
We start with this; we’re testing our Convert library. You’ll notice that we’re nesting describe statements here. This is perfectly legal. It’s actually a great way to test seperate functionality chunks of the same codebase. Instead of two seperate describe calls for Convert library’s distance conversions and volume conversions, we can have a more descriptive suite of tests like this.
Now, onto the actual tests. I’ll repeat the inner describe calls here for your convenience.
Yes, I’m taking a cue from the way Jasmine has implemented its tests, but I think it’s a nice format. So, in these two tests, I’ve made the conversions myself (ok, with a calculator) to see what the results of our calls should be. We’re using the toEqual matcher to see if our tests pass.
Here’s the volume tests:
And I’m going to add two more tests in our top-level describe call:
These check for errors that should be thrown when unknown units are passed into either the Convert function or the to method. You’ll notice that I’m wrapping the actual conversion in a function and passing that to the expect function. That’s because we can’t call the function as the expect parameter; we need to hand it a function and let it call the function itself. Since we need to pass a parameter to that to function, we can do it this way.
The other thing to note is that I’m introducing a new matcher: toThrow , which takes an error object. We’ll look at some more matchers soon.
Now, if you open SpecRunner.html in a browser, you’ll get this:
Great! Our tests are failing. Now, let’s open our convert.js file and do some work:
We’re not really going to discuss this, because we’re learning Jasmine here. But here are the main points:
- We’re making the conversions by storing the conversion in an object; conversion numbers are classified by type (distance, volume, add your own). For each field of measurement, we have a base value (meters or liters, here) that everything converts to. So when you see yards: 0.9144 , you know that that’s how many yards there are in a meter. Then, to convert yards to, say, centimeters, we multiply yards by the first parameter (to get the number of meters) and then divide the product by cm , the number of meters in a centimeter. This way, we don’t have to store the conversion rates for every pair of values. This also makes it easy to add new values later.
- In our case, we’re expecting the units passed in to be the same as the keys we’re using in the conversion “table.” If this were a real library, we’d want to support multiple formats—like ‘in’, ‘inch’, and ‘inches’—and therefore we’d want to add some logic to match the fromUnit to the right key.
- At the end of the Convert function, we store the intermediate value in betweenUnit , which is initialized to false . That way, if we don’t have the fromUnit , betweenUnit will be false going into the to method, and so an error with be thrown.
- If we don’t have the toUnit , a different error will be thrown. Otherwise, we’ll divide as neccessary and return the converted value.
Now, go back to SpecRunner.html and reload the page. You should now see this (after checking “Show passed”):
There you go! Our tests are passing. If we were developing a real project here, we would write tests for a certain chunk of functionality, make them pass, write tests for another check, make them passs, etc. But since this was a simple example, we’ve just done it all in one fell swoop.
And now that you’ve seen this simple example of using Jasmine, let’s look at a few more features that it offers you.
Создание класса Pastebin
Нам нужен класс Pastebin для моделирования нашего Pastebin внутри компонентов и тестов. Вы можете создать его с помощью Angular-CLI.
Добавьте в Pastebin.ts следующую логику:
Мы определили класс Pastebin, и каждый экземпляр этого класса будет иметь следующие свойства:
Создайте еще один файл с именем pastebin.spec.ts для набора тестов.
Набор тестов начинается с блока describe , который является глобальной функцией Jasmine, которая принимает два параметра. Первый параметр - это название набора тестов, а второй - его фактическая реализация. Спецификации определяются с использованием функции it , которая принимает два параметра, аналогичные параметрам блока describe .
Множество спецификаций (блоков it ) можно вставить внутри набора тестов (блок describe ). Тем не менее, убедитесь, что имена тестовых наборов названы так, что они недвусмысленны и читаемы, потому что они предназначены для использования в качестве документации для читателя.
Ожидания, реализованные с использованием функции expect , используются Jasmine для определения того, должна ли спецификация проходить или терпеть неудачу. Функция expect принимает параметр, который известен как фактическое значение. Затем он соединен цепью с другой функцией, которая принимает ожидаемое значение. Эти функции называются вспомогательными функциями, и в этом учебнике мы будем использовать функции матчинга, такие как toBeTruthy() , toBeDefined() , toBe() и toContain() .
Таким образом, с помощью этого кода мы создали новый экземпляр класса Pastebin и ожидаем, что это будет правдой. Давайте добавим еще одну спецификацию, чтобы подтвердить, что модель Pastebin работает так как нужно.
Мы создали экземпляр класса Pastebin и добавили несколько ожиданий в нашу тестовую спецификацию. Запустите ng test , чтобы проверить, что все тесты зеленые.
Step 4: Learning the Matchers
So far, we’ve used two matchers: toEqual and toThrow . There are, of course, many others. Here are a few you’ll probably find useful; you can see the whole list on the wiki.
Резюме
Вот и все. В этой первой статье мы узнали:
- как настроить и настроить Jasmine и Karma
- как написать базовые тесты для классов
- как проектировать и писать модульные тесты для компонентов
- как создать базовый сервис
- как использовать утилиты тестирования Angular в нашем проекте
В следующем уроке мы создадим новые компоненты, напишем больше тестовых компонентов с входными данными, сервисами и маршрутами. Оставайтесь с нами во второй части серии. Поделитесь своими мыслями в комментариях.
Программирование на стороне клиента давно стало нормой, а объем 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.
Рассмотрение работы со “шпионами” spies (mock object) и работы со временем (mock clock) оставим для следующей статьи. Если у Вас есть вопросы или замечания, буду рад на них ответить.
Во второй части я покрыл процесс оптимизации моего браузерного приложения Tube Tracker, но каждое вносимое мной изменение до сих пор требует обновление браузера, чтобы проверить, что все работает. Приложение всерьез требует набора тестов, чтобы ускорить процесс разработки и избежать регрессии кода. Как оказалось, это проще сказать, чем сделать, когда начинаешь работать с новой технологией, как React.
Настройка тестирования
Я использую тестовый фреймворк Jasmine, так как он прост в установке и широко используется, в том числе в библиотеке React. Приложение теперь содержит папку test , с двумя директориями; в папке lib скрипты для запуска тестов и папка spec , в которой находятся сами тесты:
В дополнение к окружениям разработки и продакшна, которые я описал в предыдущей части, я добавил тестовое окружение, для того чтобы связать вместе приложение и тесты. Чтобы сделать это, я включил все файлы тестов(specs) в файл suite.js и использовал его, как входную точку для Browserify:
Создание тестового окружения может быть улучшено при помощи некоторой дополнительной автоматизации, но базовый процесс и так работает. Простота установки также означает то, что тесты запускаются в браузере, а не в специальном окружении, как например jsdom, что я и предпочитаю.
Примечание: Я переключился с использования Bower на NPM дистрибутив React. В Bower версии React'а утилиты для тестирования и другие дополнения поставляются вместе с ядром библиотеки, а это значит, что ядро может быть дважды включено в тестовом окружении. Это вызывает конфликты между компонентами объявленными в разных пакетах. Использование дистрибутива NPM позволяет Browserify построить каждый пакет только с необходимыми ему зависимостями, избегая дублирования.
Тестирование компонентов React
Если считать, что React это V(представление) в MVC, то, теоретически, должен тестироваться только вывод компонентов, но компоненты React зачастую содержат логику для обработки динамического поведения, а простые приложения могут только из них и состоять. Для примера, внутри компонентов приложения Tube Tracker содержится логика для валидации ввода пользователя, установка AJAX пула(poll) и отображение состояния. Следовательно, тестирование одного вывода не предоставит достаточно информации, если внутри что-то сломается, так что тестирование внутренней реализации также необходимо.
Тестовые инструменты React
Чтобы немного облегчить тестирование React компонентов, разработчики React предоставили инструменты для тестирования (TestUtils). Дополнение, которое, вероятно, будет первым, что вы найдете поискав информацию о тестировании React приложений. Оно может быть использовано, подключив в тестовые файлы пакет React с аддонами. В пространстве имен React.addons.TestUtils содержатся методы для симуляции событий, выборки по компонентам и тестирования их типов.
Есть очень полезный метод renderIntoDocument , который может рендерить компоненты в анонимный DOM узел, но для некоторых тестов все же остается необходимость указывать контейнер, например для захвата событий или тестирования жизненного цикла компонента при его уничтожении:
TestUtils очень упрощает взаимодействие и тестирование вывода компонентов, но это не касается исследования их внутренней реализации.
Исследование реализации компонентов
Представления приложений, если работать по шаблону MVC, не содержат никакой логики, не считая нескольких циклов или условий, вся остальная логика должна быть вынесена в презентер. React приложения не укладываются в эту модель, компоненты могут, сами по себе, быть небольшими приложениями и некоторые их внутренности нуждаются в исследовании.
Приложение Tube Tracker содержит компоненты до четырех уровней вложенности, и большая часть логики приложения находится внутри них.
Вы далеко не продвинетесь, пытаясь протестировать все методы компонентов, так как, несмотря на то, что методы могут быть вызваны, вы не сможете их модифицировать, по крайней мере без копания во внутренностях React'а. Таким образом, установка стабов и моков не сработает, что сначала может показаться проблемой.
Решение в том, чтобы не создавать слепых зон для тестирования. Если вы начинаете чувствовать, что какой-то кусок логики, который напрямую не влияет на вывод, должен быть доступен для тестирования, абстрагируйте этот код. Внешняя логика компонента, таким образом может быть изолирована.
Изолирование CommonJS модулей
Нам нужно тестировать каждый модуль изолированно, так как работа со всем древом компонентов может быть неэффективной при отладке ошибок и приводит к тому, что тесты работают не вполне независимо. Проблема в том, что модули CommonJS создают свою собственную область видимости и только к их публичным свойствам, можно обратиться из зависимых компонентов. Это порождает проблему с тестированием, так как зависимости модуля не всегда объявлены публичными. Например в приложении Tube Tracker компонент tube-tracker.js содержит зависимости network.js и predictions.js :
Чтобы обойти нехватку видимости, я могу модифицировать модули таким образом, чтобы ихние зависимости поставлялись им извне, вместо того, чтобы быть созданными внутри них, это базовый шаблон инверсии зависимостей (IoC). Без какого-то способа внедрения зависимостей(dependency injection), использование шаблона IoC может привести к спагетти зависимостям. Но внедрение зависимостей не очень-то популярная вещь в JavaScript проектах, так как оно требует строгого следования соглашениям, а её реализация бывает очень разной.
К счастью, есть множество более простых способов проникновения и замены CommonJS модулей. Для node.js существует Rewire, браузерная версия этого инструмента может быть построена трансформацией Rewireify доступной для Browserify:
Rewireify очень прост, он внедряет __get__ и __set__ методы в каждый модуль, чтобы их внутренние свойства могли быть доступны извне. Зависимости модулей теперь могут быть заменены стабами:
Подмена зависимостей теперь очень проста, но компоненты нуждаются в особом обращении. TestUtils предоставляет метод mockComponent , который позволяет подменивать вывод переданного компонента, но это, в основном и все, что он может делать. На самом деле, иногда удобнее подменять целые компоненты, особенно для асинхронных тестов.
Jest, недавно созданная командой Facebook обертка для Jasmine, это альтернативный способ подмены зависимостей CommonJS. Документация по использованию Jest с React доступна здесь.
Асинхронное тестирование компонентов
Не все тесты можно заставить выполняться синхронно, в случае приложения Tube Tracker, компонент Predictions будет всегда показывать экземпляр Message перед отображением DepartureBoard . Отсутствие возможности проследить(spy) или подменить(stub) методы жизненного цикла компонента, например componentDidMount или componentWillUnmount , является проблемой, так как вы не сможете узнать, когда компонент создится или разрушится.
Чтобы обойти это ограничение, я создал функцию для обеспечения лучшей подмены компонентов. Функция принимает обратные вызовы для методов жизненного цикла, таким образом становится очень удобно вставлять обратные вызовы при выполнении тестов:
Тестирование моих React приложений оказалось намного сложнее, чем я ожидал. Это новая технология и мы до сих пор учимся, как ее лучше всего использовать. Я должен был создать Rewireify и я потратил много времени на изучение внутренностей React’а. Я не говорю, что все что я сделал, это лучшие практики, но есть не так много информации о том, как это должно работать. Самое главное, что это работает:
Вы можете попробовать приложение прямо сейчас (внимание: пример запущен на бесплатном аккаунте, так что эта ссылка может быть неустойчивой) или пройти на GitHub, чтобы посмотреть исходный код. Пожалуйста, комментируйте или твитайте мне, я буду рад получить отзывы.
Тестирование в JS становится все более распространенной практикой. Но с чего начать? Существует множество фреймворков которые предоставляют API для написания JS тестов.
Данная статья — это краткий обзор различий между двумя популярными фреймворками для тестирования JS: Jasmine 2 и Mocha. Мы также обсудим наиболее полулярные библиотеки Chai и Sinon которые часто используются в связке с Jasmine и Mocha.
Создание каркаса для службы
Создайте службу, используя следующую команду.
Проектирование и тестирование компонента Pastebin
Идем дальше и создаем компоненты с помощью Angular-CLI.
Здесь многое происходит. Давайте разложим его и рассмотрим по блокам. Внутри блока describe мы объявили некоторые переменные, а затем мы использовали функцию beforeEach . beforeEach() является глобальной функцией, предоставляемой Jasmine, и, как следует из названия, она вызывается один раз перед каждой спецификацией в блоке describe , в котором она вызывается.
Класс TestBed является частью утилит тестирования Angular, и он создает модуль тестирования, аналогичный модулю @NgModul e. Кроме того, вы можете настроить TestBed с помощью метода configureTestingModule . Например, вы можете создать тестовую среду для своего проекта, которая эмулирует фактическое приложение Angular, и затем вы можете вытащить компонент из вашего прикладного модуля и снова присоединить его к этому тестовому модулю.
Метод createComponent возвращает компонент ComponentFixture , дескриптор тестовой среды, окружающей созданный компонент. Фикстура обеспечивает доступ к самому экземпляру компонента и к DebugElement , который является дескриптором элемента DOM компонента.
Как уже упоминалось выше, мы создали фикстуру PastebinComponent , а затем использовали эту фикстуру для создания экземпляра компонента. Теперь мы можем получить доступ к свойствам и методам компонента внутри наших тестов, вызвав имя comp.property_name . Поскольку фикстура также обеспечивает доступ к debugElement , мы теперь можем запросить элементы DOM и селектора.
В нашем коде есть проблема, о которой мы еще не подумали. Наш компонент имеет внешний шаблон и файл CSS. Извлечение и чтение их из файловой системы - это асинхронная активность, в отличие от остальной части кода, которая является синхронной.
Angular предлагает вам функцию async() , которая заботится обо всех асинхронных вещах. async занимается тем, что отслеживает все асинхронные задачи внутри него, скрывая от нас сложность асинхронного выполнения. Итак, теперь у нас будет две функции beforeEach, асинхронная beforeEach() и синхронная beforeEach() .
Мы еще не написали никаких тестовых спецификаций. Тем не менее, это хорошая идея, чтобы заранее составить схему спецификаций. На рисунке ниже изображен грубый дизайн компонента Pastebin.
Нам нужно написать тесты со следующими ожиданиями.
- Компонент Pastebin должен существовать.
- Свойство title компонента должно отображаться в шаблоне.
- Шаблон должен иметь таблицу HTML для отображения существующих паст.
- pastebinService вводится в компонент, и его методы доступны.
- Пасты не должны отображаться до тех пор, пока не вызывается onInit() .
- Пасты не отображаются до тех пор, пока обещание в нашем Service не будет разрешено.
Первые три теста легко реализовать.
В тестовой среде Angular автоматически не связывает свойства компонента с элементами шаблона. Вы должны явно вызвать fixture.detectChanges() каждый раз, когда вы хотите связать свойство компонента с шаблоном. Запуск теста должен дать вам ошибку, потому что мы еще не объявили свойство title внутри нашего компонента.
Не забудьте обновить шаблон базовой структурой таблицы.
Что касается остальных случаев, нам нужно ввести Pastebinservice и написать тесты, которые касаются взаимодействия между компонентами. Реальная служба может совершать вызовы на удаленный сервер, а вставить ее в необработанную форму будет весьма сложной задачей.
Вместо этого мы должны написать тесты, в которых основное внимание будет сосредоточено на том, взаимодействует ли компонент с сервисом, как ожидалось. Мы добавим спецификации, которые будут следить за pastebinService и его методом getPastebin() .
Во-первых, импортируйте PastebinService в наш тестовый набор.
Затем добавьте его в массив providers внутри TestBed.configureTestingModule() .
В приведенном ниже коде создается шпион Jasmine, который предназначен для отслеживания всех вызовов метода getPastebin() и возвращает обещание, которое немедленно разрешается в mockPaste .
Шпион не заботится о деталях реализации реального сервиса, но вместо этого перехватывает любой вызов фактического метода getPastebin() . Более того, все удаленные вызовы, скрытые внутри getPastebin() , игнорируются нашими тестами. Во второй части учебника мы будем писать изолированные модульные тесты для сервисов Angular.
Первые два теста - это синхронные тесты. Первая спецификация проверяет, остается ли innerText элемента div пустым, пока компонент не инициализирован. Второй аргумент функции матчера Jasmine является необязательным и отображается, когда тест терпит неудачу. Это полезно, если в спецификации есть несколько ожидающих операторов.
Во втором spec компонент инициализируется (поскольку вызывается fixture.detectChanges() ), и ожидается, что шпион будет вызван, но шаблон не должен обновляться. Несмотря на то, что шпион возвращает разрешенное обещание, mockPaste пока недоступен. Он не должен быть доступен, если тест не является асинхронным.
В третьем тесте используется функция async() , обсуждавшаяся ранее, для запуска теста в тестовой зоне асинхронного тестирования. async() используется для синхронного тестирования асинхронного. fixture.whenStable() вызывается, когда все ожидающие асинхронные действия выполнены, а затем вызывается второй раунд fixture.detectChanges() для обновления DOM с новыми значениями. Ожидание в финальном тесте гарантирует, что наш DOM обновляется с помощью значений mockPaste .
Также необходимо обновить шаблон.
Настройка Jasmine и Karma
Разработчики в Angular упростили создание нашей тестовой среды. Для начала нам нужно сначала установить Angular. Я предпочитаю использовать Angular-CLI. Это решение «все-в-одном», которое заботится о создании, генерации, сборке и тестировании вашего Angular проекта.
Вот структура каталогов, созданная Angular-CLI.
Поскольку нас больше интересует тестирование в Angular, нам нужно найти два типа файлов.
karma.conf.js - это файл конфигурации для тестировщика Karma и единственный файл конфигурации, который нам понадобится для написания модульных тестов в Angular. По умолчанию в качестве браузера для Karma используется Chrome. Мы создадим свой собственный ланчер для запуска headless Chrome и добавим его в массив browsers .
Другим типом файла, который нужно найти, является все, что заканчивается на .spec.ts . По соглашению тесты, написанные на Jasime, называются спецификациями. Все тестовые спецификации должны быть расположены внутри каталога приложения src/app/ , поскольку именно здесь Karma ищет тестовые спецификации. Если вы создаете новый компонент или службу, важно, чтобы вы размещали ваши тестовые спецификации в том же каталоге, в котором находится код компонента или службы.
Читайте также: