Closed Jonghai closed 1 year ago
//파일 경로가 하드코딩되어 있는 테스트 대상
Path path = Paths.get("D:\\data\\pay\\cp0001.csv");
//의존 대상을 직접 생성
private PayInfoDao payInfoDao = new PayInfoDao();
boolean authorized = AuthUtil.authorized(authKey);
if (authorized){
resp = AuthUtil.authenticate(id,pw);
}else {
resp = -1;
}
예) 사용자의 포인트를 계산하는 로직
public class UserPointCalculator {
private PointRule pointRule = new PointRule();
private SubscriptionDao subscriptionDao;;
private ProductDao productDao;
public UserPointCalculator(SubscriptionDao subscriptionDao, ProductDao productDao){
this.subscriptionDao = subscriptionDao;
this.productDao = productDao;
}
public int calculatePoint(User u){
Subscription s = subscriptionDao.selectByUser(u.getId());
if (s == null) throw new NoSuchFieldException();
Product p = productDao.selectById(s.getProductId());
LocalDate now = LocalDate.now();
int point = 0;
if (s.isFinished(now)){
point += p.getDefaultPoint();
}else {
point += p.getDefaultPoint();
}
if (s.getGrade() == GOLD){
point += 100;
}
return point;
}
}
public void setFilePath(String filePath){
this.filePath = filePath;
}
public void sync() throws IOExecption{
Path path = Paths.get(filePath);
}
세터 메서드를 추가하여 파일 경로를 변경해서 테스트 할 수 있다.
PaySync.sync("src/test/resources/c0111.csv");
생성자나 세터를 통해 의존 대상을 교체.
//생성자를 통해서 의존 대상을 주입하게 수정해서 테스트 가능하게 함
public PaySync(PayInfoDao payInfoDao){
this.payInfoDao = payInfoDao;
}
//세터를 이용해서 의존 대상을 교체할 수 있게 만든 코드
public void setPaySync(PayInfoDao payInfoDao){
this.payInfoDao = payInfoDao;
}
의존대상을 교체할 수 있도록 수정했다면 대역을 사용하여 테스트를 진행할 수 있다.
//대역 생성
private MemoryPayInfoDao = memoryPayInfoDao = new MemoryPayInfoDao();
@Test
//대역으로 교체
paySync.setPayInfoDao(memoryDao);
예) 사용자의 포인트를 계산하는 로직
계산 기능만 테스트하려면
SubscriptionDao
와 ProductDao
에 대한 대역 또는 실제 구현이 필요.포인트 계산 코드만 테스트하고 싶다면 해당 코드를 별도 기능으로 분리해서 테스트를 진행.
public class PointRule {
public int calculatePoint(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;
}
}
@Test
void 만료전_GOLD등급은_130포인트() {
PointRule rule = new PointRule();
Subscription s = new Subscription(
LocalDate.of(2019,5,5),
Grade.GOLD);
Product p = new Product();
p.setDefaultPoint(20);
int point = rule.calculate(s, LocalDate.of(2019,5,1));
assertEquals(130,point);
}
현재 일자를 구하는 기능을 분리하고 분리한 대상을 주입할 수 있게 변경
public class Times{
pulbic LocalDate toDay(){
return LocalDate.now();
}
}
public class DailyBatchLoader {
private String basePath = ".";
private Times times = new Times();
public void setTimes(Times times){
this.times = times;
}
public int load(){
LocalDate date = times.today();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
Path batchPath = Paths.get(basePath,date.format(formatter), "batch.txt");
//batchPath에서 데이터를 읽어와 저장하는 코드
return result;
}
}
시간을 구하는 기능을 별도로 분리하면 테스트를 하기 수월해진다.
테스트 코드는 Times 대역을 이용
class DailyBatchLoaderTest {
private Times mockTimes = Mockito.mock(Times.class);
private final DailyBatchLoader loader = new DailyBatchLoader();
@BeforeEach
void setUp(){
loader.setBasePath("src/test/resources");
loader.setTimes(mockTimes);
}
@Test
void loadCount(){
given(mockTimes.today()).willReturn(LocalDate.of(2019,1,1));
int ret = loader.load();
assertEquals(3,ret);
}
}
### 외부 라이브러리는 직접 사용하지 말고 감싸서 사용하기
- 외부 라이브러리가 정적 메서드를 제공한다면 대체할 수 없다.
*예) AuthUtil 클래스*
- `AuthUtil.authorize()`와 `AuthUtil.authenticate()` 메서드는 정적 메서드이기 때문에 대역으로 대체가 어려움.
대역으로 대체하기 어려운 외부 라이브러리가 있다면
- 외부 라이브러리와 연동하기 위한 타입으로 따로 만든다.
- 테스트 대상은 분리한 타입을 사용하게 바꾼다.
테스트 대상 코드는 새로 분리한 타입을 사용함으로써 외부 연동이 필요한 기능을 대역으로 대체할 수 있다.
*외부 라이브러리를 감싼 클래스*
```java
public class AuthService {
private String authKey = "somekey";
public int authenticate(String id, String pw){
boolean authorized = AuthUtil,authorize(authKey);
if (authorized){
return AuthUtil.authenticate(id,pw);
}else {
return -1;
}
}
}
대역 사용이 어려운 외부 라이브러리를 직접 사용하지 않게 변경
public class LoginService {
private AuthService authService = new AuthService();
private CustomerRepository customerRepo;
public LoginService(CustomerRepository customerRepo){
this.customerRepo = customerRepo;
}
public void setAuthService(AuthService authService){
this.authService = authService;
}
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);
}
}
}
AuthService를 대역으로 대체하여 인증 성공 상황과 실패 상황에 대해 LoginService가 올바르게 동작하는지 검증하는 테스트 코드를 만들 수 있다.
[5주차] 테스트 가능한 설계
Chapter 8. 테스트 가능한 설계
목표