"unit testing in angularjs" Виктор Зозуляк

38
Unit Testing in AngularJS Зозуляк Віктор DataXu/RailsReactor linkedin.com/in/zozulyakviktor last.fm/user/zuzya totheshow.org.ua

Upload: fwdays

Post on 16-Jul-2015

175 views

Category:

Technology


0 download

TRANSCRIPT

Unit Testing in AngularJS

Зозуляк ВікторDataXu/RailsReactor

linkedin.com/in/zozulyakviktorlast.fm/user/zuzyatotheshow.org.ua

Робоча ситуація

Розмова двох девелоперів

- Хм… а що у нас тут відбувається?

- А хто його знає, я це рік тому писав…

- А давай так легенько ось тут змінимо, має працювати.

- Так, схоже на те...

Привіт, баг!

Навіщо писати юніт тести?

● Дають оперативну можливість виявити небажані зміни поведінки

● Покращують розуміння коду вами і тим, хто буде його читати після вас

● Страхують від помилок, зроблених через необережність

● Полегшують рефакторінг

Юніт тестування - задачі

● розбити код на частини (юніти)

● ізолювати юніти - жоден реальний юніт не має бути задіяний повторно при тестуванні іншого юніта

● реалізувати механізм обнулення початкових умов

● покрити ізольовані юніти тестами

Ізолювати юніти

Без фейкових залежностей юніт тест - не юніт тест.

Ізолювати юніти

Юніти:● a - 2 тести● b - 2 тестиПри цьому a викликає b.Якщо в тестах a юніт b реальний і він почне некоректно працювати, то у нас упадуть і тести a і тести b.

Концепти моделювання фейків

Стаб - модель юніта, що повертає вказані дані.

Мок - модель юніта, яку можна сконфігурувати повертати дані та перевіряти їх.

Обнуляти дані

Дані не мають “текти” між тестами.

Кожен тест має працювати із:

● свіжими вхідними даними

● свіжим екземпляром юніта

Як ці задачі вирішуються в Angular?

Розбити на юніти

В Angular код розбивається на логічні частини згідно призначення:● контролери● сервіси● фільтри● директиви● і т.д.

Ізолювати юніти

Вирішується за допомогою Dependency Injection (DI).

Angular DI має інструментарій, що дозволяє легко моделювати залежності.

Покрити юніти тестами

Jasmine - фреймворк для юніт тестування. Angular пристосований до роботи з ним.

Karma Spec Runner - спеціальний засіб для зручного запуску тестів, розроблений командою Angular.

Karma Features

● вотчери, що запускають тести, коли змінюється код

● підтримка препроцесорів● підтримка code coverage tools● запуск тестів одразу в декількох бразуерах

(Chrome, FireFox, PhantomJS, IE)● підтримка репортерів

Обнулення початкових умов

Angular має вбудований інструментарій для ініціалізації модулів в тестовому режимі.

Цей інструментарій можна використати в зв’язці з інструментарієм, що дозволяє запуск коду до/після кожного тесту (beforeEach/afterEach в Jasmine).

Структура юніт тесту Jasmine

Тест складається з блоків:● describe● it● beforeEach● afterEach● expect

Призначення блоків Jasmine

● describe - контейнер для групування по сутностям чи умовам

● it - описує властивість сутності

Важливо: describe та it мають формувати зв’язні речення.

