В этом руководстве мы рассмотрим, как мы можем писать автоматизированные тесты в проектах Angular 5. Угловое тестирование является основной функцией, доступной в каждом проекте, который был настроен с помощью Angular CLI или Angular Quick Start Project .
Тема углового тестирования обширна, так как это сложная и очень сложная тема. Это потребовало бы нескольких глав или курса полной длины, чтобы покрыть это полностью. Итак, в этом руководстве я покажу вам только основы, с которых можно начать.
Предпосылки
На момент написания, Angular 5.2 является текущей стабильной версией, которую мы будем здесь использовать. В этом руководстве предполагается, что вы, по крайней мере, хорошо понимаете основы Angular 4+. Также предполагается, что вы, по крайней мере, понимаете концепцию или обладаете навыками написания автоматизированных тестов.
Мы будем основывать наши примеры тестирования на официальном руководстве Angular для начинающих, чтобы продемонстрировать, как писать тесты для компонентов и сервисов. Вы можете найти готовый код с тестами в нашем репозитории GitHub . В конце этого руководства вы должны обладать навыками реализации нескольких проходных тестов в Angular 5.
Угловые технологии тестирования
Как вы уже знаете, проект Angular состоит из шаблонов, компонентов, сервисов и модулей. Все они бегают внутри так называемой угловой среды. Хотя можно писать изолированные тесты, вы не будете знать, как ваш код будет взаимодействовать с другими элементами в среде Angular.
К счастью, у нас есть несколько технологий, которые могут помочь нам написать такие модульные тесты с наименьшими усилиями.
1. Угловые утилиты тестирования
Это набор классов и функций, которые необходимы для создания тестовой среды для Angular-кода. Вы можете найти их в документации API Angular. Наиболее важным из всех является TestBed . Он используется для настройки модуля Angular точно так же, как @NgModule
за исключением того, что он подготавливает модуль к тестированию. Он имеет функцию configureTestingModule
где вы предоставляете все необходимые зависимости для работы вашего компонента в тестовой среде. Вот пример того, как dashboard component
готовится к запуску в тестовой среде. Для запуска теста требуется несколько зависимостей:
TestBed.configureTestingModule({ imports: [ RouterTestingModule ], declarations: [ DashboardComponent ], schemas: [ NO_ERRORS_SCHEMA ], providers: [ { provide: HeroService, useClass: MockHeroService } ], }) .compileComponents();
Мы рассмотрим более подробно то, что здесь происходит немного ниже.
2. Жасмин
Жасмин является де-факто основой для написания угловых тестов. По сути, это среда тестирования, использующая нотацию, основанную на поведении. Написание теста в Жасмин довольно просто:
describe('createCustomer' () => { it('should create new customer',(customer) => { ... expect(response).toEqual(newCustomer) }); it('should not create customer with missing fields', () => { ... expect(response.error.message).toEqual('missing parameters') }); it('should not create customer with existing record', () => { ... expect(response.error.message).toEqual('record already exists') }); });
Анатомия теста Жасмин состоит как минимум из двух элементов: функции describe
, которая представляет собой набор тестов, и функции it
, которая является самим тестом. Обычно мы используем createCustomer()
для указания функции, на которую мы createCustomer()
— например, createCustomer()
. Затем в комплекте мы создаем несколько тестов it
. Каждый тест ставит целевую функцию в другое состояние, чтобы обеспечить ее правильное поведение. Вы можете обратиться к документации Жасмин для получения дополнительной информации.
3. Карма
Karma — это инструмент для выполнения исходного кода в сравнении с тестовым кодом в среде браузера. Он поддерживает запуск тестов в каждом браузере, для которого он настроен. Результаты отображаются как в командной строке, так и в браузере, чтобы разработчик мог проверить, какие тесты пройдены или не пройдены. Карма также просматривает файлы и может запускать повторный тест при каждом изменении файла. В корне проекта Angular у нас есть файл karma.conf
который используется для настройки Karma. Содержимое должно выглядеть примерно так:
module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular/cli'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular/cli/plugins/karma') ], client:{ clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { reports: [ 'html', 'lcovonly' ], fixWebpackSourcePaths: true }, angularCli: { environment: 'dev' }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false }); };
установкиmodule.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular/cli'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular/cli/plugins/karma') ], client:{ clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { reports: [ 'html', 'lcovonly' ], fixWebpackSourcePaths: true }, angularCli: { environment: 'dev' }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false }); };
Изучите документацию конфигурации Karma, чтобы узнать, как ее настроить. Как видите, Chrome указан как браузер для запуска тестов. Вам необходимо определить переменную среды CHROME_BIN
которая указывает на местоположение исполняемого файла браузера Chrome. Если вы используете Linux, просто добавьте эту строку в ваш файл .bashrc
:
export CHROME_BIN="/usr/bin/chromium-browser"
Чтобы Karma запустил ваши тесты, вы должны убедиться, что тестовые файлы заканчиваются на .spec.ts
. Вы должны заметить, что Карма была разработана для запуска модульных тестов. Для запуска сквозных тестов нам понадобится другой инструмент, Protractor, который мы рассмотрим далее.
4. Транспортир
Транспортир — это комплексная тестовая среда для Angular. Он запускает ваши тесты в реальном браузере и взаимодействует с ним, как и реальный человек. В отличие от модульных тестов, где мы тестируем отдельные функции, здесь мы тестируем всю логику. Protractor может заполнять формы, нажимать кнопки и подтверждать, что ожидаемые данные и стилизация отображаются в HTML-документе. Как и в случае с Karma, Protractor имеет собственный файл конфигурации в корне вашего Angular-проекта protractor.conf
:
const { SpecReporter } = require('jasmine-spec-reporter'); exports.config = { allScriptsTimeout: 11000, specs: [ './e2e/**/*.e2e-spec.ts' ], capabilities: { 'browserName': 'chrome' }, directConnect: true, baseUrl: 'http://localhost:4200/', framework: 'jasmine', jasmineNodeOpts: { showColors: true, defaultTimeoutInterval: 30000, print: function() {} }, onPrepare() { require('ts-node').register({ project: 'e2e/tsconfig.e2e.json' }); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); } };
Вы можете найти документацию по его конфигурации здесь . В отличие от тестов Jasmine / Karma, тесты Protractor находятся вне папки src
, в папке с именем e2e
. Мы рассмотрим написание сквозных тестов позже в будущем. А пока давайте начнем писать модульные тесты.
Написание юнит-тестов
Как упоминалось ранее, Angular содержит практически все, что вам нужно для написания автоматических тестов для вашего проекта. Чтобы начать тестирование, просто запустите это:
ng test
Карма раскрутится и запустит все доступные тесты. Предполагая, что вы только что завершили учебное пособие «Тур героев», у вас должен быть похожий отчет:
Эти тесты создаются при создании компонентов, сервисов и классов с помощью инструмента Angular CLI
. На момент создания код в этих тестах был верным. Однако, когда вы добавили код в свой компонент и сервисы, тесты не сработали. В следующем разделе мы увидим, как мы можем решить сломанные тесты.
Тестирование компонента
Модульное тестирование компонента может идти двумя способами. Вы можете протестировать его отдельно или протестировать в среде Angular, чтобы увидеть, как он взаимодействует со своим шаблоном и зависимостями. Последнее звучит сложно для реализации, но использование Angular Testing Utilities облегчает создание теста. Вот пример тестового кода, который генерируется для вас при создании компонента с помощью инструмента Angular CLI
:
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { HeroesComponent } from './heroes.component'; describe('HeroesComponent', () => { let component: HeroesComponent; let fixture: ComponentFixture<HeroesComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ HeroesComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(HeroesComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should be created', () => { expect(component).toBeTruthy(); }); });
В первой функции beforeEach()
мы используем функцию TestBed.configureTestingModule
для создания модульной среды для тестирования компонента. Это похоже на NgModules , за исключением того, что в этом случае мы создаем модуль для тестирования.
Во второй функции beforeEach()
мы создаем экземпляр component-under-test
. Как только мы сделаем это, мы не TestBed
снова настроить TestBed
, так как будет TestBed
сообщение об ошибке.
Наконец, у нас есть спецификация, которая should be created
, где мы подтверждаем, что component
был инициализирован. Если этот тест пройден, это означает, что компонент должен работать правильно в среде Angular. Однако, если это терпит неудачу, вероятно, у компонента есть определенная зависимость, которую мы не включили в тестовую конфигурацию. Давайте посмотрим, как мы можем решать различные проблемы.
Тестирование компонента, использующего другой компонент
При создании пользовательского интерфейса в Angular мы часто ссылаемся на другие компоненты в файле шаблона через селектор. Взгляните на этот пример dashboard.component.html
:
<h3>Top Heroes</h3> ... </div> <app-hero-search></app-hero-search>
В этом примере мы ссылаемся на другой компонент, имеющий селектор app-hero-search
. Если вы попытаетесь запустить первоначальный тест как есть, он не будет выполнен. Это потому, что мы не объявили указанный компонент в тестовой среде. В модульном тесте мы уделяем все внимание компоненту, который тестируем. Другие компоненты нам не интересны при юнит-тестировании. Мы должны предположить, что они работают, как ожидалось. Включение ссылочных компонентов в наш тест может повлиять на результаты. Чтобы решить эту проблему, мы можем либо издеваться над указанным компонентом, либо просто игнорировать его, используя директиву NO_ERRORS_SCHEMA
. Вот пример:
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { DashboardComponent } from './dashboard.component'; describe('DashboardComponent', () => { let component: DashboardComponent; let fixture: ComponentFixture<DashboardComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ DashboardComponent ], schemas: [ NO_ERRORS_SCHEMA }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(DashboardComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should be created', () => { expect(component).toBeTruthy(); }); });
Теперь в этом тесте не должно быть проблем с зависимостями компонентов. Однако этот тест еще не пройден, так как есть еще одна ситуация, с которой нам приходится иметь дело …
Тестирование компонента, который использует модуль
Давайте на этот раз рассмотрим hero-detail.component.html
:
<div *ngIf="hero"> <h2>{{ hero.name | uppercase }} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div> <label>name: <input [(ngModel)]="hero.name" placeholder="name"/> </label> </div> <button (click)="goBack()">go back</button> <button (click)="save()">save</button> </div>
Здесь мы используем директиву ngModel
, которая поступает из библиотеки FormsModule
. Чтобы написать тест, который поддерживает этот модуль, нам нужно только импортировать FormsModule
и включить его в конфигурацию TestBed
:
import { FormsModule } from '@angular/forms'; ... beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ HeroDetailComponent ], imports: [ FormsModule], }) .compileComponents(); })); ...
Это должно решить проблему с FormsModule
. Однако есть еще пара зависимостей, которые мы должны указать в нашей тестовой среде.
Тестирование компонента, использующего модуль маршрутизации
Давайте рассмотрим конструктор hero-detail.component.ts
:
constructor( private route: ActivatedRoute, private location: Location, private heroService: HeroService ) {}
Компонент имеет зависимости ActivatedRoute
и Location
которые связаны с маршрутизацией. В нашем тестовом коде hero-detail.component.spec.ts
мы могли реализовать фиктивные версии классов. Однако я обнаружил, что лучшим решением было импортировать RouterTestingModule
следующим образом:
import { RouterTestingModule } from '@angular/router/testing'; ... beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ HeroDetailComponent ], imports: [ FormsModule, RouterTestingModule ], }) .compileComponents(); }));
RoutingTestingModule
легко решает зависимости ActivateRoute
и Location
в нашем тестовом коде. RoutingTestingModule
также обрабатывает другие ситуации, в которых участвует маршрутизация. Взгляните на этот код в dashboard.component.html
:
<h3>Top Heroes</h3> <div class="grid grid-pad"> <a *ngFor="let hero of heroes" class="col-1-4" routerLink="/detail/{{hero.id}}"> <div class="module hero"> <h4>{{hero.name}}</h4> </div> </a> </div>
Обратите внимание, у нас есть директива routerLink
. Это директива, предоставляемая библиотекой AppRoutingModule
. Если запустить тест панели мониторинга, он потерпит неудачу из-за этой зависимости. Чтобы это исправить, просто реализуйте RoutingTestingModule
в dashboard.component.spec.ts
же, как мы сделали для hero-detail.component.spec.ts
.
Давайте теперь посмотрим, как мы можем тестировать компоненты, которые зависят от сервисов.
Тестирование компонента, использующего сервисы
Каждому компоненту нужен как минимум сервис для обработки логики. Есть несколько способов тестирования компонентов, которые используют сервисы. Давайте посмотрим на message.service.ts
, который используется message.component.ts
:
import { Injectable } from '@angular/core'; @Injectable() export class MessageService { messages: string[] = []; add(message: string) { this.messages.push(message); } clear() { this.messages = []; } }
MessageService
имеет очень простую реализацию. Он не использует никаких внешних зависимостей. Хотя рекомендуется исключать внешнюю логику из модульных тестов, здесь мы сделаем исключение. Я не вижу необходимости усложнять наши тесты. По этой причине я считаю, что лучше всего включить сервис в тест. Вот тестовый код для message.component.spec.ts
:
import { MessageService } from '@services/message.service'; ... beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ MessagesComponent ], providers: [ MessageService ] }) .compileComponents(); }))
Теперь давайте посмотрим на другой сервис, hero-service.ts
:
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { catchError, map, tap } from 'rxjs/operators'; ... @Injectable() export class HeroService { private heroesUrl = 'api/heroes'; constructor( private http: HttpClient, private messageService: MessageService) { } /** GET heroes from the server */ getHeroes (): Observable<Hero[]> { return this.http.get<Hero[]>(this.heroesUrl) .pipe( tap(heroes => this.log(`fetched ${heroes.length} heroes`)), catchError(this.handleError('getHeroes', [])) ); } getHero(id: number): Observable<Hero> { const url = `${this.heroesUrl}/${id}`; return this.http.get<Hero>(url).pipe( tap(_ => this.log(`fetched hero id=${id}`)), catchError(this.handleError<Hero>(`getHero id=${id}`)) ); } ... }
Класс HeroService
содержит немного логики — всего около 104 строк. Он имеет несколько зависимостей, в том числе один к другому сервису. Также все его функции являются асинхронными. Такой сложный код имеет большой потенциал загрязнения наших модульных тестов. По этой причине мы должны исключить его логику. Мы делаем это путем создания фиктивной версии hero.service.ts
. Просто создайте новый файл и назовите его hero.service.mock.ts
. Смоделируйте его функции так, чтобы его основная логика была удалена:
import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import { Hero } from '@models/hero.model'; export class MockHeroService { getHeroes(): Observable<Hero[]> { return of([]); } getHero() { return of({}); } }
Вы можете видеть, насколько проще макетная версия. Теперь у него нет шансов загрязнить наши юнит-тесты. Чтобы включить его в наши файлы спецификаций компонентов, мы реализуем это так:
import { HeroService } from '@services/hero.service'; import { MockHeroService } from '@services/hero.service.mock'; ... TestBed.configureTestingModule({ declarations: [ HeroDetailComponent ], imports: [ FormsModule, RouterTestingModule ], providers: [ { provide: HeroService, useClass: MockHeroService }, ], }) .compileComponents(); })); ...
Мы используем опцию providers
для внедрения MockHeroService
качестве нашей услуги. Реализуйте это для тестового кода всех компонентов, используя сервис.
Тестирование Сервиса
Теперь, когда мы рассмотрели некоторые распространенные сценарии, возникающие при тестировании компонентов, давайте посмотрим, как мы можем тестировать сервисы. Сервисы выполняют основную логику наших приложений, поэтому очень важно тщательно протестировать их функции. Как упоминалось ранее, угловое тестирование — это глубокая тема, поэтому мы просто собираемся немного коснуться этой темы.
Откройте hero.service.ts
и изучите функции. Позвольте мне выделить пару:
... /** GET heroes from the server */ getHeroes (): Observable<Hero[]> { return this.http.get<Hero[]>(this.heroesUrl) .pipe( tap(heroes => this.log(`fetched ${heroes.length} heroes`)), catchError(this.handleError('getHeroes', [])) ); } /** UPDATE: update selected hero on the server */ updateHero (hero: Hero): Observable<any> { return this.http.put(this.heroesUrl, hero, httpOptions).pipe( tap(_ => this.log(`updated hero id=${hero.id}`)), catchError(this.handleError<any>('updateHero')) ); } ...
Каждая функция состоит из нескольких строк кода, но многое происходит. Чтобы полностью протестировать каждый из них, нам нужно рассмотреть ряд сценариев. Когда мы выполняем getHeroes()
, сервер может
- отправить назад список героев
- отправить обратно пустой список
- выбросить ошибку
- не в состоянии ответить.
Вы можете подумать о более возможных сценариях, которые можно добавить в список. Теперь, когда мы рассмотрели возможные сценарии, пришло время написать тесты. Вот пример того, как написать spec
для HeroService
:
import { TestBed, inject } from '@angular/core/testing'; import { HttpClientModule, HttpClient, HttpResponse } from '@angular/common/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HeroService } from './hero.service'; import { MessageService } from './message.service'; import { Hero } from '@models/hero.model'; const mockData = [ { id: 1, name: 'Hulk' }, { id: 2, name: 'Thor'}, { id: 3, name: 'Iron Man'} ] as Hero[]; describe('HeroService', () => { let service; let httpTestingController: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ imports: [ HttpClientTestingModule ], providers: [HeroService, MessageService] }); httpTestingController = TestBed.get(HttpTestingController); }); beforeEach(inject([HeroService], s => { service = s; })); beforeEach(() => { this.mockHeroes = [...mockData]; this.mockHero = this.mockHeroes[0]; this.mockId = this.mockHero.id; }); const apiUrl = (id: number) => { return `${service.heroesUrl}/${this.mockId}`; }; afterEach(() => { // After every test, assert that there are no more pending requests. httpTestingController.verify(); }); it('should be created', () => { expect(service).toBeTruthy(); }); describe('getHeroes', () => { it('should return mock heroes', () => { service.getHeroes().subscribe( heroes => expect(heroes.length).toEqual(this.mockHeroes.length), fail ); // Receive GET request const req = httpTestingController.expectOne(service.heroesUrl); expect(req.request.method).toEqual('GET'); // Respond with the mock heroes req.flush(this.mockHeroes); }); }); describe('updateHero', () => { it('should update hero', () => { service.updateHero(this.mockHero).subscribe( response => expect(response).toEqual(this.mockHero), fail ); // Receive PUT request const req = httpTestingController.expectOne(service.heroesUrl); expect(req.request.method).toEqual('PUT'); // Respond with the updated hero req.flush(this.mockHero); }); }); describe('deleteHero', () => { it('should delete hero using id', () => { const mockUrl = apiUrl(this.mockId); service.deleteHero(this.mockId).subscribe( response => expect(response).toEqual(this.mockId), fail ); // Receive DELETE request const req = httpTestingController.expectOne(mockUrl); expect(req.request.method).toEqual('DELETE'); // Respond with the updated hero req.flush(this.mockId); }); it('should delete hero using hero object', () => { const mockUrl = apiUrl(this.mockHero.id); service.deleteHero(this.mockHero).subscribe( response => expect(response).toEqual(this.mockHero.id), fail ); // Receive DELETE request const req = httpTestingController.expectOne(mockUrl); expect(req.request.method).toEqual('DELETE'); // Respond with the updated hero req.flush(this.mockHero.id); }); }); });
Это всего лишь пример того, как мы должны написать тест для сервиса, который взаимодействует с HttpClientModule
. Изучите каждый тест и обратите внимание, что мы используем класс HttpTestingController
для перехвата запросов. В этом тесте мы контролируем входы и выходы для создания различных сценариев. Основная цель этих тестов — убедиться, что наши сервисные методы способны корректно обрабатывать каждый сценарий. Обратите внимание, что мы не полностью реализовали все тесты, необходимые для hero.service.spec.ts
, поскольку это выходит за рамки данного руководства.
Есть еще несколько тем, которые нам еще предстоит рассмотреть до конца этого руководства.
Сквозное угловое тестирование
Модульные тесты обеспечивают правильную работу компонентов и сервисов в контролируемой тестовой среде. Однако нет гарантии, что компоненты и сервисы будут взаимодействовать друг с другом в среде Angular. Вот почему нам необходимо выполнить сквозное тестирование. Сквозное испытание — это то, которое имитирует тестирование на людях. Другими словами, тесты предназначены для взаимодействия с нашим приложением так же, как мы — через интерфейс браузера.
В нашем приложении Tour of Heroes есть несколько вариантов использования, которые мы можем протестировать, например:
- пять компонентов отображаются на панели инструментов
- все герои отображаются в компоненте героев
- навигационные ссылки не битые
- новый герой может быть создан
- герой может быть обновлен
- герой может быть удален.
И вы можете продолжать добавлять в этот список по мере реализации новых функций. Сквозное испытание в идеале состоит из двух частей.
Первая часть представляет собой вспомогательный файл, который предоставляет вспомогательные функции, специфичные для компонента. Вот пример app.po.ts
:
import { browser, by, element } from 'protractor'; export class AppPage { navigateTo() { return browser.get('/'); } getParagraphText() { return element(by.css('app-root h1')).getText(); } }
После того, как вы определили свои вспомогательные функции, вы можете легко получить к ним доступ во время написания теста e2e. Вот пример e2e/app.e2e.spec.ts
:
import { AppPage } from './app.po'; describe('angular-tour-of-heroes App', () => { let page: AppPage; beforeEach(() => { page = new AppPage(); }); it('should display welcome message', () => { page.navigateTo(); expect(page.getParagraphText()).toEqual('Welcome to app!'); }); });
Чтобы запустить этот тест, просто выполните следующую команду:
ng e2e
Возможно, вам понадобится подключение к Интернету, если вы выполняете эту команду впервые. После завершения теста вы, скорее всего, получите сообщение об ошибке, которое выглядит примерно так:
angular-tour-of-heroes App ✗ should display welcome message - Expected 'Tour of Heroes' to equal 'Welcome to app!'.
Давайте исправим ошибку следующим образом. Я также добавил еще один тест, чтобы убедиться, что перенаправление, указанное в app-routing.module.ts
работает:
import { AppPage } from './app.po'; import { browser } from 'protractor'; describe('angular-tour-of-heroes App', () => { let page: AppPage; beforeEach(() => { page = new AppPage(); }); it('should redirect to dashboard', async () => { page.navigateTo(); const url = await browser.getCurrentUrl(); expect(url).toContain('/dashboard'); }); it('should display welcome message', () => { page.navigateTo(); expect(page.getParagraphText()).toEqual('Tour of Heroes'); }); });
Запустите тест снова. Теперь мы должны пройти тесты:
angular-tour-of-heroes App ✓ should redirect to dashboard ✓ should display welcome message
Наблюдать e2e
тестами e2e
— это потрясающее чувство. Это дает вам уверенность, что ваше приложение будет работать бесперебойно в производстве. Теперь, когда вы попробовали e2e
, e2e
время перейти к другой классной функции тестирования.
Покрытие кода
Один из наших самых больших вопросов для разработчиков — «проверили ли мы достаточно кода?» К счастью, у нас есть инструменты, которые могут генерировать «покрытие кода», чтобы определить, сколько тестируется нашего кода. Чтобы создать отчет, просто выполните следующее:
ng test --watch=false --code-coverage
Папка покрытия будет создана в корне вашего Angular проекта. Перейдите в папку, и вы найдете index.html
. Откройте его с помощью веб-браузера. Вы должны увидеть что-то вроде этого:
Я не буду вдаваться в подробности, но вы можете видеть, что некоторые классы были протестированы полностью, а другие — не полностью. Из-за времени и доступности ресурсов не всегда возможно реализовать 100% тестовое покрытие. Тем не менее, вы можете решить с вашей командой, какой должен быть минимум. Чтобы указать минимум, используйте karma.conf
чтобы настроить параметры покрытия кода следующим образом:
coverageIstanbulReporter: { reports: [ 'html', 'lcovonly' ], fixWebpackSourcePaths: true, thresholds: { statements: 80, lines: 80, branches: 80, functions: 80 } }
Приведенное выше пороговое значение указывает минимум на 80%, который должен охватываться модульными тестами.
Дополнительные утилиты
Теперь мы рассмотрели основы углового тестирования. Однако мы можем улучшить качество нашего кода, пройдя несколько шагов дальше.
1. Линтинг
Angular поставляется с инструментом для выполнения кодирования. Просто выполните следующий код для проверки вашего проекта:
ng lint
Эта команда выдаст предупреждения о вашем коде — например, если вы забыли использовать точку с запятой или использовали слишком много пробелов. Команда также поможет выявить неиспользуемый код и некоторые ошибки в ваших утверждениях. Использование этой команды часто гарантирует, что все в вашей команде пишут код в едином стиле. Вы можете дополнительно настроить параметры lint в файле tslint.json
.
2. Интеллектуальные редакторы кода
Когда дело доходит до редакторов кода и IDE, мои личные фавориты — это Atom и Sublime Text . Однако недавно я обнаружил код Visual Studio , который имеет более привлекательные функции. Это бесплатный редактор кода, который может работать в Windows, MacOS и Linux. Он многое заимствует у Atom, за исключением того, что у него есть дополнительные функции, которые я хотел бы выделить:
- Intellisense
- Подсветка ошибок
- Современные угловые надставки
В настоящее время ни Atom, ни Sublime Text не имеют этих функций, хотя они встроены в VSCode. Вам нужно только установить необходимое расширение языка. Функция Intellisense перечисляет параметры для вас при вводе кода. Это как автозаполнение, но с определенным списком синтаксически правильных опций. С этой функцией сложно сделать синтаксическую ошибку. Вы также можете просмотреть документацию функции, которая позволяет вам увидеть тип возвращаемого значения и необходимые входные данные.
Visual Studio Code также имеет правильную функцию выделения ошибок. Он не только проверяет синтаксические ошибки, но и гарантирует, что назначения имеют правильный тип. Например, если вы попытаетесь присвоить массив результату функции Observable, для вас будет подсвечена ошибка. VSCode также имеет угловые расширения, совместимые с Angular 5.
Наличие IDE, которая проверяет ваш код на наличие ошибок при наборе текста, очень полезно для производительности. Это поможет вам тратить меньше времени на исправление ошибок, которые вы в противном случае сделали бы. Могут быть и другие редакторы кода, которые могут выполнить то же самое, но сейчас я рекомендую Visual Studio Code для Angular проектов.
3. Непрерывная интеграция
Непрерывная интеграция (CI) — это процесс автоматизации тестирования и сборки. Как разработчики, мы часто работаем в изоляции в течение пары недель или более. К тому времени, когда мы объединяем изменения в основную ветку, возникает много ошибок и конфликтов. Это может занять много времени, чтобы исправить.
CI рекомендует разработчикам писать тесты и выполнять задачи часто небольшими битами. Сервер CI будет автоматически создавать и запускать тесты, помогая разработчикам своевременно обнаруживать ошибки, что приведет к уменьшению конфликтов и проблем. Для разработчиков Angular доступно множество решений CI. Ознакомьтесь с руководством SitePoint по тестированию Жасмин и Карма на Трэвисе .
Завершение
У нас есть доступ к тоннам информации об автоматизированных тестах, а также к фреймворкам для разработки через тестирование, которые помогают нам писать тесты. Однако есть пара причин, по которым мы не всегда должны писать тесты:
- Не пишите тесты для нового приложения. Масштаб проекта будет быстро меняться в зависимости от того, чего хочет клиент или как реагирует рынок.
- Написание тестов требует больше времени в дополнение к реализации функций. Также требуется время для обслуживания при изменении области действия функции. Если у вас низкий бюджет, можно пропустить письменные тесты. Будьте практичны с ресурсами, которые у вас есть.
Так что остается вопрос о том, когда подходящее время писать тесты. Вот несколько указателей:
- Вы завершили фазу прототипа и определили основные функции вашего приложения.
- Ваш проект имеет достаточное финансирование.
Теперь, если вы решили применить TDD, есть много преимуществ:
- Написание кода, который можно протестировать, означает, что вы пишете код лучшего качества.
- Как разработчик, у вас будет больше уверенности в выпуске вашей последней версии в производство.
- Написание тестов — это способ документирования вашего кода. Это означает, что будущим разработчикам будет проще обновлять устаревший код.
- Вам не нужно нанимать кого-то для контроля качества, поскольку ваш CI-сервер сделает эту работу за вас.
Если вы решите полностью пропустить тесты для вашего готового к применению приложения, будьте готовы встретиться с гневными и разочарованными клиентами в будущем. Количество ошибок будет увеличиваться экспоненциально по мере увеличения размера вашей кодовой базы.
Надеюсь, это было полезным введением в угловое тестирование для вас. Если вы хотите узнать больше, я рекомендую вам сначала ознакомиться с официальной документацией по Angular 5. Большая часть информации предназначена для более старых версий Angular, если не указано иное.
Дайте нам знать, какие у вас есть интересные советы по тестированию Angular!