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