От автора: в этом руководстве мы узнаем, как писать автоматизированные тесты в проектах Angular 5. Тестирование – базовая функция, доступная во всех проектах, созданных с помощью Angular CLI или Angular quick start. Тестирование Angular — это огромная, сложная тема, которая включает в себя много подтем. Чтобы полностью ее рассказать, понадобится несколько глав или целый курс. В этом руководстве я покажу лишь основы, чтобы можно было начать.
Предварительные условия
На момент написания статьи текущая стабильная версия – это Angular 5.2. Ее мы и будем использовать. Руководство предполагает, как минимум, хорошее понимание основ Angular 4+. Также у вас должно быть понимание концепции или навыки написания автоматизированных тестов.
За основу наших примеров для тестирования возьмем официальный урок по Angular для начинающих. С их помощью мы продемонстрируем, как писать тесты для компонентов и сервисов. Весь код с тестами можно найти в репозитории GitHub. После прочтения руководства вы должны уметь писать успешные тесты в Angular 5.
Технологии тестирования Angular
Как вы уже знаете, проект Angular состоит из шаблонов, компонентов, сервисов и модулей. Все они запускаются в так называемом окружении Angular. Можно писать изолированные тесты, но так вы не будете по-настоящему знать, как ваш код взаимодействует с другими элементами в окружении Angular.
К счастью есть технологии, помогающие писать такие юнит тесты с минимальными затратами усилий.
1. Утилиты тестирования Angular
Это набор классов и функций, необходимых для создания тестового окружения для кода Angular. Их можно найти в документации к Angular api. Самый важный класс — TestBed. Он настраивает модуль Angular точно так же, как @NgModule (только модуль подготавливается к тестированию). В классе есть функция configureTestingModule. В ней указываются все обязательные зависимости для работы компонента в тестовом окружении. Ниже представлен пример подготовки dashboard component к запуску в тестовом окружении. Для запуска теста компоненту требуется несколько зависимостей:
TestBed.configureTestingModule({ imports: [ RouterTestingModule ], declarations: [ DashboardComponent ], schemas: [ NO_ERRORS_SCHEMA ], providers: [ { provide: HeroService, useClass: MockHeroService } ], }) .compileComponents();
Чуть позже разберем код более подробно.
2. Jasmine
Jasmine – это, по факту, фреймворк для написания тестов Angular. Это фреймворк для тестирования, использующий нотации behavior-driven. Писать тесты в Jasmine очень просто:
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') }); });
Тест Jasmin состоит из минимум двух элементов: функция describe (сьют тестов) и функция it (сам тест). Обычно с помощью describe показывают функцию, на которой сосредоточены – например, createCustomer(). Внутри сьюта создается несколько тестов it. Каждый тест проверяет целевую функцию на ожидаемое поведение с разным условием. Подробнее в документации Jasmine.
3. Karma
Karma – инструмент для выполнения исходного кода с тестовым кодом в браузере. Поддерживается запуск тестов во всех браузерах, для которых настроен инструмент. Результаты отображаются в командной строке и в браузере. Так разработчик может следить за тем, какие тесты прошли, а какие упали. 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 }); };
Для настройки Karma обязательно прочтите документацию. В коде сверху в качестве браузера для запуска тестов указан Chrome. Вам понадобится объявить переменную среды CHROME_BIN со ссылкой на исполняемый файл браузера Chrome. Если вы используете Linux, просто добавьте строку ниже в файл .bashrc:
export CHROME_BIN="/usr/bin/chromium-browser"
Файлы тестов должны оканчиваться на .spec.ts, чтобы Karma запускала тесты. Обратите внимание, что, по большей степени, Karma спроектирована для запуска юнит тестов. Для запуска комплексных тестов понадобится другой инструмент, Protractor. Его мы далее и рассмотрим.
4. Protractor
Protractor – фреймворк комплексных тестов для 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 хранятся в папке e2e, а не src. Позже мы покажем, как писать комплексные тесты. Пока что напишем юнит тесты.
Написание юнит тестов
Как было сказано, в Angular есть все необходимое для написания тестов для проекта. Чтобы начать тестирование, просто запустите команду:
ng test
Karma запустит все доступные тесты. Если вы прошли урок «Tour of Heroes», у вас должен быть похожий результат:
Эти тесты создаются во время генерации компонентов, сервисов и классов с помощью инструмента 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, будет ошибка.
И наконец, у нас есть спецификация should be created, где мы подтверждаем инициализацию component. Если тест пройден, это означает, что компонент должен правильно запуститься в окружении Angular. Если тест упал, скорее всего, у компонента есть определенная зависимость, которую мы не подключили в настройках теста. Давайте посмотрим, как решать разные проблемы.
Тестирование компонента, который использует другой компонент
При создании UI в 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(); })); ...
Для вставки MockHeroService как сервиса используем опцию providers. Сделайте так для кода всех тестов компонентов, использующих этот сервис.
Тестирование сервиса
Мы разобрали общие сценарии, которые могут возникнуть при тестировании компонентов. Теперь давайте перейдем к тестированию сервисов. Сервисы выполняют базовую логику приложений. Поэтому очень важно протестировать их на правильность работы. Как говорилось ранее, тестирование Angular – обширная тема. Мы пройдемся по самой поверхности.
Откройте 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
Юнит тесты проверяют, чтобы компоненты и сервисы правильно запускались в контролируемом тестовом окружении. Однако нет гарантии, что компоненты и сервисы будут взаимодействовать друг с другом в окружении Angular. Вот почему нужно проводить комплексное тестирование. Комплексный тест моделирует тестирование человеком. То есть тест спроектирован взаимодействовать с нашим приложением точно так же, как это делаем мы – через интерфейс браузера.
В нашем приложении tour of heroes можно протестировать ряд кейсов –
На панели компонента отображается 5 героев
В компоненте героев отображаются все герои
Ссылки навигации не сломаны
Можно добавить нового героя
Героя можно обновить
Героя можно удалить
По мере реализации новых функций список можно пополнять. В идеале, комплексный тест состоит из 2 частей.
Первая часть – файл-хелпер с вспомогательными функциями для компонента. Пример 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 тесты, пора переходить к другой классной функции тестирования.
Покрытие кода
Наш самый главный вопрос, как разработчиков – «а достаточно ли кода мы протестировали?». К счастью, есть инструменты, которые умеют генерировать «покрытие кода». С его помощью можно определять объемы протестированного кода. Чтобы сгенерировать отчет, просто запустите команду:
ng test --watch=false --code-coverage
В корне проекта Angular создастся папка coverage. В папке вы найдете файл index.html. Откройте его в браузере. Перед вами будет примерно следующее:
Не буду сильно вдаваться в детали. Вы видите, что какие-то классы протестированы полностью, а другие нет. Время и ресурсы не всегда позволяют реализовать 100% тестовое покрытие. Но вместе со своей командой можно определить минимум. Для определения минимума используйте karma.conf (настройка покрытия кода):
coverageIstanbulReporter: { reports: [ 'html', 'lcovonly' ], fixWebpackSourcePaths: true, thresholds: { statements: 80, lines: 80, branches: 80, functions: 80 } }
В коде сверху указано пороговое значение 80%. Столько должно покрываться юнит тестами.
Дополнительные утилиты
Мы изучили основы тестирования Angular. Но мы можем повысить качество нашего кода.
1. Линтинг
В Angular есть инструмент для линтинга кода. Выполните следующую команду для выполнения линтинга:
ng lint
Команда покажет предупреждения в коде – например, где вы забыли точку с запятой, где использовали слишком много пробелов. Команда поможет найти неиспользуемый код и определенные ошибки в утверждениях. Частое использование этой команды приведет код, написанный всей командой к единообразному стилю. Опции линтинга можно дополнительно настроить в файле tslint.json.
2. Умные редакторы кода
Среди IDE и редакторов кода мои любимые — Atom и Sublime Text. Не так давно открыл для себя Visual Studio Code, в нем больше приятных функций. Это бесплатный редактор кода, работающий на Windows, macOS и Linux. В нем многое позаимствовано из Atom, но есть и дополнительные функции, которые я хочу выделить:
Автокомплитер кода (Intellisense)
Подсветка ошибок
Современные расширения Angular
На данный момент ни Atom ни Sublime Text не обладают этими функциями, хотя они и встроены в VSCode. Вам необходимо лишь установить требуемое расширение языка. Функция Intellisense предлагает варианты при вводе кода. С такой функцией сложно допустить синтаксическую ошибку. Также можно посмотреть документацию функции, где виден возвращаемый тип и необходимые параметры.
В Visual Studio Code есть правильная функция выделения ошибок. Она проверяет не только ошибки синтаксиса, но и следит за совпадением присваиваемых типов. Например, если попробовать присвоить массив результату функции Observable, среда разработки подсветит ошибку. В VSCode также есть расширения Angular, совместимые с Angular 5.
IDE, проверяющая код на ошибки, пока вы его пишите повышает продуктивность. Это позволяет меньше времени проводить за разрешением ошибок, которые иначе бы проскакивали. Может, есть и другие редакторы кода с такими же функциями, но пока что я рекомендую Visual Studio Code для проектов Angular.
3. Непрерывная интеграция
Непрерывная интеграция (CI) – это процесс автоматизированного тестирования и сборки. Как разработчики, мы часто работаем в изоляции по несколько недель и более. Когда приходит пора выливать изменения в master ветку, вылезает много ошибок и конфликтов. На их решение может понадобиться много времени.
CI призывает разработчиков писать тесты и комитить задачи часто и маленькими частями. CI сервер автоматически соберет и запустит тесты, помогая разработчикам отлавливать ошибки на раннем этапе. Это снижает количество конфликтов и проблем. Разработчикам Angular доступно много CI решений.
Заключение
Мы получили массу информации об автоматизированных тестах, фреймворках для разработки через тестирование, помогающих писать тесты. Тем не менее, не всегда нужно писать тесты. Несколько причин:
Не пишите тесты для нового приложения. Масштаб проекта будет меняться стремительно, в зависимости от того, что захочет клиент или поведения рынка.
В паре с реализацией функций написание тестов требует больше времени. При изменении области функции нужно время на обновление теста. Если бюджет небольшой, тесты можно не писать. Практично относитесь к ресурсам.
Остаются вопросы. Когда же нужно писать тесты? Пара примеров:
Вы прошли фазу прототипирования и четко определились с базовыми функциями приложения.
Ваш проект хорошо финансируется.
Предположим, вы решили внедрить TDD. Вы получаете много преимуществ:
Написание кода, который можно протестировать означает, что вы пишите более качественный код.
Как разработчик, вы будете чувствовать себя увереннее, выпуская последнюю версию в продакшен.
Написание тестов – это способ документирования кода. Будущим разработчикам будет легче обновлять старый код.
Не нужно нанимать человека за контролем качества, ваш CI сервер сделает все сам.
Если решили полностью отказаться от тестирования готового приложения, будьте готовы к злым и разочарованным клиентам в будущем. С ростом кода количество багов будет расти по экспоненте.
Надеюсь, это было полезное введение в тестирование Angular. Если хотите углубиться в изучение, рекомендую сначала прочитать официальную документацию Angular 5. Большая часть информации там для старых версий Angular, если не указано обратное. Пишите свои советы по тестированию Angular!
Автор: Michael Wanyoike
Источник: https://www.sitepoint.com/
Редакция: Команда webformyself.