Angular 2
각도 2 앱 테스트
수색…
재스민 테스트 프레임 워크 설치
Angular 2 앱을 테스트하는 가장 일반적인 방법은 Jasmine 테스트 프레임 워크를 사용하는 것입니다. Jasmine을 사용하면 브라우저에서 코드를 테스트 할 수 있습니다.
설치
시작하려면 jasmine-core
패키지 ( jasmine
아님) 만 있으면됩니다.
npm install jasmine-core --save-dev --save-exact
확인
Jasmine이 제대로 설정되었는지 확인하려면 다음 내용으로 ./src/unit-tests.html
파일을 만들고 브라우저에서 엽니 다.
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <title>Ng App Unit Tests</title> <link rel="stylesheet" href="../node_modules/jasmine-core/lib/jasmine-core/jasmine.css"> <script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script> <script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script> <script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script> </head> <body> <!-- Unit Testing Chapter #1: Proof of life. --> <script> it('true is true', function () { expect(true).toEqual(true); }); </script> </body> </html>
Gulp, Webpack, Karma 및 Jasmine을 사용한 테스트 설정
우리가 필요로하는 첫 번째 작업은 Webpack 엔진을 위해 설정 한 구성에서 카르마에게 Webpack을 사용하여 테스트를 읽도록 지시하는 것입니다. 여기서 ES6에서 코드를 작성하기 때문에 바벨 (bavelle)을 사용하고 있습니다. Typescript와 같은 다른 맛을 위해 변경할 수 있습니다. 또는 Pug (이전의 Jade) 템플릿을 사용하면 필요하지 않습니다.
여전히 전략은 동일하게 유지됩니다.
그래서 이것은 webpack 설정입니다 :
const webpack = require("webpack");
let packConfig = {
entry: {},
output: {},
plugins:[
new webpack.DefinePlugin({
ENVIRONMENT: JSON.stringify('test')
})
],
module: {
loaders: [
{
test: /\.js$/,
exclude:/(node_modules|bower_components)/,
loader: "babel",
query:{
presets:["es2015", "angular2"]
}
},
{
test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/,
loader: "file"
},
{
test: /\.scss$/,
loaders: ["style", "css", "sass"]
},
{
test: /\.pug$/,
loader: 'pug-html-loader'
},
]
},
devtool : 'inline-cheap-source-map'
};
module.exports = packConfig;
그런 다음 webpack config를 사용하려면 karma.config.js 파일이 필요합니다.
const packConfig = require("./webpack.config.js");
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
exclude:[],
files: [
{pattern: './karma.shim.js', watched: false}
],
preprocessors: {
"./karma.shim.js":["webpack"]
},
webpack: packConfig,
webpackServer: {noInfo: true},
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
browsers: ['PhantomJS'],
concurrency: Infinity,
autoWatch: false,
singleRun: true
});
};
지금까지, 우리는 웹팩을 사용하는 업보를 말한, 우리는라는 파일 karma.shim.js에서 시작하는 데 말했습니다. 이 파일은 webpack의 시작점 역할을 할 것입니다. webpack은이 파일을 읽고 import 와 require 문을 사용하여 모든 의존성을 모으고 테스트를 실행합니다.
이제 karma.shim.js 파일을 살펴 보겠습니다.
// Start of ES6 Specific stuff
import "es6-shim";
import "es6-promise";
import "reflect-metadata";
// End of ES6 Specific stuff
import "zone.js/dist/zone";
import "zone.js/dist/long-stack-trace-zone";
import "zone.js/dist/jasmine-patch";
import "zone.js/dist/async-test";
import "zone.js/dist/fake-async-test";
import "zone.js/dist/sync-test";
import "zone.js/dist/proxy-zone";
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
Error.stackTraceLimit = Infinity;
import {TestBed} from "@angular/core/testing";
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "@angular/platform-browser-dynamic/testing";
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting());
let testContext = require.context('../src/app', true, /\.spec\.js/);
testContext.keys().forEach(testContext);
본질적으로 우리는 각 코어 테스트에서 TestBed 를 가져 와서 모든 테스트에 대해 한 번만 시작해야하므로 환경을 시작합니다. 그런 다음 src / app 디렉토리를 반복적으로 통과 하고 .spec.js로 끝나는 모든 파일을 읽고 testContext에 제공하여 실행합니다.
나는 보통 시험을 수업과 같은 장소에 두려고 노력한다. Personat의 취향에 따라 클래스를 사용하여 종속성 및 리팩토링 테스트를보다 쉽게 가져올 수 있습니다. 하지만 src / test 디렉토리 아래와 같이 테스트를 다른 곳에 두려면 여기에 기회가 주어집니다. karma.shim.js 파일에서 마지막 행을 변경하십시오.
완전한. 무엇이 남았나? 아아, 위에서 만든 karma.config.js 파일을 사용하는 꿀꺽 꿀꺽하는 일 :
gulp.task("karmaTests",function(done){
var Server = require("karma").Server;
new Server({
configFile : "./karma.config.js",
singleRun: true,
autoWatch: false
}, function(result){
return result ? done(new Error(`Karma failed with error code ${result}`)):done();
}).start();
});
지금 우리가 만든 config 파일로 서버를 시작하고, 한 번 실행되도록 지시하고 변경 사항을 감시하지 않습니다. 나는 그들이 시험 할 준비가되어있을 때에 만 시험이 진행될 것이므로 스위트 룸에 더 좋다고 느낍니다.하지만 당신이 달라 지길 원한다면 변경해야 할 곳을 알 수 있습니다.
그리고 마지막 코드 샘플로 여기 Angular 2 튜토리얼 인 "Tour of Heroes"에 대한 테스트 세트가 있습니다.
import {
TestBed,
ComponentFixture,
async
} from "@angular/core/testing";
import {AppComponent} from "./app.component";
import {AppModule} from "./app.module";
import Hero from "./hero/hero";
describe("App Component", function () {
beforeEach(()=> {
TestBed.configureTestingModule({
imports: [AppModule]
});
this.fixture = TestBed.createComponent(AppComponent);
this.fixture.detectChanges();
});
it("Should have a title", async(()=> {
this.fixture.whenStable().then(()=> {
expect(this.fixture.componentInstance.title).toEqual("Tour of Heros");
});
}));
it("Should have a hero", async(()=> {
this.fixture.whenStable().then(()=> {
expect(this.fixture.componentInstance.selectedHero).toBeNull();
});
}));
it("Should have an array of heros", async(()=>
this.fixture.whenStable().then(()=> {
const cmp = this.fixture.componentInstance;
expect(cmp.heroes).toBeDefined("component should have a list of heroes");
expect(cmp.heroes.length).toEqual(10, "heroes list should have 10 members");
cmp.heroes.map((h, i)=> {
expect(h instanceof Hero).toBeTruthy(`member ${i} is not a Hero instance. ${h}`)
});
})));
it("Should have one list item per hero", async(()=>
this.fixture.whenStable().then(()=> {
const ul = this.fixture.nativeElement.querySelector("ul.heroes");
const li = Array.prototype.slice.call(
this.fixture.nativeElement.querySelectorAll("ul.heroes>li"));
const cmp = this.fixture.componentInstance;
expect(ul).toBeTruthy("There should be an unnumbered list for heroes");
expect(li.length).toEqual(cmp.heroes.length, "there should be one li for each hero");
li.forEach((li, i)=> {
expect(li.querySelector("span.badge"))
.toBeTruthy(`hero ${i} has to have a span for id`);
expect(li.querySelector("span.badge").textContent.trim())
.toEqual(cmp.heroes[i].id.toString(), `hero ${i} had wrong id displayed`);
expect(li.textContent)
.toMatch(cmp.heroes[i].name, `hero ${i} has wrong name displayed`);
});
})));
it("should have correct styling of hero items", async(()=>
this.fixture.whenStable().then(()=> {
const hero = this.fixture.nativeElement.querySelector("ul.heroes>li");
const win = hero.ownerDocument.defaultView ||hero.ownerDocument.parentWindow;
const styles = win.getComputedStyle(hero);
expect(styles["cursor"]).toEqual("pointer", "cursor should be pointer on hero");
expect(styles["borderRadius"]).toEqual("4px", "borderRadius should be 4px");
})));
it("should have a click handler for hero items",async(()=>
this.fixture.whenStable().then(()=>{
const cmp = this.fixture.componentInstance;
expect(cmp.onSelect)
.toBeDefined("should have a click handler for heros");
expect(this.fixture.nativeElement.querySelector("input.heroName"))
.toBeNull("should not show the hero details when no hero has been selected");
expect(this.fixture.nativeElement.querySelector("ul.heroes li.selected"))
.toBeNull("Should not have any selected heroes at start");
spyOn(cmp,"onSelect").and.callThrough();
this.fixture.nativeElement.querySelectorAll("ul.heroes li")[5].click();
expect(cmp.onSelect)
.toHaveBeenCalledWith(cmp.heroes[5]);
expect(cmp.selectedHero)
.toEqual(cmp.heroes[5], "click on hero should change hero");
})
));
});
여기서 주목할 점은 beforeEach () 가 테스트 모듈을 구성하고 테스트에서 컴포넌트를 생성하는 방법과 detectChanges ()를 호출하여 각도가 실제로 이중 바인딩과 all을 통과하도록하는 방법입니다.
각 테스트는 async ()에 대한 호출이며 fixture를 검사하기 전에 언제나 StStable 이 해결할 것을 기다립니다. 그런 다음 componentInstance를 통해 구성 요소에 액세스하고 nativeElement 를 통해 요소에 액세스합니다.
올바른 스타일링을 점검하는 테스트가 있습니다. 자습서의 일부로 각도 팀은 구성 요소 내부에서 스타일을 사용하는 방법을 보여줍니다. 우리의 테스트에서는 getComputedStyle () 을 사용하여 스타일이 우리가 지정한 곳에서 왔는지 확인했지만 Window 객체가 필요합니다. 테스트에서 볼 수있는 것처럼 요소에서 가져옵니다.
HTTP 서비스 테스트
일반적으로 서비스는 원격 API를 호출하여 데이터를 검색 / 전송합니다. 그러나 단위 테스트는 네트워크 호출을해서는 안됩니다. Angular는 내부적으로 XHRBackend
클래스를 사용하여 HTTP 요청을 처리합니다. 사용자는이 동작을 변경하여 동작을 변경할 수 있습니다. 각도 테스트 모듈은 http 요청을 테스트하고 어설 션하는 데 사용할 수있는 MockBackend
및 MockConnection
클래스를 제공합니다.
posts.service.ts
이 서비스는 게시물 목록을 가져 오기 위해 API 끝점에 posts.service.ts
합니다.
import { Http } from '@angular/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/rx'; import 'rxjs/add/operator/map'; export interface IPost { userId: number; id: number; title: string; body: string; } @Injectable() export class PostsService { posts: IPost[]; private postsUri = 'http://jsonplaceholder.typicode.com/posts'; constructor(private http: Http) { } get(): Observable<IPost[]> { return this.http.get(this.postsUri) .map((response) => response.json()); } }
posts.service.spec.ts
여기서는 http API 호출을 조롱하여 위의 서비스를 테스트합니다.
import { TestBed, inject, fakeAsync } from '@angular/core/testing'; import { HttpModule, XHRBackend, ResponseOptions, Response, RequestMethod } from '@angular/http'; import { MockBackend, MockConnection } from '@angular/http/testing'; import { PostsService } from './posts.service'; describe('PostsService', () => { // Mock http response const mockResponse = [ { 'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto' }, { 'userId': 1, 'id': 2, 'title': 'qui est esse', 'body': 'est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla' }, { 'userId': 1, 'id': 3, 'title': 'ea molestias quasi exercitationem repellat qui ipsa sit aut', 'body': 'et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut' }, { 'userId': 1, 'id': 4, 'title': 'eum et est occaecati', 'body': 'ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit' } ]; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpModule], providers: [ { provide: XHRBackend, // This provides mocked XHR backend useClass: MockBackend }, PostsService ] }); }); it('should return posts retrieved from Api', fakeAsync( inject([XHRBackend, PostsService], (mockBackend, postsService) => { mockBackend.connections.subscribe( (connection: MockConnection) => { // Assert that service has requested correct url with expected method expect(connection.request.method).toBe(RequestMethod.Get); expect(connection.request.url).toBe('http://jsonplaceholder.typicode.com/posts'); // Send mock response connection.mockRespond(new Response(new ResponseOptions({ body: mockResponse }))); }); postsService.get() .subscribe((posts) => { expect(posts).toBe(mockResponse); }); }))); });
각도 구성 요소 테스트 - 기본
구성 요소 코드는 다음과 같습니다.
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<h1>{{title}}</h1>'
})
export class MyAppComponent{
title = 'welcome';
}
각도 테스트의 경우 angle은 좋은 테스트 케이스를 작성하는 데 도움이되는 테스트 프레임 워크와 함께 테스트 유틸리티를 제공합니다. 각도 유틸리티는 @angular/core/testing
에서 가져올 수 있습니다.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyAppComponent } from './banner-inline.component';
describe('Tests for MyAppComponent', () => {
let fixture: ComponentFixture<MyAppComponent>;
let comp: MyAppComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
MyAppComponent
]
});
});
beforeEach(() => {
fixture = TestBed.createComponent(MyAppComponent);
comp = fixture.componentInstance;
});
it('should create the MyAppComponent', () => {
expect(comp).toBeTruthy();
});
});
위의 예에서 컴포넌트 존재에 대한 테스트 케이스를 설명하는 테스트 케이스는 하나뿐입니다. 위의 예제에서 TestBed
및 ComponentFixture
와 같은 각도 테스트 유틸리티가 사용됩니다.
TestBed
는 각도 테스트 모듈을 만드는 데 사용되며 configureTestingModule
메소드로이 모듈을 configureTestingModule
하여 테스트하려는 클래스의 모듈 환경을 생성합니다. beforeEach
함수에서 테스트 모듈을 구성하는 이유 인 모든 테스트 케이스를 실행하기 전에 구성 할 테스트 모듈.
TestBed
createComponent
메소드는 테스트중인 컴포넌트의 인스턴스를 생성하는 데 사용됩니다. createComponent
는 ComponentFixture
반환합니다. Fixture는 컴포넌트 인스턴스 자체에 대한 액세스를 제공합니다.