보통 의존성이 얽혀있는 코드는 테스트하기가 어렵다. 하지만 mocking을 사용하면 매우 쉽게 테스트 코드를 작성할 수 있다.
(mocking은 "모의 객체로 외부의 다른 서비스나 모듈들을 실제 사용하는 모듈을 사용하지 않고 실제의 모듈을 "흉내"내는 "가짜" 모듈을 사용하는 객체" 이다.)
먼저 mocking을 사용하지 않고 테스트 코드를 작성해 보면
// blog.service.ts
import { Test1Repository } from './test2.repository';
import { Test2Repository } from './test2.repository2';
import { Test3Repository } from './test2.repository3';
import { Test4Repository } from './test2.repository4';
export class TestService {
constructor(
private _test1Repository: Test1Repository,
private _test2Repository: Test2Repository,
private _test3Repository: Test3Repository,
private _test4Repository: Test4Repository,
) {}
async 더하기(a: number, b: number): Promise<number> {
const 데이터베이스에있는숫자 = await this._test4Repository.findNumber();
if (!데이터베이스에있는숫자) {
return 0;
}
return a + b + 데이터베이스에있는숫자;
}
}
해당 코드는 TestService라는 클래스 안에 Test1Repository ~ Test4Repository를 주입받고 있다.
1. Mocking 없이 가짜 객체 사용
// mocking을 사용하지 않은 blog.test.ts
class MockTest1Repository {
// 기타 repository 코드들 1
}
class MockTest2Repository {
// 기타 repository 코드들 2
}
class MockTest3Repository {
// 기타 repository 코드들 3
}
class MockTest4Repository {
async updateShortfall(num: number): Promise<boolean> {
return true;
}
async findNumber() {
return 2;
}
}
describe('mocking 없이 가짜 객체를 이용한 테스트 코드 작성', () => {
it('더하기 함수 테스트코드', async () => {
const mockTest1Repository = new MockTest1Repository();
const mockTest2Repository = new MockTest2Repository();
const mockTest3Repository = new MockTest3Repository();
const mockTest4Repository = new MockTest4Repository();
const testService = new TestService(mockTest1Repository, mockTest2Repository, mockTest3Repository, mockTest4Repository);
const result = await testService.더하기(2, 3);
expect(result).toBe(7);
});
});
해당 테스트 코드를 보면 repository에 대한 의존성을 회피하기 위해서 MockTest1Repository ~ MockTest2Repository를 만들고 해당 클래스들을 테스트할 TestService layer에 주입하고 있다.
다음은 mocking을 사용한 테스트 코드의 예제이다 (여기선 ts-mockito 사용)
2. Mocking 사용
// mocking을 사용한 mockingBlog.test.ts
describe('mocking을 이용한 테스트 코드 작성', () => {
it('더하기 함수 테스트코드', async () => {
// given
const stubTest4Repository: Test4Repository = mock(Test4Repository);
when(await stubTest4Repository.findNumber()).thenReturn(2);
const sut = new TestService(
instance(stubTest4Repository),
instance(stubTest4Repository),
instance(stubTest4Repository),
instance(stubTest4Repository),
);
// when
const result = await sut.더하기(2, 3);
// then
expect(result).toBe(7);
});
});
mocking을 사용하지 않은 blog.test.ts 와 mocking을 사용한 mockingBlog.test.ts를 비교해 보면 세팅 부분에서 mocking을 사용한쪽이 가짜 객체를 선언하지 않아도 되기 때문에 훨씬 간단한 것을 볼 수 있다. 이처럼 mocking을 사용하면 테스트하려고 하는 코드의 환경 세팅이나 외부 의존성을 쉽게 사용할 수 있다.
사실 테스트 코드를 공부하면서 mocking을 사용해 세팅하는 것도 너무 길다고 느껴져서 mocking과 가짜 객체를 섞어서 작성해보기도 했다. 코드는 아래와 같다.
3. Mocking + 가짜객체 사용
// mocking + 가짜객체 둘다 활용한 테스트 코드
class MockTest4Repository {
async updateShortfall(num: number): Promise<boolean> {
return true;
}
async findNumber() {
return 2;
}
}
describe('mocking + 가짜객체 둘다 활용한 테스트 코드 작성', () => {
it('더하기 함수 테스트코드', async () => {
// given
const mockTest4Repository = new MockTest4Repository();
const 데이터베이스에있는숫자 = await mockTest4Repository.findNumber();
const testService = mock(TestService);
when(await testService.더하기(2, 3)).thenReturn(2 + 3 + 데이터베이스에있는숫자);
// when
const service = instance(testService);
// then
const result = await service.더하기(2, 3);
expect(result).toBe(7);
});
});
그런데 이렇게 작성하게 되면 테스트 해야하는 Service 계층도 mocking을 해야하는 문제가 발생한다. 그럼 결국 두번째 방법처럼 mocking을 통해 테스트를 구현해야 할까?
테스트와 관련된 많은 영상들이나 글을 보면 mocking의 남용은 좋지 않다고 얘기하고 있다. 이유는 mocking을 사용하면 손쉽게 테스트를 작성할 수 있지만, 코드 단계에서의 설계가 좋은 설계인지 나쁜 설계인지를 판별할 수 없게 한다는 것이다. 다음 글에서는 위의 TestService 클래스가 갖고 있는 문제점이 뭔지, 해당 문제점을 해결하고 mocking 없이 테스트를 작성해보려고 한다.
'테스트 코드' 카테고리의 다른 글
테스트하기 쉬운 코드, 구조 (2) : mocking 없이 테스트 코드 작성하기 (0) | 2023.01.27 |
---|---|
테스트 코드 공부 기록_ 20221006 (1) | 2022.10.06 |