Effective unit testing ch3. 테스트더블
-
Upload
yongeun-choi -
Category
Documents
-
view
386 -
download
0
Transcript of Effective unit testing ch3. 테스트더블
Effective Unit Testing테스트 더블
최용은
테스트 더블
“테스트 더블”은 무엇인가?
테스트하려는 코드를 주변에서 분리하게 도와 주는 놈!또는
테스트 작성 시 테스트 대상 코드와 상호작용하는 객체
테스트 대상 코드와 협력 객체를 분리
출처 : Effective Unit Testing
테스트 더블의 위력!
● 테스트 대상 코드를 격리한다.
● 테스트 속도를 개선한다.
● 예측 불가능한 실행 요소를 제거한다.
● 특수한 상황을 시뮬레이션한다.
● 감춰진 정보를 얻어낸다.
예) 테스트 대상코드(Car)와 협력 객체(Engine과 Rout)
public class Car {private Engine engine;public Car(Engine engine) { this.engine = engine;}public void start() { engine.start(); }public void drive(Route route) {
for(Directions directions : route.directions()){directions.follow();
}}public void stop(){ engine.stop(); }
}
테스트 대상 코드를 격리한다.
● Car는 Engine과 Route를 직접 사용하지만 , Directions는 Route를 통해 간접적으로
만 사용
● 즉, 협력 객체로 부터 Car를 격리하는 일은 Engine과 Route만 테스트 더블로 교체.출처 : Effective Unit Testing
출처 : Effective Unit Testing
테스트 속도를 개선한다.
● 만약 Car가 이동할 최단경로를 구할 때 Route가 가중 그래프 검색 알
고리즘을 이용한다면 ?
○ 알고리즘을 계산하느라, 속도가 느려짐
● 테스트 더블을 이용해서 사전에 계산해둔 경로를 반환 한다면?
○ 테스트는 눈부시게 빨리짐!
예측 불가능한 실행 요소를 제거한다.public class Route {
private Clock clock = new Clock();private ShortestPath algorithm = new …();public Collection<Directions> directions() {
if(clock.isRushHour()) {return algorithm.avoidBusyIntersections();
}return algorithm.calculateRouteBetween(...);
}}
혼잡한 시간에는 경로 계산 결과가 달라진다!
● 비결정적인 요인을 다룰땐, 테스트 더블!○ 항상 똑같은 시간을 알려주는 테스트 더블로 변경!
특수한 상황을 시뮬레이션한다.● Route가 Directions를 구할 때 구글 맵스를 이용한다고 가정 해보자.
○ 목적지까지 경로를 요청하는 도중 인터넷이 끊긴 경우 Route는
잘 대처하는지 어떡해 확인 할 수 있을까?
연결 요청을 처리하는 부분을 테스트 더블로 대체해서 예외 발생시키자!
감춰진 정보를 얻어낸다.● 시나리오
○ 누군가 Car의 시동을 걸면 Car는 Engine을 가동한다.
○ 이 동작이 실제로 일어났는지 어떻게 확인 할 수 있을
까?
● 역시 테스트 더블이 해결책!
감춰진 정보를 얻어낸다.public class CarTest {
@Test public void engineIsStartedWhenCarStarts() {TestEngine engine = new TestEngine();new Car(engine).start();assertTrue(engine.isRunning());
}}public class TestEngine extends Engine {
private boolean isRunning;public void start(){isRunning = true;}public boolean isRunning(){return isRunning;}
}
테스트 더블의 종류
테스트 더블
테스트 스텁 가짜 객체 테스트 스파이 Mock 객체
테스트 스텁 - 유난히 짧다.
● 스텁의 사전적 정의
○ 스텁 (명사) 끝이 잘렸거나 유난히 짧은 것
● 테스트 스텁의 목적은 원래의 구현을 최대한 단순한 것으로 대체 하는 것
스텁의 전형적인 모습! ( 아.무.것.도.하.지.않.아.요 )public class LoggerStub implements Logger {
public void log(LogLevel level, String message) { }}
테스트 스텁 - 유난히 짧다.
public class LoggerStub implements Logger {public void log(LogLevel level, String message) { // 여전히 아무 일도 하지 않는다. }public LogLevel getLogLevel() {
return LogLevel.WARN; // 하드코딩된 값을 반환한다.}
}
LogLevel을 반환하는 메서드 정의
스텁을 사용하는 세 가지 이유
1. 테스트는 대상 코드가 로깅하는 내용에는 전혀 관심 없다.2. 가동 중인 로그 서버가 없으니 로깅은 어차피 실패했을 거다.3. 테스트 스위트가 콘솔로 대량의 정보를 쏟아내는 건 바라지 않는다. (파일에 쓰는 건 별로 상관없다.)
가짜 객체 - 뒤끝 없이 처리한다.
● 언제 사용?○ 최소한의 행동을 취해주거나 입력값에 따라 다르게 행동 할때 사용!
● 왜 사용?○ 진짜 객체를 사용할때 생기는 부수효과나 연쇄동작이 일어나지 않게 경량화 및 최적화 한 것
가짜 객체 - 뒤끝 없이 처리한다.public class FakeUserRepository implements UserRepository {
private Collection<User> users = …. // 인메모리 데이터베이스public void save(User user){
if( findById(user.getId()==null ) { users.add(user); }}public User findById(long id) {
for( User user : users ) {if( user.getId() == id ) return user;
}return null;
}}
public interface UserRepository {void save(User user);User findById(long id);
}
테스트 스파이 - 기밀을 훔친다.
● 언제 사용?
○ 입력 인자로 사용되는 객체가 테스트에 필요한 정보를 알려주는 API를 제
공하지 않을 때 유용
○ 구현 어디에도 전달된 메시지가 잘 기록되었는지 알려주는 메서드가 보이
지 않을 시
주의 : 테스트 스파이를 사용해야 하는 상황이 발생하면 설계가 제대로 되었는지 한번쯤 의심을 ;ㅁ;
테스트 스파이 - 기밀을 훔친다.코드 - 테스트에 필요한 정보를 제공하지 않아 테스트 스파이가 필요한 상황
public class DLog{private final DLogTarget[] targets;public DLog(DLogTarget… targets) {
this.targets = targets;}public void write(Level level, String message) {
for (DlogTarget each : targets) { each.write(level, message);
}}
}
public interface DLogTarget {void write(Level level, String message);
}
테스트 스파이 - 기밀을 훔친다.public class DLogTest{
@Test public void writeEachMessage…() {SpyTarget spy = new SpyTarget();DLog log = new DLog(spy);log.write(Level.INFO, “message”);assertTrue(spy.received(Level.INFO, “message”);
}private class SpyTarget implements DLogTarget {
private List<String> log = ….;public void write(Level level, String message) {
log.add(concatenated(level, message));}boolean received(Level level, String message) {
return log.contains(concatenated(level, message));}private String concatenated(Level level, String message){
return level.getName() + “: “ + message;}
}}
코드 - 간단히 구현해본 테스트 스파이
Mock 객체 - 예기치 않은 일을 막아준다.
● 특정 조건이 발생하면 미리 약속된 행동을 취하고, 약속된 행동이 취해지지 않으면 바로 테스트 실패가 된다. ○ 예
■ UserRespository 으로 설명 하자면, findById()의 파라미터로 123을 주면 null, 124를 주면 가짜 user객체를 반환하는 식이다.● 메서드를 파라미터에 따라 다르게 처리하게 한 스텁 수준 이지만, 이게 다가 아니다.
■ 하지만 123,124 외에 인자를 넘겼거나 다른 메서드를 호출하면 테스트를 실패하게 만들 수 있다.
● 이전 보다(스텁,가짜,스파이) 훨씬 정교한 테스트를 만들 수 있다.○ 메서드가 호출되었는지? 몇번 호출되었는지 확인 할 수 있다.
Mock 객체 - 예기치 않은 일을 막아준다.
● Mock 객체 라이브러리○ Mockito
○ JMock
○ EasyMock
Mock 객체 - 예기치 않은 일을 막아준다.
public class TestTranslator {@Test public void userInternetForTranslation() {
//givenfinal Internet internet = mock(Internet.class);given(internet.get(with(containsString(“langpair=en%7Cfi”))).willReturn(returnValue(“{\“translatedText\”:\”kukka\”}”));//whenTranslator t = new Translator(internet);String translation = t.translate(“flower”, ENGLISH, FINNISH);//thenassertEquals(“kukka”, translation);
}}
테스트 더블 활용 지침
● 용도에 맞는 도구를 꺼내 써라
● 준비하고, 시작하고, 단언하라
● 구현이 아니라 동작을 확인하라
● 자신의 도구를 선택해라
● 종속 객체를 주입하라
용도에 맞는 더블을 선택해라.● “가독성” - 테스트를 가장 읽기 쉽게 만들어주는 선택
● 그 외○ 두 객체 간 상호작용의 결과로 특정메서드가 호출되었는지 확인하고 싶다면 Mock 객체 ○ …...○ 이도 저도 아니라면 동전을 던져보자, 앞면 Mock, 뒷면 스텁
용도에 맞는 더블을 선택해라.● 결론
○ 스텁은 질문하고 Mock은 행동한다.
구현이 아니라 동작을 확인하라.● 검증 목적과 관련 없는 지극히 사소한 변경마저도 테스트 실패 한다면..
○ 못질을 너무 많이 해서 구멍이 송송 뚫린 불쌍한 목판처럼 되어 버린다.
● 테스트는 오직 한 가지만 검사해야 하고 그 의도를 명확히 전달하도록 작성되어야 한
다.
● 핵심
○ “구현이 아니라 동작을 검증 하자!”
종속 객체를 주입하라● 진짜 객체를 테스트 더블 객체로 교체할 수 있어야 한다. 교체할 수
없다면 단위 테스트 작성도 못한다.
○ 종속 객체를 private 필드에 저장하거나 팩토리 메서드 등을 통
해 외부로부터 얻도록 해야 함
○ 보통 생성자 주입 방식을 많이 애용 함
Thank U