톰 롱의 <좋은 코드, 나쁜 코드>를 읽다가 메모
테스트 코드 작성 시 의존성 주입 사용에 대해 설명하기 위한 예시가 이메일 발송과 관련되어 있다.
이메일 발송 기능이 들어가는 테스트 코드를 작성했던 개인적인 경험(흑역사)이 떠올랐다.
당시 작성했던 코드의 문제를 깨달은 과정을 적어본다.
작가는 아래 두 클래스의 예를 들어 의존성 주입을 사용하면 테스트 용이성이 크게 향상된다고 한다.
의존성을 주입하지 않은 클래스 (not good)
// 고객에게 송장 리마인더를 보내는 클래스
class InvoiceReminder {
private final AddressBook addressBook;
private final EmailSender emailSender;
// 여기를 보자!
// 의존 객체가 생성자에서 생성된다.
// 하드 코딩된 의존성
InvoiceReminder() {
this.addressBook = DataStore.getAddressBook();
this.emailSender = new EmailSenderImpl();
}
Boolean sendReminder(Invoice invoice) {
EmailAddress ? address = addressBook.lookupEmailAddress(invoice.getCustomerId());
if(address == null) {
return false;
}
return emailSender.send(
address,
InvoiceReminder.generate(invoice));
}
}
위 클래스로 테스트 하기 적절하지 못한 이유
- 생성자 내에서 AddressBook 인스턴스를 생성한다.
- 데이터는 언제든 변경될 수 있기 때문에 테스트에 적합하지 않다.
- 실제 DB에 접근 권한이 없는 경우 테스트가 불가능할 수 있다.
- EmailSenderImpl을 생성하여 실제로 이메일을 보내는데 이는 테스트에서 일어나면 안 되는 부수 효과이다.
- 테스트로부터 외부 세계를 보호해야 한다.
내 경험
- 메일 발송 코드가 들어가는 로직의 테스트를 작성
- 개발 환경에서 클라이언트사 계정으로 테스트가 가능하다고 하여 테스트를 할 때마다 진짜 메일을 보내버림(아.............)
- 100건 테스트하다가 에러가 발생
- 알고 보니 계정 관련 SMTP 에러
- 문제는 테스트를 하다가 외부 세계와 접촉을 해버렸다는 것
의존성을 주입한 클래스
// 고객에게 송장 리마인더를 보내는 클래스
class InvoiceReminder {
private final AddressBook addressBook;
private final EmailSender emailSender;
// 여기를 보자 !!!!!!!!!!!!!!!!!
// 생성자를 통해 주입되는 의존성
InvoiceReminder(AddressBook addressBook, EmailSender emailSender) {
this.addressBook = addressBook;
this.emailSender = emailSender;
}
// 정적 팩토리 함수
static InvoiceReminder create() {
return new InvoiceReminder(DataStore.getAddressBook(), new EmailSenderImpl());
}
Boolean sendReminder(Invoice invoice) {
EmailAddress ? address = addressBook.lookupEmailAddress(invoice.getCustomerId());
if(address == null) {
return false;
}
return emailSender.send(
address,
InvoiceReminder.generate(invoice));
}
}
- 생성자를 통해 의존성을 주입할 수 있도록 변경
- 정적 팩토리 함수도 포함되어 걱정 없이 생성할 수 있다.
테스트 코드
// 테스트 코드
...
FakeAddressBook fakeAddressBook = new FakeAddressBook(); // 페이크 주소책 객체
fakeAddressBook.addEntry(
customerId: 123456,
emailAddress: "test@example.com");
FakeEmailSender emailSender = new FakeEmailSender();
InvoiceReminder invoiceReminder = new InvoiceReminder(fakeAddressBook, emailSender);
...
- 페이크에 대해 간단하게 설명하자면, 실제 객체를 구현한 테스트용 가짜 객체이다.
- 실제 외부 세계와 통신하는 게 아니라 테스트 데이터와 내부에서 교류하는 데에 그친다.
+
6/27
최근에 OOP구조로 작성한 클래스로 Mock 테스트를 해보니까 의존성의 중요성이 더 와닿는다.
만약 A클래스를 테스트할 때, A클래스가 B, C에 의존하고 있을 경우
1. A 클래스 내부에서 폐쇄적으로 생성 -> 가짜 객체를 어떻게 사용할 것인가? B, C가 외부 API라면 실제로 호출할 것인가?
2. 외부에서 A 클래스에 의존성 주입을 해줄 때 -> 외부에서 가짜 객체를 만들고, 테스트용 외부 API를 사용하면 되지 않을까?
이래서 테스트 주도 개발이 나온 거구나 생각도 들고.
'Study > Test' 카테고리의 다른 글
JUnit은 JVM에서 돌아가는가? (0) | 2022.05.28 |
---|