본문 바로가기

Back-End/토비의 스프링3

5-4장. 메일 서비스 추상화

메일 서비스 추상화

고객으로부터 사용자 레벨 관리에 관한 새로운 요청사항이 들어온 경우라면?
  1. User에 email 추가
  2. upgradeLevel()에 메일 발송 기능 추가

JavaMail이 포함된 코드의 테스트

테스트 코드를 돌리면 SMTP host에 연결할 수 없다고 에러가 날 것이다.

그렇다면 아래 처럼 테스트 서버를 구축하는건 어떨까?

UserService ---> JavaMail ---> 메일 서버
                                           ---> 테스트용 메일 서버


매번 메일이 발송되는건 바람직하지 못하다. 메일 발송은 부하가 큰 작업일 뿐 아니라, JavaMail은 검증된 라이브러리이다. 따라서 JavaMail API를 통해 요청이 들어간다는 보장만 있다면 굳이 테스트할 때마다 JavaMail을 구동시킬 필요가 없다.

UserService ---> JavaMail ---> 메일 서버
                   ---> 테스트용 JavaMail


테스트에서 불필요한 메일 전송 요청이 사라지기 때문에 테스트도 매우 빠르고 안전하게 수행될 수 있다.

테스트를 위한 메일 서비스 추상화


public class UserService {

private MailSender mailSender;

public void setMailSender(MailSender mailSender){
this.mailSender=mailSender;
}

public class DummyMailSender implements MailSender {

public void send(SimpleMailMessage mailMessage) throws MailException {

}

public void send(SimpleMailMessage[] mailMessage) throws MailException {

}
}
public class UserServiceTest {

@Autowired
MailSender mailSender;

테스트와 서비스 추상화

스프링이 제공하는 MailSender 인터페이스를 핵심으로 하는 메일 전송 서비스 추상화의 구조이다. 일반적으로 서비스 추상화라고 하면 기능은 유사하나 사용 방법이 달라 일관성 있는 접근 방법을 제공해주는 것을 말한다.

반면에 JavaMail의 경우처럼 테스트를 어렵게 만드는 건전하지 않은 방식으로 설계된 API를 사용할 때도 유용하게 쓰일 수 있다.
즉, 특별히 외부의 리소스와 연동하는 대부분 작업은 추상화의 대상이 될 수 있다.

UserService  ------>  <interface> MainSender <----- DummyMailService
                                                                           <----- JavaMailServiceImpl                <---- JavaMail
(애플리케이션 계층)                       ( 추상화 계층 )                                                                     ( 메일 서비스 계층 )


테스트 대역

MailSender 인터페이스 덕분에 테스트 환경에서는 아무것도 안하는 DummyMailSender를 DI하여 메일 테스트는 제외시킬 수 있다.

서비스 추상화는 실전에서 사용할 오브젝트를 교체하지 않더라도, 단지 테스트만을 위해서도 DI는 유용하다.

목 오브젝트를 이용한 테스트

upgradeLevels()에서 메일 전송이 요청이 되었는지에 대한 검증인 필요하다. 예를 들어, 코드가 잘못되어 이메일 전송 자체가 호출되지 않을 수도 있기 때문이다.

public class MockMailSender implements MailSender {

// 전송 요청을 받은 메일 주소를 저장해 두고
// 이를 읽을 수 있게 한다.
private List<String> requests = new ArrayList<String>();

public List<String> getRequests() {
return requests;
}

// 전송 요청을 받은 이메일 주소를 저장해 둔다.
public void send(SimpleMailMessage mailMessage) throws MailException {
requests.add(mailMessage.getTo()[0]);
}

public void send(SimpleMailMessage[] mailMessage) throws MailException {

}
}