테스트 어려운 코드
모든 코드를 테스트할 수 있는 것은 아니다. 개발을 진행하다 보면 테스트하기 어려운 코드를 만나게 된다. 이를 살펴보고 이를 어떻게 테스트 가능한 코드로 변경하는 지 파악한다.
하드 코딩된 경로
Path path = Paths.get("D:\\\\data\\\\pay\\\\cp0001.csv)
하드 코딩된 경로뿐만 아니라 하드코딩된 IP 주소, 포트 번호도 테스트를 어렵게 만든다.
의존 객체를 직접 생성
private PayInfoDao payInfoDao = new PayInfoDao();
테스트를 어렵게 만드는 또 다른요인은 의존 대상을 직접 생성하고 있다는 점이다. 해당 코드를 테스트 하려면 PatInfoDao가 올바르게 동작하는데 필요한 모든 환경을 구성해야한다. DB를 준비해야하고 필요한 테이블도 만들어야 한다.
정적 메서드 사용
public LoginResult login(String id, String pw) {
int resp = authService.authenticate(id, pw);
if (resp == -1) return LoginResult.badAuthKey();
if (resp == 1) {
Customer c = customerRepo.findOne(id);
return LoginResult.authenticated(c);
} else {
return LoginResult.fail(resp);
}
}
해당 코드는 AuthUtil 클래스의 정적 메서드를 사용하고 있다. AuthUtil 클래스가 인증 서버와 통신하는 경우 이 테스트하려면 동작하고 있는 인증 서버가 필요하다.
실행 시점에 따라 달라지는 결과
LocalDate now = LocalDate.now();
같은 테스트 코드라도 어제가지는 문제 없이 성공하던 코드가 오늘 실패할 수 있다.
역할이 섞여 있는 코드
포인트 계산 로직만 테스트하기 어렵다. 포인트 계산 자체는 이 두 DAO와 상관이 없다. 포인트 계산에 필요한 것은 두개의 DAO가 필요하기 때문이다.
그 외의 테스트가 어려운 코드
- 메서드 중간에 소켓 통신 코드가 포함한다.
- 콘솔에서 입력을 받거나 결과를 콘솔에 출력한다.
- 테스트 대상이 사용하는 의존 대상 클래스가 메서드가 final이다. 이 경우 대역으로 대체가 어려울 수 있다.
- 테스트 대상의 소스를 소유하고 있지 않아 수정이 어렵다.
테스트 가능한 설계
하드 코딩된 상수를 생성자나 메서드 파라미터로 받기
public void setFilePath(String filePath) {
this.filePath = filePath;
}
의존 대상을 주입 받기
public void setPayInfoDao(PayInfoDao payInfoDao) {
this.payInfoDao = payInfoDao;
}
테스트하고 싶은 코드를 분리하기
public class PointRule {
public int calculate(Subscription s, Product p, LocalDate now) {
int point = 0;
if (s.isFinished(now)) {
point += p.getDefaultPoint();
} else {
point += p.getDefaultPoint() + 10;
}
if (s.getGrade() == GOLD) {
point += 100;
}
return point;
}
}
public int calculatePoint(User u) {
Subscription s = subscriptionDao.selectByUser(u.getId());
if (s == null) throw new NoSubscriptionException();
Product p = productDao.selectById(s.getProductId());
LocalDate now = times.today();
return pointRule.calculate(s, p, now);
}
클래스로 분리하여테스가 가능하도록 한다.
시간이나 임의 값 생성 기능 분리하기
현재 일자를 구하는 기능을 분리하고 분리한 대상을 주입할 수 있게 변경하면 테스트를 원하는 상황으로 쉽게 제어할 수 있다.
외부 라이버리는 직접 사용하지 말고 감싸서 사용하기
대역으로 대체하기 어려운 외부 라이브러리가 있다면 외부 라이브러리를 직접 사용하지 말고 외부 라이브러리와 연동하기 위한 타입을 따로 만든다. 테스트 대상 코드는 새로 분리한 타입을 사용함으로써 외부 연동으로 필요한 기능을 쉽게 대역으로 대체할 수 있게 된다.
Reference.