역시 뭐든지 기초, 기본은 "할 만 한데😦?"...하지만 조금 난이도가 올라가면 뉴비에게는 "지옥"이다...
class UserClient {
async login(id, password) {
const response = await fetch('https://example.com/login/id+password');
return response.json();
}
}
module.exports = UserClient;
class UserService {
constructor(userClient) {
this.userClient = userClient;
this.isLogedIn = false;
}
async login(id, password) {
if (!this.isLogedIn) {
const data = await this.userClient.login(id, password);
this.isLogedIn = true;
return data;
}
}
}
module.exports = UserService;
이렇게 두 Class가 있다는 가정 하에 테스트 코드에 대한 이야기다.
<상황 가정>
상상해보자. 나는 학교라는 큰 건물에 가서 수업을 들으러 가는 학생이다.
- 학교는 여러 교실이 있어. 각 교실에서는 다양한 선생님이 수업을 진행해.
- 나는 수학 수업을 듣고 싶은데, 수학 선생님이 수업을 진행해. 이 때, 수학 선생님이 중요한 역할을 해.
이제, 수학 선생님이 계산 문제를 풀어주는 예를 들어보자.
# 테스트 코드(1)
const UserClient = require('../user_client');
const UserService = require('../user_service');
jest.mock('../user_client.js');
describe('UserService', () => {
const login = jest.fn(async () => 'success');
UserClient.mockImplementation(() => {
return {
login: login,
};
});
let userService;
beforeEach(() => {
userService = new UserService(new UserClient());
});
it('calls login() on UserClient when tries to login', async () => {
await userService.login('testUser', 'password');
expect(login).toHaveBeenCalledTimes(1);
});
it('should not call login() on UserClient again if already logged in', async () => {
await userService.login('testUser', 'password');
await userService.login('testUser', 'password');
expect(login).toHaveBeenCalledTimes(1);
});
});
1. 테스트 코드(1)
# 비유
- 상황 : 나는 수학 선생님의 수업을 듣기 위해 수학 선생님을 직접 학교에 초대했어.
- 설정 : 수학 선생님이 수업을 시작할 때, 문제를 직접 준비하고, 문제를 푸는 방법도 직접 설정해.
- 테스트 : 나는 수학 선생님이 수업을 잘 진행하는지 확인하려고 해. 그래서 수업을 한 번 진행하고, 수학 선생님이 수업을 제대로 진행했는지 확인하려 한다.
=> 내가 수학 선생님을 학교에 직접 초대하고, 수업을 진행하는 방식을 설정한 다음, 수업이 잘 진행되는지 확인하는 것과 같다.
2. 코드에 대한 설명
- 테스트 코드(1)에서는 UserClient 모듈 전체를 mock하고, UserClient의 인스턴스를 생성해서 사용하는 방식이다.
즉, UserClient 클래스의 모든 인스턴스에서 login 메서드가 mock된다.
그리고 UserService를 생성할 때 실제 UserClient 인스턴스를 사용한다. 이로 인해 UserClient의 login 메서드가 mock된 동작으로 대체된다.
=> 모듈 전체를 mock하고, UserClient의 인스턴스를 직접 생성하여 사용하는 방식
# 테스트 코드(2)
const UserClient = require('../user_client');
const UserService = require('../user_service');
describe('UserService', () => {
let userService;
let mockLogin;
beforeEach(() => {
mockLogin = jest.fn().mockResolvedValue({ success: true });
const mockUserClient = { login: mockLogin };
userService = new UserService(mockUserClient);
});
it('should call UserClient.login and return user data on successful login', async () => {
const data = await userService.login('testuser', 'password123');
expect(mockLogin).toHaveBeenCalledWith('testuser', 'password123');
expect(data).toEqual({ success: true });
expect(userService.isLogedIn).toBe(true);
});
});
1. 테스트 코드(2)
# 비유
- 상황 : 나는 수학 선생님이 수업을 할 때 사용할 문제집을 가지고 있다. 이 문제집은 미리 준비되어 있다.
- 설정 : 문제집은 이미 정해진 문제와 답을 가지고 있고, 나는 이 문제집을 사용해서 수학 선생님이 수업을 진행하도록 한다.
- 테스트 : 나는 수학 선생님이 이 문제를 잘 풀어보는지, 그리고 수업이 잘 진행되는지 확인한다.
=> 나는 문제집을 사용해서 수학 선생님이 수업을 진행하도록 하고, 이 문제집ㅇ이 문제와 답이 정확한지, 그리고 선생님이 문제를 잘 푸는지 확인하는 것과 같다.
2. 코드에 대한 설명
UserClient를 직접 mock 객체로 주입한다. UserService를 생성할 때 mockUserClient를 주입하여, 테스트할 때 UserClient의 실제 인스턴스가 아니라 mock된 인스턴스를 사용한다. 이로 인해 UserClient의 login 메서드만 mock되며, 테스트에 필요한 동작을 정확히 설정할 수 있다.
차이점 요약
첫 번째 테스트 코드
- 어떻게 : 실제 수학 선생님을 초대하고 수업을 진행하게 한다.
- 장점 : 모든 성정이 포함되어 있으므로, 클래스의 실제 동작을 잘 테스트할 수 있다.
- 단점 : 테스트의 설정이 복잡할 수 있다.
두 번째 테스트 코드
- 어떻게 : 문제집을 준비해 놓고, 이 문제집을 사용해 테스트를 진행한다.
- 장점 : 특정 부분만 테스트할 수 있어서, 테스트가 더 간단하고 빠를 수 있다.
- 단점 : 실제 동작을 완전히 확인하기 어려울 수 있다.
'TDD' 카테고리의 다른 글
React에서의 단위, 통합 테스트의 경계에 대해 (0) | 2024.09.19 |
---|---|
TDD 흐름 이해 및 적용...이제 스택 알고리즘을 더한...(연습) (1) | 2024.09.18 |
렌더링 테스트와 스냅샷 테스트에 대해 (1) | 2024.09.09 |
테스트 코드를 리액트에 적용해보자 (1) | 2024.09.05 |
리액트에서 테스트는? (1) | 2024.09.05 |