CHAPTER 12. Testing your app
The problem with troubleshooting is that trouble shoots back
๋๋ ์๋ ํ ์คํธ๋ฅผ ๊ถ์ฅ ํ๋ค. IDE์์ ๋ น์์ผ๋ก ํ ์คํธ ์งํ๋ฅ ํ์ ์ค์ ์ค์ฌ์ผ๋ก ์์ ์ ์ผ์ ์ ๋๋ก ํ๊ธฐ ์ํด ๋ด๊ฐ ๋ค์ ์ฐ๋ฅด๊ณ ์๋ค. ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ๊ฐ ์ฝ๋๋ฅผ ์์ฑํ ๋ ์ ์ผํ ์์ ๋ง์ด๊ธฐ ๋๋ฌธ์ ํ ์คํธ๋ ์กฐ์ฌํด์ ํด์ผ ํ๋ค. ์ฝ๋๋ฅผ ์๋์ผ๋ก ํ ์คํธํ๋ ๊ฒ๋ณด๋ค ๋ณต์กํ์ง ์๋๋ค.
Angular 2๋ ํ ์คํธ๋ฅผ ์ฝ๊ฒ ์์ฑํ ์ ์๋๋ก ๋์์ค๋ค. ๊ทธ๋์ AngularJS 1.x๋ ๋ถ๋ถ์ ์ผ๋ก ๋ด๊ฐ ๊ทธ๊ฒ์ ์ฌ์ฉํ๋ ๊ฒ์ ์ข์ํ๋ค. AngularJS 1.x์ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ ๊ฐ์ง ์ ํ์ ํ ์คํธ๋ฅผ ์์ฑํ ์ ์๋ค. โข unit tests โข end-to-end tests
์ฒซ ๋ฒ์งธ๋ ์ฝ๋์ ์์ ๋จ์ (์ปดํฌ๋ํธ, ์๋น์ค, ํ์ดํ ๋ฑ)๋ฅผ ์ฃผ์ฅํ๊ธฐ ์ํด ๋ ๋ฆฝ์ ์ผ๋ก, ์ฆ ์ข ์ ๊ด๊ณ๋ฅผ ๊ณ ๋ คํ์ง ์๊ณ ์ ๋๋ก ๋์ํ๋ค. ์ด๋ฌํ ๋จ์ ํ ์คํธ๋ฅผ ์์ฑ, ๊ตฌ์ฑ ์์ / ์๋น์ค / ํ์ดํ์ ๊ฐ ๋ฉ์๋๋ฅผ ์คํํ๊ณ ๊ทธ๊ฒ์ด ์ฐ๋ฆฌ๊ฐ ์ ๋ ฅ ํ ์ ๋ ฅ์ ๋ํ ์์๋๋ก ์ถ๋ ฅ์์ ํ์ธํด์ผ ํ๋ค. ๋ํ ์ด ์ฅ์น๊ฐ ์ฌ์ฉํ๋ ์ข ์์ฑ์ด ์ ๋๋ก ํธ์ถ๋๊ณ ์๋์ง ํ์ธ ํ ์ ์๋ค. ์๋ฅผ ๋ค์ด, ์๋น์ค๊ฐ ์ฌ๋ฐ๋ฅธ HTTP ์์ฒญ์ ์ํํ ์ง ์ฌ๋ถ๋ฅผ ํ์ธํ ์ ์๋ค.
end-to-end ํ ์คํธ๋ ๋ง๋ค ์ ์๋ค. ๊ทธ ๋ชฉ์ ์ ์ค์ ์ธ์คํด์ค๋ฅผ ์์ํ๊ณ ๋ธ๋ผ์ฐ์ ๋ฅผ ์คํํ๊ณ ์ ๋ ฅ ๊ฐ์ ์ ๋ ฅํ๊ฑฐ๋ ๋ฒํผ์ ํด๋ฆญํ๋ ๋ฑ ์ค์ ์ฌ์ฉ์์ ์์ฉ ํ๋ก๊ทธ๋จ ๊ฐ์ ์ํธ ์์ฉ์ ๋ชจ๋ฐฉํ๋ค. ๋ ๋๋ง ๋ ํ์ด์ง๊ฐ ์์๋๋ก ๋น์ ์ด ์๊ฐํ ์์๋ ๊ฒ์ด๋ฉด URL์ ์ฌ๋ฐ๋ฅธ ์ํ์ ์๋์ง ํ์ธ ํ์ฌ๋ผ.
Unit test
๋จผ์ ๋ณธ๋๋ก, ๋จ์ ํ ์คํธ๋ ์ฝ๋์ ์์ ๋จ์๋ฅผ ๋จ๋ ์ผ๋ก ์ฒดํฌ ํ๋ค. ์ด ํ ์คํธ์์๋ ์๋ ํ๋๋ก ์์ฉ ํ๋ก๊ทธ๋จ์ ์์ ๋ถ๋ถ์ ์ฃผ์ฅ ํ ์ ๋ฐ์ ์์ง๋ง, ๋ช ๊ฐ์ง ์ฅ์ ์ด ์๋ค.
โข ๊ทธ๋ค์ ์ ๋ง ๋นจ๋ฆฌ ๋น์ ์ ๋ช ์ด์์ ๋ช ๋ฐฑ๋ฒ์ ์คํ์ํฌ ์ ์๋ค. โข ์ค์ ์์ฉ ํ๋ก๊ทธ๋จ์์ ์๋์ผ๋ก ํ ์คํธํ๊ธฐ ์ด๋ ค์ด ํนํ ์ด๋ ค์ด ์ผ์ด์ค ๊ฐ์ ๋ชจ๋ ์ฝ๋๋ฅผ (๊ฑฐ์) ํ ์คํธํ๋ ๊ฒ์ ๋งค์ฐ ํจ์จ์ ์ด๋ค.
๋จ์ ํ ์คํธ์ ํต์ฌ ๊ฐ๋ ์ค ํ๋๋ ๋ถ๋ฆฌ์ ๋๋ค. ์ฐ๋ฆฌ์ ํ ์คํธ๊ฐ ์ข ์์ฑ์ ์ํด ํธํฅ๋๋ ๊ฒ์ ์ํ์ง ์์ต๋๋ค. ๊ทธ๋์ ์ฐ๋ฆฌ๋ ๋๊ฐ "mock"๊ฐ์ฒด๋ฅผ ์ข ์๋ฌผ๋ก ์ฌ์ฉํฉ๋๋ค. ์ด๋ค์ ์ฐ๋ฆฌ๊ฐ ํ ์คํธ ๋ชฉ์ ์ผ๋ก ๋ง ๋ง๋ ๊ฐ์ง ๊ฐ์ฒด์ ๋๋ค.
์ด๋ฅผ ์ํด ์ฐ๋ฆฌ๋ ๋ช ๊ฐ์ง ๋๊ตฌ์ ์์กด ํ ๊ฒ์ด๋ค. ๋จผ์ ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํ์ํ๋ค. ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ๊ฒ (๊ฐ์ฅ ์ธ๊ธฐ๊ฐ ์๋ค๋ฉด) ์ค ํ๋๊ฐ Jasmine์ด๋ฏ๋ก, ์ฐ๋ฆฌ๋ ๊ทธ๊ฒ์ ์ฌ์ฉํ ๊ฒ์ด๋ค!
Jasmine and Karma
Jasmine์ ํ ์คํธ๋ฅผ ์ ์ธ ํ ์์๋ ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
โข describe()๋ ํ ์คํธ ์ค์ํธ (ํ ์คํธ ๊ทธ๋ฃน)๋ฅผ ์ ์ธํ๋ค. โข it()์ ํ ์คํธ๋ฅผ ์ ์ธํ๋ค. โข expect()๋ ์ฃผ์ฅ์ ์ ์ธํ๋ค.
Jasmine์ ์ฌ์ฉํ ๊ธฐ๋ณธ JavaScript ํ ์คํธ๋ ๋ค์๊ณผ ๊ฐ๋ค.
class Pony {
constructor(public name: string, public speed: number) {
}
isFasterThan(speed) {
return this.speed > speed;
}
}
describe('My first test suite', () => {
it('should construct a Pony', () => {
const pony = new Pony('Rainbow Dash', 10);
expect(pony.name).toBe('Rainbow Dash');
expect(pony.speed).not.toBe(1);
expect(pony.isFasterThan(8)).toBe(true);
});
});
expect() ํธ์ถ์ toBe(), toBeLessThan(), toBeUndefined() ๋ฑ์ ๋ง์ ๋ฉ์๋๋ก ์ฐ๊ฒฐ๋ ์ ์๋ค. ๋ชจ๋ ๋ฉ์๋๋ expect()์ ์ํด ๋ฆฌํด ๋ ์ค๋ธ์ ํธ์ not ์์ฑ์ผ๋ก ๋ฌดํจํ ๋ ์ ์๋ค.
ํ ์คํธ ํ์ผ์ ํ ์คํธ ์ฝ๋์๋ ๋ค๋ฅธ ํ์ผ์์ ์ผ๋ฐ์ ์ผ๋ก .spec.ts ๊ฐ์ ํ์ฅ์๊ฐ ์๋ค. pony.ts ํ์ผ์ ์ฐ์ฌ์ง Pony ํด๋์ค์ ํ ์คํธ๋ ์๋ง pony.spec.ts๋ผ๋ ํ์ผ์ ์๋ค. ํ ์คํธ๋ ํ ์คํธ ํ์ผ ์์ ๋๋ ๊ฒ๋ ๋ชจ๋ ํ ์คํธ ์ ์ฉ ๋๋ ํ ๋ฆฌ์ ๋ ์ ์๋ค. ๋๋ ์ฝ๋์ ํ ์คํธ๋ฅผ ๊ฐ์ ๋๋ ํ ๋ฆฌ์ ๋๋ ๊ฒฝํฅ์ด ์์ง๋ง, ์ด๋ ๋ฐฉ์๋ ์์ ํ ์ ํจํ๋ค.
ํ๋์ ํ๋ฅญํ ํธ๋ฆญ์ describe() ๋์ fdescribe()๋ฅผ ์ฌ์ฉํ๋ฉด ์ด ํ ์คํธ ๋ง ์คํ๋ ๊ฒ์ด๋ค. (f๋ ์์ ์ ๋ํ๋ธ๋ค). ํ๋์ ํ ์คํธ๋ง์ ์คํํ๊ณ ์ถ์ ๊ฒฝ์ฐ๋ ์ด์ ๊ฐ์ ์ผ์ ํ๋ค : it() ๋์ fit()๋ฅผ ์ฌ์ฉํ๋ค. ํ ์คํธ๋ฅผ ๋ฐฐ์ ํ๋ ค๋ฉด ์ค์ํธ xit() ๋๋ xdescribe()๋ฅผ ์ฌ์ฉํด๋ผ.
beforeEach () ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ํ ์คํธ ์ ์ ์ปจํ ์คํธ๋ฅผ ์ค์ ํ ์๋ ์๋ค. : ์กฐ๋ช ๊ธฐ. ๋์ผํ ์กฐ๋๋ง์ ๋ํด ์ฌ๋ฌ ํ ์คํธ๋ฅผ ์ํํ๋ฉด ๋ชจ๋ ํ ์คํธ์์ ๊ฐ์ ๊ฒ์ ๋ณต์ฌ / ๋ถ์ฌ ๋ฃ๊ธฐ ํ๋ ๋์ ์กฐ๋๋ง์ ์ด๊ธฐํํ๋ beforeEach ()๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
describe('Pony', () => {
let pony: Pony;
beforeEach(() => {
pony = new Pony('Rainbow Dash', 10);
});
it('should have a name', () => {
expect(pony.name).toBe('Rainbow Dash');
});
it('should have a speed', () => {
expect(pony.speed).not.toBe(1);
expect(pony.speed).toBeGreaterThan(9);
});
});
afterEach ๋ฉ์๋๋ ์์ง๋ง ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ์ง ์๋๋ค. ๋ง์ง๋ง ํธ๋ฆญ ํ๋ : Jasmine์ ์ฌ์ฉํ๋ฉด ๊ฐ์ง ์ค๋ธ์ ํธ (mock ๋๋ spy, ์ํ๋๋๋ก)๋ฅผ ๋ง๋ค๊ฑฐ๋ ์ค์ ์ค๋ธ์ ํธ์ ๋ฉ์๋๋ฅผ ์คํ์ด๋ก ๋ง๋ค ์ ์๋ค. ๊ทธ๋ฐ ๋ค์ ๋ฉ์๋๊ฐ ํธ์ถ๋์๋์ง ํ์ธํ๋ HaveBeenCalled ()์ toHaveBeenCalledWith ()๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฌํ ๋ฉ์๋์ ๋ํ ๋ด์ฉ์ ์ํ ํ ์ ์๋ค. ์คํ์ด ๋ฉ์๋ ํธ์ถ์ ๋ํ ์ ํํ ๋งค๊ฐ ๋ณ์๋ฅผ ๊ฒ์ฌํ๋ค. ๋ฉ์๋๊ฐ ํธ์ถ ๋ ํ์๋ฅผ ํ์ธํ๊ฑฐ๋ ํธ์ถ ๋ ์ ์ด ์๋์ง ์ ๊ฒ ํ ์ ์๋ค.
describe('My first test suite with spyOn', () => {
let pony: Pony;
beforeEach(() => {
pony = new Pony('Rainbow Dash', 10);
// define a spied method
spyOn(pony, 'isFasterThan').and.returnValue(true);
});
it('should test if the Pony is fast', () => {
const runPonyRun = pony.isFasterThan(60);
expect(runPonyRun).toBe(true); // as the spied method always returns
expect(pony.isFasterThan).toHaveBeenCalled();
expect(pony.isFasterThan).toHaveBeenCalledWith(60);
});
});
๋จ์ ํ ์คํธ๋ฅผ ์์ฑํ ๋๋ ์๊ณ ์ฝ๊ธฐ ์ฝ๋๋ก ๋ช ์ฌํ๊ณ ์ฒ์์๋ ์คํจ๋ก ๋ง๋๋ ๊ฒ์ ์์ง ๋ง์๋ผ. ์ฌ๋ฐ๋ฅธ ๊ฒ์ ํ ์คํธํ๊ณ ์๋์ง ํ์ธ ํ์ฌ๋ผ.
๋ค์ ๋จ๊ณ๋ ํ ์คํธ๋ฅผ ์คํํ๋ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ Angular ํ์ Karma์ ๊ฐ๋ฐํ์๋ค. Karma์ ๋ชฉ์ ์ ํ๋ ๋๋ ์ฌ๋ฌ ๋ธ๋ผ์ฐ์ ์์ ํ ์คํธ๋ฅผ ์คํํ๋ ๊ฒ๋ฟ์ด๋ค. ๋ํ ํ์ผ์ ๋ชจ๋ํฐ๋งํ์ฌ ์ ์ฅ๋ง๋ค ํ ์คํธ๋ฅผ ๋ค์ ์ํ ํ ์ ์๋ค. ํ ์คํธ ์คํ์ ๋งค์ฐ ๋น ๋ฅด๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์คํ ์ฝ๋(๋๋ถ๋ถ) ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ๋ถ์ด๋ ๊ฒ์ ์ ๋ง ์ข์ ๊ฒ์ด๋ค.
๋๋ ์นด๋ฅด๋ง๋ฅผ ์ค์นํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์์ธํ ๋ด์ฉ์ ์ธ๊ธํ์ง ์์ง๋ง, ๋น์ ์ด ์ฌ์ฉํ ์์๋ ํ๋ฌ๊ทธ์ธ์ด ๋ง์ด ๋งค์ฐ ํฅ๋ฏธ๋ก์ด ํ๋ก์ ํธ์ด๋ค. ์ข์ํ๋ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ ์ฉ ๋ฒ์ ๋ณด๊ณ ์๋ฅผ ๋ง๋ค๊ฑฐ๋ํ๋ ๋ด์ฉ์ TypeScript์์ ์ฝ๋๋ก ์์ฑํ ์ ์๋ค. TypeScript ์ปดํ์ผ๋ฌ๊ฐ ์ฝ๋์ ํ ์คํธ๋ฅผ ๋ชจ๋ํฐ๋งํ๊ณ ๋ค๋ฅธ ์ถ๋ ฅ ๋๋ ํ ๋ฆฌ์ ์ปดํ์ผ ๋ ํ์ผ์ ์์ฑํ๊ณ ์นด๋ฅด๋ง๊ฐ ์ด ๋๋ ํ ๋ฆฌ๋ฅผ ๊ฐ์ํ๋๋ก ํ๋ ๊ฒ์ด๋ค.
์ด์ ์ฐ๋ฆฌ๋ JavaScript๋ก ๋จ์ ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์๊ณ ์๋ค. mix์ Angular 2๋ฅผ ์ถ๊ฐํฉ์๋ค.
Using dependency injection
RaceService์ ๊ฐ์ ๊ฐ๋จํ ์๋น์ค๋ก Angular 2 ์ ํ๋ฆฌ์ผ์ด์ ์ด ์๊ณ ํ๋ ์ฝ๋ ๋ ๋ ์ด์ค ๋ชฉ๋ก์ ๋ฐํํ๋ ๋ฉ์๋๊ฐ ์๋ค๊ณ ๊ฐ์ ํด๋ณด์.
export class RaceService {
list() {
const race1 = new Race('London');
const race2 = new Race('Lyon');
return [race1, race2];
}
}
ํ ์คํธ๋ฅผ ์ํด ์คํํด ๋ณด์.
describe('RaceService', () => {
it('should return races when list() is called', () => {
const raceService = new RaceService();
expect(raceService.list().length).toBe(2);
});
});
RaceService๋ฅผ ๋ฐ๊ณ ํ ์คํธ์ ์ฃผ์ ํ๊ธฐ ์ํด Angular๊ฐ ์ ๊ณตํ๋ ์์กด์ฑ ์ฃผ์ ์ ์์กด ํ ์ ์๋ค. RaceService๋ ์ผ๋ถ ์ข ์์ฑ์ด ์๋ ๊ฒฝ์ฐ ํนํ ์ ์ฉํ๋ค. ์์ ์ด ์์กด ๊ด๊ณ๋ฅผ ์ธ์คํด์คํํ๋ ๊ฒ์ด ์๋๋ผ Injector์ ์์กดํ์ฌ "์ด๋ด, ์ฐ๋ฆฌ๋ RaceService์ ์ฐพ๊ณ , ๊ทธ๊ฒ์ ๋ง๋ค์ด ๊ทธ๊ฒ์ ๋์๊ฒ ๊ฑด๋ค ์ค ํ์๊ฐ ์๋ค๊ณ ์๊ฐํ๋ค"๋ฉฐ ํ ์คํธ ํ ์ ์๋ค. ์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ฉด ํ ์คํธ ํจ์ ๋ด๋ถ์ ์ธ์ ํฐ์์ ํน์ ์ข ์์ฑ์ ์ป์ ์ ์๋ค. TestBed.get์ด ์๊ฐ์ ์ฌ์ฉํ์ฌ ์์ ๋ก ๋์๊ฐ ๋ณด์.
import { TestBed } from '@angular/core/testing';
describe('RaceService', () => {
it('should return races when list() is called', () => {
const raceService = TestBed.get(RaceService);
expect(raceService.list().length).toBe(2);
});
});
์ฐ๋ฆฌ๊ฐ ์ฑ์ ์์ํ ๋ ๋ฃจํธ ๋ชจ๋์์์ ๊ฐ์ด ์ฃผ์ ์ ์ฌ์ฉํ ์ ์๋ ๊ฒ์ด ๋ฌด์์ธ์ง ํ ์คํธ์ ์๋ฆด ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ ์ ํํ๊ฒ ์๋ํ์ง ์๋๋ค.
TestBed ํด๋์ค๋ ์ฌ๊ธฐ์ ๋์์ด ๋๋ค. ๊ทธ configureTestingModule ๋ฉ์๋๋ ํ์ํ ๊ฒ๋ง์ ํฌํจํ ํ ์คํธ ๋ชจ๋์ ์์ฑํ์ฌ ํ ์คํธ ์ฃผ์ ํ ๊ฒ์ ์ ์ธ ํ ์ ์๋ค. ๋ฌด์์ ์ฃผ์ ํ๋๊ฑธ๊น? ๋น์ ์ ํ ์คํธ์ ํ์ํ ๊ฒ๋ค์ ์์ฉ ํ๋ก๊ทธ๋จ์ ๋๋จธ์ง ๋ถ๋ถ๊ณผ ์ต๋ํ ๋์จํ๊ฒ ๊ฒฐํฉ ์ํค๋๋ก ํ์. ์ด ๋ฉ์๋๋ beforeEach Jasmine ๋ฉ์๋ ํธ์ถ ๋ชจ๋ ์ค์ ํ๋ค. @NgModule ์ฅ์์ ์ ๋ฌํ ์ ์๋ค. providers ์์ฑ์ ์ฃผ์ ์ด ๊ฐ๋ฅํ๊ฒ ๋๊ณ ์ข ์์ฑ์ ๋ฐฐ์ด์ ์ทจํ๋ค.
import { TestBed } from '@angular/core/testing';
describe('RaceService', () => {
beforeEach(() => TestBed.configureTestingModule({
providers: [RaceService]
}));
it('should return races when list() is called', () => {
const raceService = TestBed.get(RaceService);
expect(raceService.list().length).toBe(2);
});
});
์ง๊ธ์ ์ข์ ๊ฒ์ ๋๋ค! ์ฐ๋ฆฌ์ RaceService ์์ฒด์ ์ผ๋ถ ์ข ์์ฑ์ด์๋ ๊ฒฝ์ฐ๋ ๊ทธ๋ค์ inject ํน์ฑ์ผ๋ก ์ ์ธํ๊ณ ์ฃผ์ ๊ฐ๋ฅํ๊ฒ ํ ํ์๊ฐ ์๋ค๋ ์ ์ ์ ์ํด๋ผ. ๊ฐ๋จํ Jasmine์ ์์ฒ๋ผ, beforeEach ๋ฉ์๋์์ RaceService ์ด๊ธฐํ๋ฅผ ์ด๋ํ ์ ์๋ค. BeforeEach์์ TestBed.get์ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก ํ ์คํธ ํด๋ณด์.
import { TestBed } from '@angular/core/testing';
describe('RaceService', () => {
let service: RaceService;
beforeEach(() => TestBed.configureTestingModule({
providers: [RaceService]
}));
beforeEach(() => service = TestBed.get(RaceService));
it('should return races when list() is called', () => {
expect(service.list().length).toBe(2);
});
});
beforeEach์์ TestBed.getlogic์ ์ด๋ํ๋๋ฐ, ํ ์คํธ๋ ๊น๋ํ์๋ค. ์ธ์ ํฐ๋ฅผ ์ค์ ๋ก ์ฌ์ฉํ๊ธฐ ์ ์ ํญ์ TestBed.configureTestingModule (์ธ์ ํฐ๋ฅผ ์ค์ )๋ฅผ ํธ์ถํ๋๋ก ์ฃผ์ํ์ฌ๋ผ. TestBed.get ๋๋ ๋น์ ์ ํ ์คํธ๊ฐ ์คํจํ๋ค. ๋ฌผ๋ก ์ค์ RaceService์๋ ๋ ์ด์ค ํ๋ ์ฝ๋ ๋ ๋ชฉ๋ก์ ํฌํจ๋์ง ์๊ณ ์๋ต์ด ๋น๋๊ธฐ ์ผ์ด ๋ ์ข์ ๊ธฐํ๊ฐ ์๋ค. ๋ชฉ๋ก์ด ์ฝ์์ ๋ฐํํ๋ค๊ณ ๊ฐ์ ํด ๋ณด์. ๊ทธ๊ฒ์ ๋ฌด์์ด ๋ฐ๋๊น? ์ฝ๋ฐฑ์์ ๊ธฐ๋์น๋ฅผ ์ค์ ํด์ผ ํ๋ค.
import { async, TestBed } from '@angular/core/testing';
describe('RaceService', () => {
let service: RaceService;
beforeEach(() => TestBed.configureTestingModule({
providers: [RaceService]
}));
beforeEach(() => service = TestBed.get(RaceService));
it('should return a promise of 2 races', async(() => {
service.list().then(races => {
expect(races.length).toBe(2);
});
}));
});
์ฝ์์ด ํด์ ๋๊ธฐ ์ ์ ํ ์คํธ๊ฐ ์ข ๋ฃ๋๊ณ ์ฐ๋ฆฌ์ ๊ธฐ๋๋ ๊ฒฐ์ฝ ์คํ๋์ง ์๊ธฐ ๋๋ฌธ์, ์ด๊ฒ์ ์๋ชป ์๊ฐํ๊ณ ์์์ง๋ ๋ชจ๋ฅธ๋ค. ๊ทธ๋ฌ๋ ์ฌ๊ธฐ์์๋ async () ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ๋ฅผ ์ํํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ๋ฐฉ๋ฒ์ ์ ๋ง ํจ์จ์ ์ด๋ค. ํ ์คํธ์์ ์ด๋ฆฐ ๋น๋๊ธฐ ํธ์ถ์ ์ถ์ ํ๊ณ ํด๊ฒฐ ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค.
Angular 2 ์์ญ์ด๋ผ๋ ์๋ก์ด ๊ฐ๋ ์ ์ฌ์ฉํ์. ์ด๋ฌํ ์์ญ์ ์คํ ์ปจํ ์คํธ๋ฅผ ๊ฐ์ง๊ณ ๋จ์ํํ๊ธฐ ์ํด ์คํ์ค์ธ ๋ชจ๋ ํ๋ก์ธ์ค (์๊ฐ ์ ํ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ฝ๋ฐฑ ...)๋ฅผ ์ถ์ ํ๋ค. ๋ํ ์์ญ์ ๋ค๋ฝ๋ ๋ฝ ํ ๋ ํธ์ถ๋๋ ํํฌ๋ฅผ ์ ๊ณตํ๋ค. Angular 2 ์์ฉ ํ๋ก๊ทธ๋จ ์์ญ์์ ์๋ํ๋ค. ์ด๊ฒ์ ๋น๋๊ธฐ ์์ ์ด ์๋ฃ ๋ ๋ DOM์ ๊ฐฑ์ ํ ํ์๊ฐ ์๋ ๊ฒ์ ํ๋ ์ ์ํฌ๊ฐ ์ธ์ํ๊ณ ์ ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
์ด ๊ฐ๋ ์ ํ ์คํธ์์ async()๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ ํ ์คํธ์์ ์ฌ์ฉ๋๋ค. ํ ์คํธ๋ ์์ญ์์ ์คํ๋๋ฏ๋ก ํ๋ ์ ์ํฌ๋ ๋ชจ๋ ๋น๋๊ธฐ ์์ ์ด ์๋ฃ๋ ์์ ์ ์๊ณ ๊ทธ๋๊น์ง ์๋ฃ๋์ง ์๋๋ค. ๊ทธ๋์ ์ฐ๋ฆฌ์ ๋น๋๊ธฐ์ ์ธ ๊ธฐ๋๊ฐ ์คํ๋ ๊ฒ์ด๋ค.
fakeAsync ()์ tick ()์ ์ฌ์ฉํ์ฌ Angular 2์์ ๋น๋๊ธฐ ํ ์คํธ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ด ์์ง๋ง ์ด๋ ๋ ๊ณ ๊ธ ์ฅ์์ํ ๊ฒ์ด๋ค.
Fake dependencies
ํ ์คํธ ๋ชจ๋์ ๋ํ ์ข ์์ฑ์ ์ ์ธ ํ ์์๋ ๋ค๋ฅธ ์ฉ๋๋ฅผ ๊ฐ์ง๊ณ ์๋ค. ์ค์ ์๋น์ค ๋์ ๊ฐ์ง ์๋น์ค๋ฅผ ์ข ์ ์๋น์ค๋ก ์ ์ธํ๋ ๊ฒ์ ๋๋ฌด ๋ง์ ์๊ณ ๋ฅผ ๋ค์ด์ง ์๊ณ ๋ ํ ์ ์๋ค. ์ด ์์์๋ ๋ด RaceService๊ฐ ๋ก์ปฌ ์คํ ๋ฆฌ์ง๋ฅผ ์ฌ์ฉํ์ฌ ๋ ์ด์ค๋ฅผ ์ ์ฅํ๊ณ ํค 'race'๋ฅผ ๊ฐ์ง๊ณ ์๋ค๊ณ ํ์. ๋น์ ์ ๋๋ฃ๋ ์ฐ๋ฆฌ์ RaceService๊ฐ ์ฌ์ฉํ๋ JSON ์ง๋ ฌํ ๋ฑ์ ์ทจ๊ธ LocalStorageService๋ผ๋ ์๋น์ค๋ฅผ ๊ฐ๋ฐํ์๋ค. list ()ํจ์๋ ๋ค์๊ณผ ๊ฐ๋ค.
@Injectable()
export class RaceService {
constructor(private localStorage: LocalStorageService) {
}
list() {
return this.localStorage.get('races');
}
}
์ด์ ์ฐ๋ฆฌ๋ LocalStorageService ์๋น์ค๋ฅผ ํ ์คํธํ๊ณ ์ถ์ง ์๋๋ค. RaceService๋ฅผ ํ ์คํธํ๊ณ ์ถ๋ค. ์์กด์ฑ ์ฃผ์ ์์คํ ์ ํ์ฉํ์ฌ ์์กฐ ๋ LocalStorageService๋ฅผ ์ ๊ณตํจ์ผ๋ก์จ ์ฝ๊ฒ ์ํ ํ ์ ์๋ค.
class FakeLocalStorage {
get(key) {
return [{ name: 'Lyon' }, { name: 'London' }];
}
}
provide๋ฅผ ์ฌ์ฉํ์ฌ RaceService๋ฅผ ํ ์คํธ๋ก ํ ์ ์๋ค.
import { TestBed } from '@angular/core/testing';
describe('RaceService', () => {
beforeEach(() => TestBed.configureTestingModule({
providers: [
{ provide: LocalStorageService, useClass: FakeLocalStorage },
RaceService
]
}));
it('should return 2 races from localStorage', () => {
const service = TestBed.get(RaceService);
const races = service.list();
expect(races.length).toBe(2);
});
});
ํ์ง๋ง ์ด ํ ์คํธ์ ์์ ํ ๋ง์กฑํ์ง๋ ์๋๋ค. ๊ฐ์ง ์๋น์ค๋ฅผ ๋ง๋๋ ๊ฒ์ ์ง๋ฃจํ๊ณ ์ฌ์ค๋ฏผ์ ์๋น์ค๋ฅผ ๊ฐ์งํ๊ณ ๊ตฌํ์ ๊ฐ์ง๋ก ๋์ฒดํ๋ ๊ฒ์ ๋์ธ ์ ์์ต๋๋ค. ๋ํ get () ๋ฉ์๋๊ฐ ์ฌ๋ฐ๋ฅธ ํค์ธ 'race'์ ํจ๊ป ํธ์ถ๋์๋์ง ํ์ธํ ์ ์๋ค.
import { TestBed } from '@angular/core/testing';
describe('RaceService', () => {
const localStorage = jasmine.createSpyObj('LocalStorageService', ['get']);
beforeEach(() => TestBed.configureTestingModule({
providers: [
{ provide: LocalStorageService, useValue: localStorage },
RaceService
]
}));
it('should return 2 races from localStorage', () => {
localStorage.get.and.returnValue([{ name: 'Lyon' }, { name: 'London' }]);
const service = TestBed.get(RaceService);
const races = service.list();
expect(races.length).toBe(2);
expect(localStorage.get).toHaveBeenCalledWith('races');
});
});
Testing components
๊ฐ๋จํ ์๋น์ค๋ฅผ ํ ์คํธ ํ ํ ๋ค์ ๋จ๊ณ๋ ๊ตฌ์ฑ ์์๋ฅผ ํ ์คํธํ๋ ๊ฒ์ด๋ค. ๊ตฌ์ฑ ์์ ํ ์คํธ๋ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋ค์ด์ผํ๊ธฐ ๋๋ฌธ์ ์ฝ๊ฐ ๋ค๋ฅด๋ค. ์์กด์ฑ ์ฃผ์ ์์คํ ์ ์ฌ์ฉํ์ฌ ํ ์คํธ ํ ๊ตฌ์ฑ ์์์ ์ธ์คํด์ค๋ฅผ ์ ๊ณต ํ ์๋ ์๋ค. (์ด์ ๋ ๋ค๋ฅธ ๊ตฌ์ฑ ์์์ ๊ตฌ์ฑ ์์๊ฐ ์ฃผ์ ๊ฐ๋ฅํ์ง ์์์ ๋์น ์ฑ์ ๊ฒ์ด๋ค :))
๋จผ์ ํ ์คํธ ํ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํด ๋ณด์. ์ PonyComponent ์ปดํฌ๋ํธ๊ฐ ํ์ํ์ง ์๋๊ฐ? ํฌ๋๋ฅผ ์ ๋ ฅ์ผ๋ก ๊ฐ์ ธ์ค๊ณ ๊ตฌ์ฑ ์์๋ฅผ ํด๋ฆญ ํ ๋ ํด๋ฆญ ํ ์ด๋ฒคํธ ํฌ๋๋ฅผ ๋ด ๋ณด๋ด ๋ณด์.
@Component({
selector: 'ns-pony',
template: `<img [src]="'/images/pony-' + pony.color.toLowerCase() + '.png'" (click)=
"clickOnPony()">`
})
export class PonyComponent {
@Input() pony: PonyModel;
@Output() ponyClicked = new EventEmitter<PonyModel>();
clickOnPony() {
this.ponyClicked.emit(this.pony);
}
}
๊ทธ๊ฒ์ ๋งค์ฐ ๊ฐ๋จํ ํ ํ๋ฆฟ๊ณผ ํจ๊ป ์ ๊ณต๋๋ค. : ํฌ๋ ์์์ ๋ฐ๋ผ ๋์ ์์ค๊ฐ ์๋ ์ด๋ฏธ์ง ๋ฐ ํด๋ฆญ ํธ๋ค๋ฌ
์ด๋ฌํ ๊ตฌ์ฑ ์์๋ฅผ ํ ์คํธํ๋ ค๋ฉด ๋จผ์ ์ธ์คํด์ค๋ฅผ ์์ฑํด์ผ ํ๋ค. ์ด๋ ๊ฒ ํ๊ธฐ ์ํด TestBed๋ ์ฌ์ฉํ ์ ์๋ค. ์ด ํด๋์ค๋ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋ค๊ธฐ ์ํ createComponentํ๋ ์ ํธ๋ฆฌํฐ ๋ฉ์๋๊ฐ ํฌํจ๋์ด ์๋ค. ์ด ๋ฉ์๋๋ ๊ตฌ์ฑ ์์์ ํํ์ด๋ค ComponentFixture์ ๋ฐํํ๋ค. ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋ค๋ ค๋ฉด ํ ์คํธ ๋ชจ๋์์ ๊ตฌ์ฑ ์์๋ฅผ ์ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ declarations ์์ฑ์ ์ถ๊ฐํด์ผ ํ๋ค.
import { TestBed } from '@angular/core/testing';
import { PonyComponent } from './pony_cmp';
describe('PonyComponent', () => {
it('should have an image', () => {
TestBed.configureTestingModule({
declarations: [PonyComponent]
});
const fixture = TestBed.createComponent(PonyComponent);
// given a component instance with a pony input initialized
const ponyComponent = fixture.componentInstance;
ponyComponent.pony = { name: 'Rainbow Dash', color: 'BLUE' };
// when we trigger the change detection
fixture.detectChanges();
// then we should have an image with the correct source attribute
// depending of the pony color
const element = fixture.nativeElement;
expect(element.querySelector('img').getAttribute('src')).toBe('/images/pony-blue.png');
});
});
์ฌ๊ธฐ์์๋ "Given/When/Then" ํจํด์ ๋ฐ๋ผ ๋จ์ ํ ์คํธ๋ฅผ ์์ฑํด์ผ ํ๋ค. ๋น์ ์ ๊ทธ ์ฃผ์ ์ ๋ํ ์ ๋ฌธ์ ์ฐพ์ ์ ์๋ค.
โข ํ ์คํธ ์ปจํ ์คํธ๋ฅผ ์ค์ ํ๋ "Given"๋จ๊ณ. ์์ฑ๋ ๊ตฌ์ฑ ์์ ์ธ์คํด์ค๋ฅผ ๊ฐ์ ธ ์กฐ๋๋ง์ ์ ๊ณตํ๋ค. ์ค์ ์์ฉ ํ๋ก๊ทธ๋จ์ ๋ถ๋ชจ ๊ตฌ์ฑ ์์๋ก๋ถํฐ์ ์ ๋ ฅ์ ์๋ฎฌ๋ ์ดํธ ํ๋ค. โข detectChanges () ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์๋์ผ๋ก ๋ณ๊ฒฝ ํ์ง๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ "When"๋จ๊ณ. ํ ์คํธ๋ ๋ณ๊ฒฝ์ ๊ฒ์ถ์ ์ฐ๋ฆฌ์ ์ฑ ์์ด๋ค. ์์ฉ ํ๋ก๊ทธ๋จ์์๋ ๊ฒ์ฒ๋ผ ์๋์ผ๋ก ํ์ง ์๋๋ค. โข ๊ธฐ๋๋ฅผ ํฌํจ "Then"๋จ๊ณ. ์๋ฅผ ๋ค์ด, querySelector ()๋ฅผ ์ฌ์ฉํ์ฌ ๋ธ๋ผ์ฐ์ ์ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ธฐ๋ณธ ์์๋ฅผ ์ทจ๋ํ๊ณ DOM์ ์ฟผ๋ฆฌ ํ ์ ์๋ค. ์ฌ๊ธฐ์์๋ ์ด๋ฏธ์ง ์์ค๊ฐ ์ฌ๋ฐ๋ฅธ ๊ฒ์ธ์ง๋ฅผ ํ ์คํธ ํด์ผ ํ๋ค.
๊ตฌ์ฑ ์์๊ฐ ์ค์ ๋ก ์ด๋ฒคํธ๋ฅผ ๋ฐฉ์ถํ๋์ง ํ ์คํธ ํ ์๋ ์๋ค.
it('should emit an event on click', () => {
TestBed.configureTestingModule({
declarations: [PonyComponent]
});
const fixture = TestBed.createComponent(PonyComponent);
// given a pony
const ponyComponent = fixture.componentInstance;
ponyComponent.pony = { name: 'Rainbow Dash', color: 'BLUE' };
// we fake the event emitter with a spy
spyOn(ponyComponent.ponyClicked, 'emit');
// when we click on the pony
const element = fixture.nativeElement;
const image = element.querySelector('img');
image.dispatchEvent(new Event('click'));
// and we trigger the change detection
fixture.detectChanges();
// then the event emitter should have fired an event
expect(ponyComponent.ponyClicked.emit).toHaveBeenCalled();
});
๋ค๋ฅธ ๊ตฌ์ฑ ์์๋ฅผ ์ดํด ๋ณด๊ฒ ์ต๋๋ค.
@Component({
selector: 'ns-race',
template: `<div>
<h1>{{race.name}}</h1>
<ns-pony *ngFor="let currentPony of race.ponies" [pony]="currentPony"></ns-pony>
</div>`
})
export class RaceComponent {
@Input() race: any;
}
๊ทธ๋ฆฌ๊ณ ํ ์คํธ ํด๋ณด์.
describe('RaceComponent', () => {
let fixture: ComponentFixture<RaceComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [RaceComponent, PonyComponent]
});
fixture = TestBed.createComponent(RaceComponent);
});
it('should have a name and a list of ponies', () => {
// given a component instance with a race input initialized
const raceComponent = fixture.componentInstance;
raceComponent.race = { name: 'London', ponies: [{ name: 'Rainbow Dash', color: 'BLUE'
}] };
// when we trigger the change detection
fixture.detectChanges();
// then we should have a name with the race name
const element = fixture.nativeElement;
expect(element.querySelector('h1').textContent).toBe('London');
// and a list of ponies
const ponies = fixture.debugElement.queryAll(By.directive(PonyComponent));
expect(ponies.length).toBe(1);
// we can check if the pony is correctly initialized
const rainbowDash = ponies[0].componentInstance.pony;
expect(rainbowDash.name).toBe('Rainbow Dash');
});
});
์ฌ๊ธฐ์์๋ ์ฒซ ๋ฒ์งธ ํฌ๋๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ด๊ธฐํ๋์๋์ง ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ์ํด PonyComponent ํ์ ๋ชจ๋ ์ง์์ด์ test๋ฅผ ์กฐํ ํ๋ค.
์์ ์ปดํผ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌ์ฑ ์์์ ๊ตฌ์ฑ ์์๋ฅผ ๊ฐ์ ธ ์ค๊ฑฐ๋ query () ๋ฐ queryAll ()๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌ์ฑ ์์๋ฅผ ์ฟผ๋ฆฌ ํ ์ ์๋ค. ์ด ๋ฉ์๋๋ ์ธ์๋ก ์ ์ด๋ฅผ ์ทจํ๋ค. ์ด๊ฒ์ By.cssor By.directive๋ ๊ด์ฐฎ๋ค. ์ด๊ฒ์ PonyComponent์ ์ธ์คํด์ค์ด๋ฏ๋ก ์กฐ๋๋ง์ ํ์ํ๊ธฐ ์ํด ์ค์ํ๋ ๊ฒ์ด๋ค. ์ด๊ฒ์ querySelector ()๋ฅผ ์ฌ์ฉํ์ฌ DOM ์ฟผ๋ฆฌ์๋ ๋ค๋ฅด๋ค. Angular ์ํด ์ฒ๋ฆฌ๋๋ ์์๋ง์ ๊ฒ์ํ์ฌ DOM ์์๊ฐ ์๋ ComponentFixture๋ฅผ ๋ฐํํ๋ค. (๋ฐ๋ผ์ ๊ฒฐ๊ณผ componentInstance์ ์ก์ธ์ค ํ ์ ์๋ค)
Testing with fake templates, providersโฆ
๊ตฌ์ฑ ์์๋ฅผ ํ ์คํธ ํ ๋ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ๋ ๋ถ๋ชจ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋ค ์ ์๋ค. ์ ์ค ์ผ์ด์ค๊ฐ ์ฌ๋ฌ ๊ฐ์๋ ๊ฒฝ์ฐ ๋ค๋ฅธ ์ ๋ ฅ์ ์๋ํ๋ ์ผ๋ถ ๋ถ๋ชจ ๊ตฌ์ฑ ์์๋ฅผ ์์ฑํด์ผ ํ๋ค. ์ํ๋ฉด ํ ์คํธ ์ค์ ๊ตฌ์ฑ ์์๋ฅผ ๋ณ๊ฒฝํ์ฌ ํ ํ๋ฆฟ์ ์ฌ์ ์ํ๋ฉด ๋ค์ํ ํ ์คํธ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฌ์ฉ ํ ์ ์๋ค.
์ด๋ ๊ฒ ํ๊ธฐ ์ํด TestBed๋ overrideComponent () ๋ฉ์๋๋ฅผ ์ ๊ณตํ๊ณ createComponent () ๋ฉ์๋ ์ ์ ํธ์ถํ๋ค.
describe('RaceComponent', () => {
let fixture: ComponentFixture<RaceComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [RaceComponent, PonyComponent]
});
TestBed.overrideComponent(RaceComponent, { set: { template: '<h2>{{race.name}}</h2>'
} });
fixture = TestBed.createComponent(RaceComponent);
});
it('should have a name', () => {
// given a component instance with a race input initialized
const raceComponent = fixture.componentInstance;
raceComponent.race = { name: 'London' };
// when we trigger the change detection
fixture.detectChanges();
// then we should have a name
const element = fixture.nativeElement;
expect(element.querySelector('h2').textContent).toBe('London');
});
});
๋ณด์๋ค์ํผ,์ด ๋ฉ์๋๋ ๋ ๊ฐ์ง ์ธ์๋ฅผ ์ทจํ๋ค. โข ๋ฌด์ํ๋ ค๋ ๊ตฌ์ฑ ์์ โข ์ค์ ํ๊ฑฐ๋ ์ถ๊ฐํ๊ฑฐ๋ ์ ๊ฑฐํ๋ ค๋ ๋ฉํ ๋ฐ์ดํฐ (์ : ํ ํ๋ฆฟ์ ์ค์ )
์ฆ, ํ ์คํธ ์ค์ธ ๊ตฌ์ฑ ์์์ ํ ํ๋ฆฟ ๋๋ ํด๋น ํ์ ์ค ํ๋๋ฅผ ์์ ํ ์ ์๋ค. (๊ตฌ์ฑ ์์๋ฅผ ํฐ ํ ํ๋ฆฟ์ผ๋ก ๋ฐ๊พธ๋ ค๋ฉด ๋ฌด์ธ๊ฐ๋ฅผ ๋ฐ๊พผ๋ค.)
ํ ํ๋ฆฟ ๋ง ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฉํ ๋ฐ์ดํฐ๊ฐ ์๋๋ค.
โข ๊ณต๊ธ์๊ฐ ๊ตฌ์ฑ ์์์ ์ข ์์ฑ์ ๋์ฒด โข ๊ตฌ์ฑ ์์์ ํ ํ๋ฆฟ์ ์ฌ์ฉ ๋ ์คํ์ผ์ ๋์ฒด ํ ์คํ์ผ โข ๋๋ @Component ๋ฐ์ฝ๋ ์ดํฐ์์ ์ค์ ํ ์์๋ ๋ชจ๋ ์์ฑ
End-to-end tests (e2e)
์๋ - ํฌ - ์๋ ํ ์คํธ๋ ์ํ ํ ์์๋ ๋ ๋ค๋ฅธ ์ ํ์ ํ ์คํธ์ด๋ค. ์๋ - ํฌ - ์๋ ํ ์คํธ๋ ์ค์ ๋ก ๋ธ๋ผ์ฐ์ ์์ ์์ฉ ํ๋ก๊ทธ๋จ์ ์์ํ๊ณ ๋ฒํผ์ ํด๋ฆญํ๊ฑฐ๋ ์์์ ์์ฑํ๋ ๋ฑ ์ฌ์ฉ์์ ์ํธ ์์ฉํ๋ ์ฌ์ฉ์๋ฅผ ์๋ฎฌ๋ ์ดํธ ํ๋ค. ๊ทธ๋ค์ ์ ์ฒด ์์ฉ ํ๋ก๊ทธ๋จ์ ์ ๋ง ํ ์คํธํ๋ ์ฅ์ ์ด ์๋ค:
โข ์๋๊ฐ ๋๋ฆฐ (ํ ์คํธ๋ง๋ค ๋ช ์ด) โข ์ฃ์ง ์ผ์ด์ค๋ฅผ ํ ์คํธํ๋ ๊ฒ์ ์ด๋ ต๋ค.
๋น์ ์ด ์ถ์ธกํ๋ ๊ฒ์ฒ๋ผ ๋จ์ ํ ์คํธ์ e2e ํ ์คํธ ์ค ํ๋๋ฅผ ์ ํํ ํ์๊ฐ ์๋ค. ๋์ ๊ฒฐํฉํ์ฌ ์ ์ฒด ์์ฉ ํ๋ก๊ทธ๋จ๊ณผ ์๋ ํ๋๋ก ์๋ํ๋ค๋ ๋ณด์ฅ์๋ฐ์ ์ ์๋ค. E2e ํ ์คํธ๋ Angular๋ผ๋ ๋๊ตฌ์ ์์กดํ๊ณ ์๋ค. AngularJS 1.x์์ ๊ฐ์ ๋ชฉ์ ์ผ๋ก ์ฌ์ฉํ๋ ๋๊ตฌ์ ๋์ผํ๋ค. ๋๋ผ์ด ๊ฒ์, AngularJS 1.x์ Angular 2 ๋ชจ๋์์ ์๋ํ๋ค. ๋จ์ ํ ์คํธ์ฒ๋ผ Jasmine๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ๋ฅผ ๋ง๋ค์ง ๋ง, Angular API๋ฅผ ์ฌ์ฉํ์ฌ ์์ฉ ํ๋ก๊ทธ๋จ๊ณผ ์ํธ ์์ฉํ๋ค.
๊ฐ๋จํ ํ ์คํธ๋ ๋ค์๊ณผ ๊ฐ๋ค.
describe('Home', () => {
it('should display title, tagline and logo', () => {
browser.get('/');
expect(element.all(by.css('img')).count()).toEqual(1);
expect($('h1').getText()).toContain('PonyRacer');
expect($('small').getText()).toBe('Always a pleasure to bet on ponies');
});
});
Angular๋ ์ฐ๋ฆฌ ๋ธ๋ผ์ฐ์ ๊ฐ์ฒด๋ฅผ์ฃผ๊ณ get()๊ณผ ๊ฐ์ ๋ช ๊ฐ์ง ์ ํธ๋ฆฌํฐ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ํ์ด์ง๋ก ์ด๋ํ๋ค. ๋ค์ ์ ์ด์ ์ผ์นํ๋ ๋ชจ๋ ์์๋ฅผ ์ ํํ๊ธฐ ์ํด element.all ()๋ฅผ ์ฌ์ฉํ๋ค. ์ด ์กฐ๊ฑด์ ์ข ์ข ๋ค์ํ ๋ฐฉ๋ฒ (by.css () id ๋ฑ์์ ์์๋ฅผ ์ทจ๋ํ๋ CSS ์กฐํ, by.id ()์ ์์กดํ๋ค). element.all()๋ ์์ ํ ์คํธ์์ ์ฌ์ฉ๋ ํน๋ณํ ๋ฐฉ๋ฒ count()๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ์์ ๋ฐํํ๋ค.
$ ('h1')์ ์์ (by.css ( 'h1'))๋ฅผ ์ธ ๊ฒฝ์ฐ์ ํด๋นํ๋ ๋จ์ถํค ์ด๋ค. CSS ์ฟผ๋ฆฌ์ ์ผ์นํ๋ ์ต์ด์ ์์๋ฅผ ๊ฐ์ ธ์จ๋ค. ์ ๋ณด๋ฅผ ์ป์ getText()์ getAttribute()์ ์ด ์์์ ์์ฉํ๋ click()๊ณผ sendKeys()์ ๊ฐ์ ๋ฉ์๋์ฒ๋ผ $()์ ์ํด ๋ฐํ ๋ ์ฝ์์ ๋ํด ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ฌ์ฉ ์ ์๋ค.
์ด๋ฌํ ํ ์คํธ๋ ์ฐ๊ธฐ ๋ฐ ๋๋ฒ๊น (๋จ์ ํ ์คํธ๋ณด๋ค ํจ์ฌ ๊ธด)์ด ์๋นํ ๊ธธ์ด์ง ์ ์์ง๋ง, ์ค์ ๋ก๋ ํธ๋ฆฌํ๋ค. ๋น์ ์ ์ด๋ค ๋ธ๋ผ์ฐ์ ๋ฅผ ํ ์คํธํ๋ ํ ์คํธ๊ฐ ์คํจ ํ ๋๋ง๋ค ์คํฌ๋ฆฐ ์ท์ ์ฐ๋ ๋ฑ ๊ทธ๋ค์ ์ฌ์ฉํ์ฌ ๋ชจ๋ ์ข ๋ฅ์ ์ข์ ์ผ์ ํ ์ ์๋ค. ๋จ์ ํ ์คํธ์ e2e ํ ์คํธ๋ ๊ฒฌ๊ณ ํ๊ณ ์ ์ง ๋ณด์ ํ ์์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ๋ ์ด์ ๊ฐ ๋๋ค.
Reference URL
Last updated
Was this helpful?