Effective Unit Testing (케일)
- remarkjs로 작성후 브라우저로 pdf 인쇄
기본적으로 Effective Unit Testing을 바탕으로 내용을 만들었고, 좀더 공유하고 싶은 내용에 살을 붙였음.
예제 코드는 책에 없는건 직접 만들어 봤으나 의미가 잘 전달되지 않을수는 있다고 생각함.
깨끗한 테스트 원칙은 '클린코드' 책에 나오는 내용임.
TDD 테스트 주도 개발이며, 하나의 개발 방법론 입니다.
- TDD는 반복 테스트을 이용한 소프트웨어 개발법이다. 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 소프트웨어를 구현한다.
- TDD의 목표는 작동하는 깔끔한 코드 “Clean code that works”
- TDD는 아래 단계의 반복으로 진행된다.
빨강 : 실패하는 작은 테스트 케이스를 작성한다. 처음에는 컴파일조차 안될 수 있다.
초록 : 테스트를 통과하는 코드를 작성한다.
리펙터링 : 테스트를 통과하기 위해 만든 코드의 모든 중복을 제거하고, 불명확한 것을 명확히 한다.
이러한 단계로 인해 TDD는 “업무 코드 작성 전에 테스트 코드를 먼저 만드는 것”으로 정의되기도 한다
프로젝트가 진행될 수록 특정 테스트 만을 위한 설정 파일 수가 엄청 증가합니다. 그런데 설정파일은 변경될 수 밖에 없고, 카피해 두었던 테스트를 위한 설정파일들은 그 표준과 달라서 기존 테스트들을 깨지게 합니다.
설정 오버라이딩 이라는 개념을 도입하여 깔끔히 처리하는 방법을 소개합니다.
Effective Unit Testing (케일)
- remarkjs로 작성후 브라우저로 pdf 인쇄
기본적으로 Effective Unit Testing을 바탕으로 내용을 만들었고, 좀더 공유하고 싶은 내용에 살을 붙였음.
예제 코드는 책에 없는건 직접 만들어 봤으나 의미가 잘 전달되지 않을수는 있다고 생각함.
깨끗한 테스트 원칙은 '클린코드' 책에 나오는 내용임.
TDD 테스트 주도 개발이며, 하나의 개발 방법론 입니다.
- TDD는 반복 테스트을 이용한 소프트웨어 개발법이다. 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 소프트웨어를 구현한다.
- TDD의 목표는 작동하는 깔끔한 코드 “Clean code that works”
- TDD는 아래 단계의 반복으로 진행된다.
빨강 : 실패하는 작은 테스트 케이스를 작성한다. 처음에는 컴파일조차 안될 수 있다.
초록 : 테스트를 통과하는 코드를 작성한다.
리펙터링 : 테스트를 통과하기 위해 만든 코드의 모든 중복을 제거하고, 불명확한 것을 명확히 한다.
이러한 단계로 인해 TDD는 “업무 코드 작성 전에 테스트 코드를 먼저 만드는 것”으로 정의되기도 한다
프로젝트가 진행될 수록 특정 테스트 만을 위한 설정 파일 수가 엄청 증가합니다. 그런데 설정파일은 변경될 수 밖에 없고, 카피해 두었던 테스트를 위한 설정파일들은 그 표준과 달라서 기존 테스트들을 깨지게 합니다.
설정 오버라이딩 이라는 개념을 도입하여 깔끔히 처리하는 방법을 소개합니다.
Legacy code refactoring video rental systemJaehoon Oh
Legacy Code Refactoring
- 마틴 파울러의 Refactoring 책 1장 예제를 워크샾 형태로 구성했다.
- 레거시 코드인 비디오 렌탈 시스템을 리팩토링 하는 방법을 단계적으로 살펴본다.
- 마이클 페더스의 Characterization Test 방법을 이용해서 Legacy Code 의 테스트를 작성한다.
- 새로운 기능을 추가할 때는 Sprouting Pattern(마이클 페더스가 만든 용어)를 이용해서 기능을 추가한다.
- 코드 스멜을 찾고 코드 스멜을 제거하면서 코드의 설계를 개선한다.
탑크리에듀(www.topcredu.co.kr), 송석원 교수의 IT기술칼럼#2
자바스크립트는 ES6버전에서 class 키워드를 도입했습니다. 자바스크립트는 객체지향 언어였지만 다른 객체지향언어처럼 사용하기에는 많은 불편함이 있었습니다. 심지어 자바스크립트는 객체지향언어라고 볼 수 없다고 하는 분들도 있었을 정도였죠. 최근에 선풍적인 인기를 얻고 있는 React, Angular 같은 기술은 클래스 문법을 채택했습니다. 따라서, 클래스 문법에 대한 이해가 깊다면 새로운 기술을 습득할 때 많은 도움이 될 것 입니다.
다른 객체지향 언어와 자바스크립트의 차이를 이해하지 못하고 사용하게 되면 많은 문제점이 발생합니다. 안타깝지만 자바스크립트를 사용하고 있는 개발자들 중에서도 제대로 이해하고 사용하시는분은 많지 않은 듯 합니다.
이를 해소하기 위해서 새로 도입한 문법이 class 키워드로 대표되는 클래스 문법입니다. 객체지향 개발방법의 익숙한 개발자들을 위해서 자바스크립트가 새로운 문법을 도입한 것 입니다. 개인적으로 “JavaScript: The Good Parts”의 저자이신 Douglas Crockford 님께서 이를 어떻게 생각하는지 궁금합니다. 자바스크립트를 자바처럼 사용하는 것에 우려를 표하셨었는데, 그 사이 많은 시간이 지났으므로 입장이 바뀌었을지 궁금하군요. 선도적인 개발자들은 ES6에서 도입한 클래스 문법이 좋은 것인가 나쁜 것인가를 갖고 토론을 하기도 합니다.
2. 테스트 더블
“테스트 더블”은 무엇인가?
테스트하려는 코드를 주변에서 분리하게 도와 주는
놈!
또는
테스트 작성 시 테스트 대상 코드와 상호작용하는
객체
3. 테스트 대상 코드와 협력 객체를 분리
출처 : Effective Unit Testing
4. 테스트 더블의 위력!
● 테스트 대상 코드를 격리한다.
● 테스트 속도를 개선한다.
● 예측 불가능한 실행 요소를 제거한다.
● 특수한 상황을 시뮬레이션한다.
● 감춰진 정보를 얻어낸다.
5. 예) 테스트 대상코드(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(); }
}
6. 테스트 대상 코드를 격리한다.
● Car는 Engine과 Route를 직접 사용하지만, Directions는 Route를 통해 간접적으로
만 사용
● 즉, 협력 객체로 부터 Car를 격리하는 일은 Engine과 Route만 테스트 더블로 교체.
출처 : Effective Unit Testing
출처 : Effective Unit Testing
7. 테스트 속도를 개선한다.
● 만약 Car가 이동할 최단경로를 구할 때 Route가 가중 그래프 검색 알
고리즘을 이용한다면 ?
○ 알고리즘을 계산하느라, 속도가 느려짐
● 테스트 더블을 이용해서 사전에 계산해둔 경로를 반환 한다면?
○ 테스트는 눈부시게 빨리짐!
8. 예측 불가능한 실행 요소를 제거한다.
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(...);
}
}
혼잡한 시간에는 경로 계산
결과가 달라진다!
● 비결정적인 요인을 다룰땐, 테스트 더블!
○ 항상 똑같은 시간을 알려주는 테스트 더블로 변경!
9. 특수한 상황을 시뮬레이션한다.
● Route가 Directions를 구할 때 구글 맵스를 이용한다고 가정 해보자.
○ 목적지까지 경로를 요청하는 도중 인터넷이 끊긴 경우 Route는
잘 대처하는지 어떡해 확인 할 수 있을까?
연결 요청을 처리하는 부분을 테스트 더블로 대체해서 예외 발생시키자!
10. 감춰진 정보를 얻어낸다.
● 시나리오
○ 누군가 Car의 시동을 걸면 Car는 Engine을 가동한다.
○ 이 동작이 실제로 일어났는지 어떻게 확인 할 수 있을
까?
● 역시 테스트 더블이 해결책!
11. 감춰진 정보를 얻어낸다.
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;}
}
13. 테스트 스텁 - 유난히 짧다.
● 스텁의 사전적 정의
○ 스텁 (명사) 끝이 잘렸거나 유난히 짧은 것
● 테스트 스텁의 목적은 원래의 구현을 최대한 단순한 것으로 대체 하는 것
스텁의 전형적인 모습! ( 아.무.것.도.하.지.않.아.요 )
public class LoggerStub implements Logger {
public void log(LogLevel level, String message) { }
}
14. 테스트 스텁 - 유난히 짧다.
public class LoggerStub implements Logger {
public void log(LogLevel level, String message) { // 여전히 아무 일도 하지 않는다. }
public LogLevel getLogLevel() {
return LogLevel.WARN; // 하드코딩된 값을 반환한다.
}
}
LogLevel을 반환하는 메서드 정의
스텁을 사용하는 세 가지 이유
1. 테스트는 대상 코드가 로깅하는 내용에는 전혀 관심 없다.
2. 가동 중인 로그 서버가 없으니 로깅은 어차피 실패했을 거다.
3. 테스트 스위트가 콘솔로 대량의 정보를 쏟아내는 건 바라지 않는다. (파일에 쓰는 건 별로 상관없다.)
15. 가짜 객체 - 뒤끝 없이 처리한다.
● 언제 사용?
○ 최소한의 행동을 취해주거나 입력값에 따라 다르게 행동 할때 사용!
● 왜 사용?
○ 진짜 객체를 사용할때 생기는 부수효과나 연쇄동작이 일어나지 않게 경
량화 및 최적화 한 것
16. 가짜 객체 - 뒤끝 없이 처리한다.
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);
}
17. 테스트 스파이 - 기밀을 훔친다.
● 언제 사용?
○ 입력 인자로 사용되는 객체가 테스트에 필요한 정보를 알려주는 API를 제
공하지 않을 때 유용
○ 구현 어디에도 전달된 메시지가 잘 기록되었는지 알려주는 메서드가 보이
지 않을 시
주의 : 테스트 스파이를 사용해야 하는 상황이 발생하면 설계가 제대로 되었는지 한번쯤 의심을 ;ㅁ;
18. 테스트 스파이 - 기밀을 훔친다.
코드 - 테스트에 필요한 정보를 제공하지 않아 테스트 스파이가 필요한 상황
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);
}
19. 테스트 스파이 - 기밀을 훔친다.
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;
}
}
}
코드 - 간단히 구현해본 테스트 스파이
20. Mock 객체 - 예기치 않은 일을 막아준다.
● 특정 조건이 발생하면 미리 약속된 행동을 취하고, 약속된 행동이 취해지지 않으면 바
로 테스트 실패가 된다.
○ 예
■ UserRespository 으로 설명 하자면, findById()의 파라미터로 123을 주면
null, 124를 주면 가짜 user객체를 반환하는 식이다.
● 메서드를 파라미터에 따라 다르게 처리하게 한 스텁 수준 이지만, 이게
다가 아니다.
■ 하지만 123,124 외에 인자를 넘겼거나 다른 메서드를 호출하면 테스트를
실패하게 만들 수 있다.
● 이전 보다(스텁,가짜,스파이) 훨씬 정교한 테스트를 만들 수 있다.
○ 메서드가 호출되었는지? 몇번 호출되었는지 확인 할 수 있다.
21. Mock 객체 - 예기치 않은 일을 막아준다.
● Mock 객체 라이브러리
○ Mockito
○ JMock
○ EasyMock
22. Mock 객체 - 예기치 않은 일을 막아준다.
public class TestTranslator {
@Test public void userInternetForTranslation() {
//given
final Internet internet = mock(Internet.class);
given(internet.get(with(containsString(“langpair=en%7Cfi”)))
.willReturn(returnValue(“{“translatedText”:”kukka”}”));
//when
Translator t = new Translator(internet);
String translation = t.translate(“flower”, ENGLISH, FINNISH);
//then
assertEquals(“kukka”, translation);
}
}
23. 테스트 더블 활용 지침
● 용도에 맞는 도구를 꺼내 써라
● 준비하고, 시작하고, 단언하라
● 구현이 아니라 동작을 확인하라
● 자신의 도구를 선택해라
● 종속 객체를 주입하라
24. 용도에 맞는 더블을 선택해라.
● “가독성” - 테스트를 가장 읽기 쉽게 만들어주는 선택
● 그 외
○ 두 객체 간 상호작용의 결과로 특정메서드가 호출되었는지 확인하고 싶다면 Mock 객체
○ …...
○ 이도 저도 아니라면 동전을 던져보자, 앞면 Mock, 뒷면 스텁
26. 구현이 아니라 동작을 확인하라.
● 검증 목적과 관련 없는 지극히 사소한 변경마저도 테스트 실패 한다면..
○ 못질을 너무 많이 해서 구멍이 송송 뚫린 불쌍한 목판처럼 되어 버린다.
● 테스트는 오직 한 가지만 검사해야 하고 그 의도를 명확히 전달하도록 작성되어야 한
다.
● 핵심
○ “구현이 아니라 동작을 검증 하자!”
27. 종속 객체를 주입하라
● 진짜 객체를 테스트 더블 객체로 교체할 수 있어야 한다. 교체할 수
없다면 단위 테스트 작성도 못한다.
○ 종속 객체를 private 필드에 저장하거나 팩토리 메서드 등을 통
해 외부로부터 얻도록 해야 함
○ 보통 생성자 주입 방식을 많이 애용 함