테스트 코드 방식에 대한 고찰..테스트 코드 2가지 방식

 

역시 뭐든지 기초, 기본은 "할 만 한데😦?"...하지만 조금 난이도가 올라가면 뉴비에게는 "지옥"이다...

 

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되며, 테스트에 필요한 동작을 정확히 설정할 수 있다.

 


 

차이점 요약

 

첫 번째 테스트 코드

 

- 어떻게 : 실제 수학 선생님을 초대하고 수업을 진행하게 한다.

- 장점 : 모든 성정이 포함되어 있으므로, 클래스의 실제 동작을 잘 테스트할 수 있다.

- 단점 : 테스트의 설정이 복잡할 수 있다.

 

두 번째 테스트 코드

 

- 어떻게 : 문제집을 준비해 놓고, 이 문제집을 사용해 테스트를 진행한다.

- 장점 : 특정 부분만 테스트할 수 있어서, 테스트가 더 간단하고 빠를 수 있다.

- 단점 : 실제 동작을 완전히 확인하기 어려울 수 있다.