describe('myUnit', function () { it('inits the data', function () {...}); describe('myMethod', function () { it('does the job properly', function () { ... }); });});describe('myAnotherUnit', function () { ... };

Призначення блоків Jasmine

● expect - перевіряє властивість юніта

describe('myUnit', function () { it('inits the data', function () { expect(new myUnit().data).toBeDefined(); }); describe('myMethod of myUnit', function () { it('does the job properly', function () { expect(myUnit.myMethod()).toBe(true); }); });});

Призначення блоків Jasmine

● beforeEach - виконується перед кожною перевіркою властивості юніта

● afterEach - виконується після кожної перевірки властивості юніта

Структура юніт тесту Jasminedescribe('myUnit', function () { beforeEach(initMyUnit); afterEach(deInitMyUnit); it('inits the data', function () { expect(new myUnit().data).toBeDefined(); }); describe('myMethod of myUnit', function () { beforeEach(initMyMethod); afterEach(deInitMyMethod); it('does the job properly', function () { ... expect(myUnit.myMethod()).toBe(true); }); ... });});describe('myAnotherUnit', function () { ... };

Порядок виконання блоків Jasmine

Describe блоки виконуються під час завантаження скриптів.

Після завантаження всіх скриптів для кожного describe верхнього рівню рекурсивно виконуються всі вкладені describe та it.При цьому перед кожним it виконуються всі beforeEach, що знаходяться вище, а після -- всі afterEach.

Порядок виконання в нашому випадку:

describe myUnitdescribe myMethoddescribe myOtherUnit

beforeEach initMyUnit()it should load the data during the initialisationafterEach deinitMyUnit()

beforeEach initMyUnit()beforeEach initMyMethod()it should do the job properlyafterEach deInitMyMethod()afterEach deInitMyUnit()

beforeEach initMyOtherUnit()

Важливий наслідок

Код, написаний в describe блоці, залежить від порядку завантаження скриптів.

Для того, щоб уникнути залежності від порядку завантаження скриптів, код потрібно писати в beforeEach блоках.

Інструменти Angular для юніт тестування● module - для ініціалізації модуля і його

забезпечення фейковими залежностями● inject - для ініцалізації сервісів● $injector - сервіс для ініціалізації сервісів● $controller - сервіс для ініціалізації

контролерів● $compile - сервіс для ініціалізації директив● $filter - сервіс для ініціалізації фільтрів

module - passing fake dependencies

beforeEach(function () { module('myModule', function ($provide, $controllerProvider) { $provide.service('myService', myServiceMock); $controllerProvider .register('myController', myControllerMock); });});

Як тестувати взаємодію з фейковими сутністями?

Взаємодія з фейковими сутністями

Важливо перевірити правильну передачу контролю і правильну роботу з результатом передачі контролю.

Для цього в нагоді стануть Jasmine Spies.

Практичний випадок

myService має myMethod, що викликає anotherMethod з anotherService.

При цьому myMethod повертає те, що повертає anotherMethod.

Передача контролю - використовуємо createSpy :

Взаємодія з фейковими сутністями

it('properly calls someMethod of anotherService', function () {

var expectedCallParams = { isSomeProp: true };

spyOn(anotherService, 'someMethod');

myService.myMethod();

expect(anotherService.someMethod)

.toHaveBeenCalledWith(expectedCallParams);

});

Взаємодія з фейковими сутністями

Якщо важливий результат виконання, використовуємо andCallFake:

it('properly calls someMethod of anotherService', function () {

var expectedCallParams = { isSomeProp: true },

result,

anotherServiceRetVal = {};

spyOn(anotherService, 'someMethod')

.and.callFake(function () { return anotherServiceRetVal; });

result = myService.myMethod();

expect(anotherService.someMethod)

.toHaveBeenCalledWith(expectedCallParams);

expect(result).toBe(anotherServiceRetVal);

});

Як тестувати взаємодію з методами того ж сервісу?

Так само, як і з іншими юнітами!

Код, що покривається тестами не має бути задіяний повторно при тестуванні інших сутностей.

Aльтернатива Jasmine Spies

Sinon.JShttp://sinonjs.org

inject - доступ до сервісів

var myService,

anotherService;beforeEach(function () { inject(function (_myService_, _antotherService_) { myService = _myService_;

antotherService = _antotherService_; });

});

$injector - доступ до сервісів

var myService,

anotherService;beforeEach(function () { inject(function ($injector) { myService = $injector.get('myService');

anotherService = $injector.get('anotherService'); });

});

$controller, $filter, $compile

var myContoller, myFilter, myDirElt;beforeEach(function () { inject(function ($controller, $compile, $filter, $rootScope) { var scope = $rootScope.$new(); myController = $controller('myController'); myFilter = $filter('myFilter'); myDirElt = $compile('<my-dir></my-dir>')(scope); }); });

Моки з коробки

Angular Mocks з коробки має наступні моки:

● $httpBackend● $timeout● $interval

Ці сервіси працюють синхронно. Контроль за виконанням колбеків відбувається за допомогою методу flush (наприклад - $httpBackend.flush())

Важливий момент

В Angular колбеки промісів виконуються тільки під час digest циклів.

В додатках немає сенсу їх запускати вручну, тому що це роблять байндінги в’юх.

В тестах - часто приходиться запускати вручну.

Дякую за увагу!