테스트 데이터 빌더로 더 효율적인 단위 테스트 설정하기
2025-02-18 10:08:54테스트 데이터 빌더 패턴을 통한 테스트 객체 생성 효율화
단위 테스트를 위한 데이터 생성은 초기에 쉽고 간단해 보일 수 있습니다. 그러나 테스트 케이스가 늘어나고 복잡성이 증가함에 따라 점점 더 많은 시간이 객체 생성에 소비되곤 합니다. 이때 유용한 방법이 '테스트 데이터 빌더(Test Data Builder)' 패턴입니다. 이 글에서는 이 패턴이 어떻게 작동하며, 왜 사용해야 하는지 살펴보겠습니다.
단위 테스트의 복잡성 증가 문제
단위 테스트의 시작은 일반적으로 몇 개의 간단한 케이스로 시작합니다. 그 후 프로젝트가 성장하면서 여러 시나리오를 커버해야 할 필요성이 생깁니다. 예를 들어, 어떤 유저 객체를 생성한다고 할 때, 초기에는 그냥 기본적인 유저 정보만 넣었겠지만, 이후에는 등록된 회원, 미등록 회원 등 다양한 상태를 테스트해야 합니다. 이는 자연스럽게 여러 테스트 케이스의 중복과 혼란으로 이어지게 됩니다.
테스트 데이터 빌더 패턴이란?
테스트 데이터 빌더는 객체 생성의 복잡성을 줄여주는 디자인 패턴입니다. 이 패턴을 이용하면 객체 초기화 코드의 간결성을 유지하면서도 세부적인 설정을 손쉽게 변경할 수 있습니다. 특히, 생성해야 하는 다양한 객체 조합이 있을 때, 데이터 빌더는 안전하고 재사용 가능한 방법을 제공합니다. 간단히 말해, 데이터 빌더는 특정 상태를 가진 객체를 빠르고 정확하게 생성하게 합니다.
TypeScript로 구현한 테스트 데이터 빌더
테스트 데이터 빌더 패턴은 TypeScript로 쉽게 구현할 수 있습니다. TypeScript의 클래스와 제네릭을 활용하여 유연하고 강력한 객체 생성 기능을 구현할 수 있습니다. 아래는 TypeScript로 작성한 테스트 데이터 빌더 클래스의 간단한 예제입니다:
type FactoryOrValue<T> = T | (() => T);
type TargetConstructor<T> = new(...args: any[]) => T;
export abstract class TestDataBuilder<Target> {
protected customValues: { [key: string]: FactoryOrValue<any> } = {};
protected propertyNames: string[]
protected readonly cls: TargetConstructor<Target>;
protected readonly defaultValues: { [key: string]: FactoryOrValue<any> };
protected constructor(params: {
cls: TargetConstructor<Target>,
props: { [key: string]: FactoryOrValue<any> },
}) {
this.defaultValues = params.props;
this.cls = params.cls;
this.propertyNames = Object.keys(params.props);
}
build(): Target {
return new this.cls(this.propertyNames.reduce((prev, cur) => ({
...prev,
[cur]: this.generate(cur)
}), {}) as any);
}
withCustomAttribute(key: string, value: unknown) {
this.customValues[key] = value;
return this;
}
protected generate(key: string): any {
const factoryOrValue = this.customValues[key] ?? this.defaultValues[key];
if (typeof factoryOrValue === "function") {
return factoryOrValue();
}
return factoryOrValue;
}
}
코드 리뷰: 중요한 요소들
이 코드에서 주목해야 할 부분은 FactoryOrValue와 TargetConstructor라는 타입 정의입니다. FactoryOrValue는 단순한 값이나, 값을 반환하는 함수 특히 chance.string()과 같은 무작위 데이터 생성 함수를 지원합니다. TargetConstructor는 클래스의 생성자를 정의하여, 빌더가 클래스의 인스턴스를 생성할 수 있도록 합니다.
또한, defaultValues와 customValues는 각각 기본값과 사용자 정의 값을 저장합니다. 이 값들은 generate 메소드를 통해 결정되며, 이는 데이터를 다루는 데 유연성을 제공합니다. build 메소드는 설정된 데이터를 기반으로 객체를 반환합니다.
테스트 데이터 빌더를 사용하는 이유
테스트 데이터 빌더를 활용하면 객체 생성 코드가 정리되고 중복이 줄어듭니다. 이는 유지보수를 용이하게 하고, 새로운 요구사항이 생겼을 때 빠르게 대응할 수 있는 기반이 됩니다. 또한, 테스트 시나리오가 증가함에 따라 코드의 복잡성이나 가독성이 떨어지는 문제를 효과적으로 관리할 수 있습니다.
결론
테스트 데이터 빌더 패턴은 테스트의 효율성을 높이는 실용적인 도구입니다. 복잡한 객체 상태를 쉽게 정의하고 재사용 가능하게 하여, 테스트 케이스의 안전성을 높입니다. 꾸준한 코드를 유지하고, 생산성을 높이기 위해 이 패턴을 활용해 보는 것은 어떨까요?
참고 자료
- Test Data Builders: A Pattern, not a Framework
- devfullcycle/test-data-builder
- Robson Castilho's Article on Test Data Builders
위 링크를 참고하여 더 깊이 있는 테스트 데이터 빌더 패턴에 대한 이해를 높일 수 있습니다.