티스토리 뷰
테스트대역(test double) 을 위키피디아에서 찾아보면
A test double is software used in software test automation that satisfies a dependency so that the test need not depend on production code
테스트더블은 소프트웨어 테스트 자동화에서 사용되는 소프트웨어로서, 테스트가 프로덕션 코드에 의존할 필요 없도록 의존성을 만족시킵니다
나름 번역한 문장도 제법 이해하기 어렵습니다
테스트가 프로덕션 코드에 의존할 필요가 없다는 말이 무엇인지 살펴볼까요
우리의 프로덕션 코드에는 굉장히 많은 의존성들을 가지고 있습니다
파일시스템이나, 데이터베이스, 외부서비스통신, 이메일, 문자메시지 등등 굉장히 많은 의존성들이 있습니다
만약 테스트코드를 작성하고 실행할때 이러한 의존성들을 그대로 사용한다면 어떤일이 발생하게 될까요?
테스트가 실행될때마다, 파일이 생성/수정/삭제되고, 데이터베이스 역시 마찬가지며, 외부 서비스를 호출하고(때에 따라 호출마다 비용이 발생할수도 있습니다), 이메일이나 문자메시지를 발송하게 됩니다(이것도 돈이죠)
바로 이러한 문제를 해결하기 위해 프로덕션 코드를 변경하지 않아도 되는 인터페이스를 통해 호환되는 가짜 의존성들을 테스트 대역이라고 말합니다
여기서 말하는 대역은 대역폭을 의미하는 그 대역이 아니라, 스턴트맨을 의미하는 그 대역입니다
이러한 테스트대역에는 다음과 같은 종류가 있습니다
제가 임의의 설명을 덧붙였지만 사람마다 조금씩 해석이 다를 수 있습니다
- dummy : 아무런 동작을 하지 않음
- stub : 지정한 값만 반환함
- fake : 테스트에 적합하도록 기능을 구현(예, 상용 데이터베이스를 대체하는 인메모리형 데이터베이스같은)
- mock : 기대하는 출력을 위해 실제 동작을 흉내냄
- spy : 실제와 똑같이 호출하고, 호출을 추적함
이렇게 테스트대역을 의미하는 다양한 용어들이 있긴합니다만
사용하는 테스트 라이브러리 또는 프레임워크에 따라 선호하는 용어가 있기도한것 같습니다
사실 의미상 엄격하게 차이가 있지는 않으므로 큰 차이를 두지 않고 mock으로 통일하기도 합니다
jest를 이용하여 여러 테스트대역을 만들어보겠습니다
mock, stub
파일시스템의 동작을 흉내내는 mock을 사용한 테스트입니다
'use strict';
jest.mock('fs');
describe('listFilesInDirectorySync', () => {
const MOCK_FILE_INFO = {
'/path/to/file1.js': 'console.log("file1 contents");',
'/path/to/file2.txt': 'file2 contents',
};
beforeEach(() => {
// Set up some mocked out file info before each test
require('fs').__setMockFiles(MOCK_FILE_INFO);
});
test('includes all files in the directory in the summary', () => {
const FileSummarizer = require('../FileSummarizer');
const fileSummary =
FileSummarizer.summarizeFilesInDirectorySync('/path/to');
expect(fileSummary.length).toBe(2);
});
});
jest 공식문서에도 나와있는 테스트 코드입니다
특정 모듈의 동작을 흉내내고 있습니다
고정된 반환값을 만든다는 의미에서 stub이라고도 부를수 있습니다
stub, dummy
지정한 값을 반환하는 의미에서 stub이라고도 부를수 있고, 외부에 호출하는 동작을 하지 않는 의미에서 dummy라고도 부를수 있는 테스트대역입니다
const axios = require('axios');
class Users {
static all() {
return axios.get('/users.json');
}
}
module.exports = Users;
const Users = require('../users');
const axios = require('axios');
jest.mock('axios');
describe('Users', () => {
it('should return users', async () => {
axios.get.mockResolvedValueOnce([{ name: 'kevin' }]);
await expect(Users.all()).resolves.toEqual([{ name: 'kevin' }]);
});
});
User 클래스의 all 메서드를 모킹하여 외부의 요청없이 결과값을 흉내내어서 전달받아 테스트를 수행합니다
fake
로컬스토리지를 흉내낸 인메모리형태의 로컬스토리지 가짜 구현체입니다
class FakeLocalStorage {
constructor() {
this.store = {};
}
clear() {
this.store = {};
}
getItem(key) {
return this.store[key] || null;
}
setItem(key, value) {
this.store[key] = String(value);
}
removeItem(key) {
delete this.store[key];
}
}
실제 localStorage를 사용하지 않지만 마치 localStorage를 사용하는것처럼 테스트코드를 작성할수 있습니다
spy
실제와 똑같이 동작하지만, 호출을 추적할수 있는 spy 입니다
const video = {
play() {
// play something
return true;
},
};
test('plays video', () => {
const spy = jest.spyOn(video, 'play');
const isPlaying = video.play();
expect(spy).toHaveBeenCalled();
expect(isPlaying).toBe(true);
});
원본의 play() 메서드가 수행되었으며, play 함수가 호출되었는지 검사할수 있습니다
jest에서 spy의 재미난점중의 하나는 spy가 jest.fn의 문법적인 설탕이라는 점입니다
spyOn(object: any, methodName: any, accessType?: string): any {
if (accessType) {
return this._spyOnProperty(object, methodName, accessType);
}
if (typeof object !== 'object' && typeof object !== 'function') {
throw new Error(
'Cannot spyOn on a primitive value; ' + this._typeOf(object) + ' given',
);
}
const original = object[methodName];
if (!this.isMockFunction(original)) {
if (typeof original !== 'function') {
throw new Error(
'Cannot spy the ' +
methodName +
' property because it is not a function; ' +
this._typeOf(original) +
' given instead',
);
}
object[methodName] = this._makeComponent({type: 'function'}, () => {
object[methodName] = original;
});
object[methodName].mockImplementation(function() {
return original.apply(this, arguments);
});
}
return object[methodName];
}
spyOn의 코드를 살펴보면
기존의 original 구현체를 저장해두고, jest.fn을 이용하여 기존의 함수를 모킹하고, 원래의 함수를 실행하도록 하고
다시 원래 함수로 복원하는 과정을 확인할 수 있습니다
'WEB2.0 > 프로그래밍' 카테고리의 다른 글
타입스크립트의 초과속성체크(excess property checks)를 알고 계셨나요? (0) | 2024.12.05 |
---|---|
더 작은 컨테이너 이미지를 만드는 매직 (0) | 2024.11.22 |
arm64 기반의 github action runner를 사용해보았습니다 (0) | 2024.06.18 |
리팩터링 내성을 키우는 lint, explicit-function-return-type (1) | 2024.03.12 |
10년 이상 시니어가 주니어의 PR 리뷰시 자주 피드백하는 것들 (1) | 2023.12.31 |
- Total
- Today
- Yesterday
- 아이폰
- 아이디어
- 벤처
- 창업
- 소프트웨어
- 자바스크립트
- 네이버
- 애플
- 모바일
- 앱스토어
- JavaScript
- 웹표준
- 어플리케이션
- 안드로이드
- Apple
- 구글
- 경진대회
- android
- 대학생
- 앱
- 트위터
- 공모전
- 스마트폰
- iPhone
- CSS
- php
- AWS
- 게임
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |