effective unit testing ch3. 테스트더블

28
Effective Unit Testing 테스트 더블 최용은

Upload: yongeun-choi

Post on 31-May-2015

386 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Effective unit testing   ch3. 테스트더블

Effective Unit Testing테스트 더블

최용은

Page 2: Effective unit testing   ch3. 테스트더블

테스트 더블

“테스트 더블”은 무엇인가?

테스트하려는 코드를 주변에서 분리하게 도와 주는 놈!또는

테스트 작성 시 테스트 대상 코드와 상호작용하는 객체

Page 3: Effective unit testing   ch3. 테스트더블

테스트 대상 코드와 협력 객체를 분리

출처 : Effective Unit Testing

Page 4: Effective unit testing   ch3. 테스트더블

테스트 더블의 위력!

● 테스트 대상 코드를 격리한다.

● 테스트 속도를 개선한다.

● 예측 불가능한 실행 요소를 제거한다.

● 특수한 상황을 시뮬레이션한다.

● 감춰진 정보를 얻어낸다.

Page 5: Effective unit testing   ch3. 테스트더블

예) 테스트 대상코드(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(); }

}

Page 6: Effective unit testing   ch3. 테스트더블

테스트 대상 코드를 격리한다.

● Car는 Engine과 Route를 직접 사용하지만 , Directions는 Route를 통해 간접적으로

만 사용

● 즉, 협력 객체로 부터 Car를 격리하는 일은 Engine과 Route만 테스트 더블로 교체.출처 : Effective Unit Testing

출처 : Effective Unit Testing

Page 7: Effective unit testing   ch3. 테스트더블

테스트 속도를 개선한다.

● 만약 Car가 이동할 최단경로를 구할 때 Route가 가중 그래프 검색 알

고리즘을 이용한다면 ?

○ 알고리즘을 계산하느라, 속도가 느려짐

● 테스트 더블을 이용해서 사전에 계산해둔 경로를 반환 한다면?

○ 테스트는 눈부시게 빨리짐!

Page 8: Effective unit testing   ch3. 테스트더블

예측 불가능한 실행 요소를 제거한다.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(...);

}}

혼잡한 시간에는 경로 계산 결과가 달라진다!

● 비결정적인 요인을 다룰땐, 테스트 더블!○ 항상 똑같은 시간을 알려주는 테스트 더블로 변경!

Page 9: Effective unit testing   ch3. 테스트더블

특수한 상황을 시뮬레이션한다.● Route가 Directions를 구할 때 구글 맵스를 이용한다고 가정 해보자.

○ 목적지까지 경로를 요청하는 도중 인터넷이 끊긴 경우 Route는

잘 대처하는지 어떡해 확인 할 수 있을까?

연결 요청을 처리하는 부분을 테스트 더블로 대체해서 예외 발생시키자!

Page 10: Effective unit testing   ch3. 테스트더블

감춰진 정보를 얻어낸다.● 시나리오

○ 누군가 Car의 시동을 걸면 Car는 Engine을 가동한다.

○ 이 동작이 실제로 일어났는지 어떻게 확인 할 수 있을

까?

● 역시 테스트 더블이 해결책!

Page 11: Effective unit testing   ch3. 테스트더블

감춰진 정보를 얻어낸다.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;}

}

Page 12: Effective unit testing   ch3. 테스트더블

테스트 더블의 종류

테스트 더블

테스트 스텁 가짜 객체 테스트 스파이 Mock 객체

Page 13: Effective unit testing   ch3. 테스트더블

테스트 스텁 - 유난히 짧다.

● 스텁의 사전적 정의

○ 스텁 (명사) 끝이 잘렸거나 유난히 짧은 것

● 테스트 스텁의 목적은 원래의 구현을 최대한 단순한 것으로 대체 하는 것

스텁의 전형적인 모습! ( 아.무.것.도.하.지.않.아.요 )public class LoggerStub implements Logger {

public void log(LogLevel level, String message) { }}

Page 14: Effective unit testing   ch3. 테스트더블

테스트 스텁 - 유난히 짧다.

public class LoggerStub implements Logger {public void log(LogLevel level, String message) { // 여전히 아무 일도 하지 않는다. }public LogLevel getLogLevel() {

return LogLevel.WARN; // 하드코딩된 값을 반환한다.}

}

LogLevel을 반환하는 메서드 정의

스텁을 사용하는 세 가지 이유

1. 테스트는 대상 코드가 로깅하는 내용에는 전혀 관심 없다.2. 가동 중인 로그 서버가 없으니 로깅은 어차피 실패했을 거다.3. 테스트 스위트가 콘솔로 대량의 정보를 쏟아내는 건 바라지 않는다. (파일에 쓰는 건 별로 상관없다.)

Page 15: Effective unit testing   ch3. 테스트더블

가짜 객체 - 뒤끝 없이 처리한다.

● 언제 사용?○ 최소한의 행동을 취해주거나 입력값에 따라 다르게 행동 할때 사용!

● 왜 사용?○ 진짜 객체를 사용할때 생기는 부수효과나 연쇄동작이 일어나지 않게 경량화 및 최적화 한 것

Page 16: Effective unit testing   ch3. 테스트더블

가짜 객체 - 뒤끝 없이 처리한다.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);

}

Page 17: Effective unit testing   ch3. 테스트더블

테스트 스파이 - 기밀을 훔친다.

● 언제 사용?

○ 입력 인자로 사용되는 객체가 테스트에 필요한 정보를 알려주는 API를 제

공하지 않을 때 유용

○ 구현 어디에도 전달된 메시지가 잘 기록되었는지 알려주는 메서드가 보이

지 않을 시

주의 : 테스트 스파이를 사용해야 하는 상황이 발생하면 설계가 제대로 되었는지 한번쯤 의심을 ;ㅁ;

Page 18: Effective unit testing   ch3. 테스트더블

테스트 스파이 - 기밀을 훔친다.코드 - 테스트에 필요한 정보를 제공하지 않아 테스트 스파이가 필요한 상황

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);

}

Page 19: Effective unit testing   ch3. 테스트더블

테스트 스파이 - 기밀을 훔친다.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;}

}}

코드 - 간단히 구현해본 테스트 스파이

Page 20: Effective unit testing   ch3. 테스트더블

Mock 객체 - 예기치 않은 일을 막아준다.

● 특정 조건이 발생하면 미리 약속된 행동을 취하고, 약속된 행동이 취해지지 않으면 바로 테스트 실패가 된다. ○ 예

■ UserRespository 으로 설명 하자면, findById()의 파라미터로 123을 주면 null, 124를 주면 가짜 user객체를 반환하는 식이다.● 메서드를 파라미터에 따라 다르게 처리하게 한 스텁 수준 이지만, 이게 다가 아니다.

■ 하지만 123,124 외에 인자를 넘겼거나 다른 메서드를 호출하면 테스트를 실패하게 만들 수 있다.

● 이전 보다(스텁,가짜,스파이) 훨씬 정교한 테스트를 만들 수 있다.○ 메서드가 호출되었는지? 몇번 호출되었는지 확인 할 수 있다.

Page 21: Effective unit testing   ch3. 테스트더블

Mock 객체 - 예기치 않은 일을 막아준다.

● Mock 객체 라이브러리○ Mockito

○ JMock

○ EasyMock

Page 22: Effective unit testing   ch3. 테스트더블

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);

}}

Page 23: Effective unit testing   ch3. 테스트더블

테스트 더블 활용 지침

● 용도에 맞는 도구를 꺼내 써라

● 준비하고, 시작하고, 단언하라

● 구현이 아니라 동작을 확인하라

● 자신의 도구를 선택해라

● 종속 객체를 주입하라

Page 24: Effective unit testing   ch3. 테스트더블

용도에 맞는 더블을 선택해라.● “가독성” - 테스트를 가장 읽기 쉽게 만들어주는 선택

● 그 외○ 두 객체 간 상호작용의 결과로 특정메서드가 호출되었는지 확인하고 싶다면 Mock 객체 ○ …...○ 이도 저도 아니라면 동전을 던져보자, 앞면 Mock, 뒷면 스텁

Page 25: Effective unit testing   ch3. 테스트더블

용도에 맞는 더블을 선택해라.● 결론

○ 스텁은 질문하고 Mock은 행동한다.

Page 26: Effective unit testing   ch3. 테스트더블

구현이 아니라 동작을 확인하라.● 검증 목적과 관련 없는 지극히 사소한 변경마저도 테스트 실패 한다면..

○ 못질을 너무 많이 해서 구멍이 송송 뚫린 불쌍한 목판처럼 되어 버린다.

● 테스트는 오직 한 가지만 검사해야 하고 그 의도를 명확히 전달하도록 작성되어야 한

다.

● 핵심

○ “구현이 아니라 동작을 검증 하자!”

Page 27: Effective unit testing   ch3. 테스트더블

종속 객체를 주입하라● 진짜 객체를 테스트 더블 객체로 교체할 수 있어야 한다. 교체할 수

없다면 단위 테스트 작성도 못한다.

○ 종속 객체를 private 필드에 저장하거나 팩토리 메서드 등을 통

해 외부로부터 얻도록 해야 함

○ 보통 생성자 주입 방식을 많이 애용 함

Page 28: Effective unit testing   ch3. 테스트더블

Thank U