테스트 하기 어려운 코드는 최대한 분리한다.
만약 기존에 있는 대부분의 코드가 테스트 하기 어렵다면 테스트하기 쉬운 부분과 어려운 부분이 뒤섞여 있는 코드일 확률이 높다. 따라서 분리를 진행한다.
출처 글에 많은 영향을 받았다. how-to-write-more-testable-code
이전 글에서 언급했던 유형 1. 외부의 영향을 받는 코드를 테스트하기 쉽게 바꿔보자.
function isMorning() {
const now = new Date();
if (now.getHours() < 12) return true;
return false;
}
위의 코드를 아래로 바꾼다면 테스트가 쉬워질 것이다.
function isMorning(now:Date) {
if (now.getHours() < 12) return true;
return false;
}
전과 달라진 점은 외부의 영향을 받던 요소를 Control가능 하게 변경한 것이다.
이렇게 되면 Date 객체를 생성하여 매개변수로 넘겨줌으로써 신뢰할 수 있는 테스트를 진행 할 수 있다. 이러한 기법을 DI(Dependency Injection)이라고 한다.(적어도 내가 이해한 DI 기법은 그렇다.)
이처럼 외부의 영향을 받는 코드를 테스트하기 좋게 바꿀때는 주로 DI 기법이 사용된다.
유형 2. 외부에 영향을 주는 코드
아래와 같이 회원을 생성하는 코드가 있다.
async function createUser(name:string, email:string) {
if(name.length < 2) {
throw new Error(`${name}가 너무 짧습니다.`);
}
if(!email.contains("@")) {
throw new Error(`이메일 형식이 아닙니다.`);
}
return await userRepository.create(name, email);
}
위 코드는 return await userRepository.create(name, email);
이 부분 때문에 테스트 하기 어렵다.
그렇다면 분리해보자.
필자가 최근에 알게 된 개념중에 값객체(value Object)라는게 있는데 이를 한번 적용해보자
우선 최종적인 createUser Func는 다음과 같이 변경될 것이다.
async function createUser(name:Name, email:Email) {
return await userRepository.create(name.toString(), email.toString());
}
createUser는 name과 email의 두 매개변수를 string 타입이 아닌 class로 받는다.
각 Name, Email class는 다음과 같다.
class Name {
private name: string;
constructor(name: string) {
if(name.length < 2) {
throw new Error(`${name}가 너무 짧습니다.`);
}
this.name = name
}
toString() {
return this.name;
}
}
class Email {
private email: string;
constructor(email: string) {
if(!email.contains("@")) {
throw new Error(`이메일 형식이 아닙니다.`);
}
this.email = email
}
toString() {
return this.email;
}
}
이렇게 분리를 진행하면 자동화가 가능한 테스트(외부에 영향을 주지 않는 코드 테스트)
비용이 큰 테스트가 필요한 테스트(변경된 createUser는 DB에 record를 생성하는 역할만 한다.)로 분리할 수 있다.
물론 기업의 Production레벨 코드는 훨씬 더 복잡하겠지만 이런식으로 코드를 분리하는 것이 테스트하기도 쉽고 응집성있는 코드 구조가 될 것이라 생각하는 바이다.
'Test' 카테고리의 다른 글
테스트 하지 않기 (0) | 2025.03.07 |
---|---|
테스트하기 좋은코드, 안좋은 코드 (0) | 2025.03.07 |