1. Stub 방식
const UserService = require('../user_service');
const StubUserClient = require('./stub_user_client');
describe('UserService - Stub', () => {
let userService;
beforeEach(() => {
userService = new UserService(new StubUserClient());
});
it('should log in successfully', async () => {
const data = await userService.login('testUser', 'password');
expect(userService.isLogedIn).toBe(true);
expect(data).toEqual({ userId: 'testUser', token: 'fake-token' });
});
});
장점 :
- stub은 특정 메서드의 반환값을 미리 정의하여 간단하게 테스트할 수 있음. 실제로 API 호출을 하지 않으므로 테스트가 빠르다.
- stub에서 반환하는 값이 고정되어 있어, 테스트 결과가 항상 일관된다.
단점:
- stub은 주로 반환값을 설정하는 데에만 집중하므로, 메서드 호출 횟수나 호출 여부 같은 부분은 검증하가 어렵다.
2. Mock 방식
const UserService = require('../user_service');
describe('UserService - Mock', () => {
let userService;
let userClientMock;
beforeEach(() => {
userClientMock = {
login: jest.fn().mockResolvedValue({ userId: 'testUser', token: 'fake-token' }),
};
userService = new UserService(userClientMock);
});
it('should log in successfully', async () => {
const data = await userService.login('testUser', 'password');
expect(userClientMock.login).toHaveBeenCalledWith('testUser', 'password');
expect(userService.isLogedIn).toBe(true);
expect(data).toEqual({ userId: 'testUser', token: 'fake-token' });
});
});
장점:
- mock은 메서드 호출 횟수, 호출 파라미터 등을 검증할 수 있어, 테스트의 정확성을 높일 수 있다.
- 다양한 시나리오를 쉽게 시뮬레이션할 수 있어, 복잡한 비즈니스 로직을 테스트하는 데 유리하다.
단점:
- mock을 설정하는 과정이 stub보다 복잡할 수 있으며, 잘 못 설정할 경우 테스트가 실패할 수 있다.
3. 결론 - 어떤 방법을 선택할까?
- 간단한 테스트 : login 메서드의 기본적인 동작을 검증하는 경우, stub 방식이 더 간단하고 빠르다.
- 로그인 시도 횟수나 특정 조건에서의 동작을 검증하고 싶다면 mock 방식이 더 적합하다.
테스트를 작성할 때, 검사하는 메서드의 기능이 "어떻게" 작동하는지가 아니라 "무엇을"하는지에 집중해야 한다. 마치 우리가 휴대폰을 사용할 때, 휴대폰 내부의 복잡한 회로나 프로그램을 알 필요 없이 그저 버튼을 눌러 원하는 기능을 사용하는 것과 같다.
class Calculator {
add(a, b) {
// 내부 구현은 복잡할 수 있어요
let result = 0;
for (let i = 0; i < b; i++) {
result += a;
}
return result;
}
}
test('2 더하기 3은 5이다', () => {
const calc = new Calculator();
expect(calc.add(2, 3)).toBe(5);
});
test('0 더하기 0은 0이다', () => {
const calc = new Calculator();
expect(calc.add(0, 0)).toBe(0);
});
위의 테스트들은 계산기가 실제로 올바른 결과를 내는지만을 확인하고 있다. 결과야말로 인류 모두가 정말과 관심이 있는 부분이다....
왜 이렇게 해야 할까라고 하면,
1. 유연성 및 재사용 : 나중에 덧셈 함수의 내부 구현을 바꾸더라도 (예를 들어, 더 빠른 방법을 찾았다면), 테스트는 여전히 유효하다.
2. 명확성 : 테스트를 보는 사람이 그 기능이 무엇을 해야 하는지 쉽게 이해할 수 있다.
3. 유지보수 : 내부 구현이 바뀔 때마다 테스트를 수정할 필요가 없다.
결론적으로, 테스트를 작성할 때 "이 기능이 사용자에게 어던 결과를 제공해야 하는가?"에 초점을 맞춰야 한다.
좀 더 구체적으로 보면, 메서드가 "무엇을 하는지"와 "어떤 결과를 반환하는지"에 초점을 맞추는 것이다.
1. 입력 : 메서드에 어떤 값을 넣었는지
2. 출력 : 그 결과로 어떤 값이 나오는지
3. 동작 : 메서드가 어떤 일을 수행했는지 (예: 데이터베이스에 저장, 이메일 전송 등)
이런 접근 방식은 "블랙박스 테스팅"이라고도 불리며, 메서드의 외부에서 관찰 가능한 동작에만 집중한다.
이는 실제 사용자의 관점에서 소프트웨어를 테스트하는 것과 유사하다.
'TDD' 카테고리의 다른 글
테스트 코드를 리액트에 적용해보자 (1) | 2024.09.05 |
---|---|
리액트에서 테스트는? (1) | 2024.09.05 |
TDD 예제 2 (Dependency Injection)에 대해서 (3) | 2024.09.02 |
테스트 코드의 원칙을 다시 상기하자. (0) | 2024.08.29 |
TDD 예제(2) (1) | 2024.08.29 |