combacsa-20100610-1
Download
Report
Transcript combacsa-20100610-1
Combacsa’s SPARCS Web Seminar
SOFTWARE DEVELOPMENT #2:
TEST-DRIVEN DEVELOPMENT
Test-Driven Development
Test-Driven Development
Agile Family (of course!)
Test Code 작성으로부터
프로그램 개발이 진행되게 하는 방법론.
Two major philosophy
코딩을 시작하기에 앞서,
실패하는, 자동화된 테스트 코드부터 작성하라.
중복을 제거하라.
Test Code 의 역할?
Test Code 는 보조 수단이다
프로그램 개발에 필수적인 요소
문서
설계
개발 실력
디버깅 능력
Test Code 는 조금 더 안심할 수 있도록 할 뿐
프로그램 개발의 필수 요소라고 볼 수 없다
TDD 에서 Test Code 의 역할!
Test Code 는 필수적이다!
Working Software 의 정의
“올바르게” 동작하는 프로그램
“올바르다” 의 정의는 어떻게 내리는가?
그때 그때 실행해 보면서?
No, 확고하게 설계한 Test Code 를 실행해서!
Test Code 작성만으로도 대형 프로젝트가
얼마든지 진행될 수 있다!
Test 는 프로그램이
당연히 통과해야 하는 것
Test Code 작성은 프로그램 개발의
보조수단일 뿐이다
TDD Rhythm in 5 Steps
Add a new TEST
Run all TESTs, and check there is a FAILURE
Slightly modify CODE
Run all TESTs, and check everything SUCCESS
Eliminate DUPLICATION by REFACTORING
TDD 에 따른 프로젝트의 삶
고객과 함께 TO-DO List 를 작성한다
리스트에서 할 일 하나을 고른다
TDD Rhythm 을 타면서 그 일을 처리한다
중간에 새로운 일이 생겨도 지금 처리중인 일에만
집중하고, 새로 생긴 일은 까먹지 않도록
단지 TO-DO List 에 올리기만 한다
TO-DO List 가 빌 때까지 반복한다
프로그램 개발이 끝난다
TO-DO LIST
할일 1
할일 2
할일 n …
TEST still SUCCESS
Refactored Code for 할일 i
TEST SUCCESS
할일 i
TEST Code for 할일 i
TEST Failure
Code for 할일 i
TDD Terminology
Red bar period
Period when one or more tests fail
테스트 중 하나라도 통과하지 못한다
Green bar period
Period when entire tests success
모든 테스트를 통과한다
개발 진행 = 새로운 빨간막대 파란막대
Let’s see how it really works.
제한점
어디까지나 TDD 를 “보여주기” 위한 것
TDD 를 진행하면서 일어날 수 있는 다양한 상황
들을 위한 작위적인 예제임을 잊지 말자
JUnit Framework 를 사용하여 Java 로 코딩
Java 를 모르는 사람은 따라해보긴 힘들겠다
Java 를 알면 알아서 프로젝트 생성 / 파일 추가
잘 하고 따라해보길 바람
귀찮아서 Eclipse 스크린샷은 안 뜨겠음
Kent Beck’s Example
Project Bank Account
은행의 사용자 계좌 관리 프로그램을 개발하자
고객은 다음과 같은 사항을 요구한다
서로 다른 통화간에 덧셈이 가능해야 함
계좌에 있는 돈을 특정 배로 늘릴 수 있어야 함
Blah Blah Yada Yada …
첫단계
TO-DO List 부터 작성하자.
TO-DO List 의 작성법
각각의 기능을 최대한 분해할 것
새로운 기능은 최소한의 Test 로 검증가능해야!
때때로 TO-DO List 에서 제거할 일도 있다
다른 일을 처리하면서 자연스레 해결되는 경우.
TO-DO List 작성의 실제
고객 요구 조건 (Recall)
서로 다른 통화간에 덧셈이 가능해야 함
계좌에 있는 돈을 특정 배로 늘릴 수 있어야 함
두 계좌에 있는 돈이 동일함을 확인해야 함
기능만 뽑아내어 다시 써보자!
$5 + 10YEN = $6 (환율이 1:10 일 때)
$5 x 2 = $10
$5 = $5 (서로 다른 계좌)
할일 고르기
원칙
다른 일보다 먼저 진행될 수 있는 것부터
다른 일보다 먼저 진행되어야 하는 것부터
좀더 쉽고 단순한 일부터
TODO List Recall
$5 + 10YEN = $6 (환율이 1:10 일 때)
$5 x 2 = $10 한 계좌에 대한 것. 너로 정했다
$5 = $5 (서로 다른 계좌)
Test Code 설계
Test Code 를 작성하며 결정되는 것들
함수 / 클래스의 이름과 파라메터
각 함수 / 클래스의 예상되는 동작
public void testMultiplication() {
Dollar five = new Dollar(5);
five.times(2);
assertEquals(10, five.amount);
}
Test 실행
결과 확인
테스트 실패 : 컴파일조차 되지 않는다.
Dollar 라는 클래스가 정의되지 않음
실패에 대한 대처법
원인을 파악하고 가장 간단한 것부터 해결한다
지금은 Dollar 클래스를 정의하면 충분하겠다
times 메소드와 amount 필드가 있어야겠다
어떻게든 이 테스트 코드를 통과시킬 수 없다면
다른 할일부터 해결하는 것이 나을지도 모른다
Dollar 클래스 설계
Public class Dollar {
int amount;
public Dollar(int amount) {
}
void times(int multiplier) {
}
}
Test 실행
결과 확인
assertEquals(10, five.amount);
결과가 0 이 나오고 있다
assertEquals(A, B) 메소드
A 의 값과 B 의 값이 일치할 때 통과하고,
일치하지 않을 때 실패하는 메소드이다
five 객체의 amount 변수의 값이 10이 아니어서
통과하지 못한 것이다
상념에 대한 적절한 대처
상념 1 : amount 멤버 변수는 개인 정보이니
private 로 지정되었어야 하지 않나?
대처 1 : TODO 리스트에 다음을 추가한다
Amount 를 private 로 만들기
상념 2 : 실수 연산일텐데, 반올림 처리는?
대처 2 : TODO 리스트에 다음을 추가한다
Money 반올림?
상념에 방해받지 말자. TODO List 가 기억!
Test 결과에 대한 적절한 대처
한번의 코딩으로 초록 막대를 보지 못함을 슬
퍼할 이유는 전혀 없다
결국은 초록 막대를 볼 것임을 우리는 안다
가장 단순한 해결책을 찾는다
five 객체의 amount 변수의 값이 10이 아니어서 통과
하지 못한 것이라고 했었다
결론 : 객체의 amount 변수의 값이 10이어야!
가장 간단한 코드 수정
Public class Dollar {
int amount = 10;
public Dollar(int amount) {
}
void times(int multiplier) {
}
}
Test 실행
결과
모든 테스트가 성공한다
의의
오직 테스트를 통과시키기 위한 목적의 코딩.
이런 코드는 사실 죄악이다.
그리고 그런 죄악을 저지르자. 왜?
프로그램 개발 = 테스트 코드를 만들고
그 테스트를 통과시켜 초록 막대를 보는 거니까!
그리고 죄악은 수습하면 그만이다.
죄악의 수습
죄악을 저지르는 이유
테스트 케이스에 대해 빨간 막대를 유지하면
심리적인 불안감이 조성된다
기분이 나쁘고 언짢다
따라서 최대한 빨리 초록 막대를 보는 게 낫다
죄악을 수습하는 방법
처음부터 죄를 저지르지 않는다
Refactoring 을 실시한다
Cf) Refactoring
정의
The process of changing a program's source code
without modifying its external functional behavior
in order to improve some of the
nonfunctional attributes of the software.
간단히 말하면
함수 / 파라메터 이름만 살짝 바꾸는 것
함수의 내부 구조만 바꾸는 것
어떤 때 Refactoring 을 하는가
확실한 성능의 개선이 있을 때
예) 정렬을 담당하는 함수가 하나 있는데
알고리즘을 Bubble Sort 로 했는데
나중에 정렬하는 핵심 코드만 Quick Sort 로 바꾸기
좀 더 가독성이 좋은 소스 코드로 바꿀 때
예) 변수 이름이 a, b, c, d, 이런 식인데
변수 이름을 gender, name, age, grade 하는 식으로
좀더 그 기능과 연관되는 이름으로 바꾸기
TDD With Refactoring
초록 막대 주기에서 Refactoring 을 하는 이유
Refactoring 이후에도 초록 막대를 본다는 것은
진짜로 External Behavior 는 바뀌지 않았음이
보장된다는 것을 의미
Refactoring 하고 나서 빨간 막대가 나타난다는 것은
뭔가 Refactoring 하다가 실수 했다는 뜻이니
실수를 바로잡을 기회가 생김
TDD 방법론의 특성이 안전한 Refactoring 을
보장하는 셈이다
Typical TDD Refactoring
죄악 수습용으로서의 Refactoring
죄악들의 공통된 특징
Test Code 에 쓰인 상수를
프로그램 Code 에도 그대로 쓴다
어떤 형태로든 중복이 발생한다
코드의 중복은 나쁜 것
코드의 중복을 제거한다!
성능 개선을 위한 Refactoring
성능 개선을 알아볼 수 있는 Test 도 추가하면 좋다
중복되는 코드 찾기, #1
테스트 코드의 상수와 코드 내부의 상수
밑줄 친 부분을 잘 관찰하자
…
Dollar five = new Dollar(5);
five.times(2);
…
…
int amount = 10;
…
5 * 2 = 10
테스트 코드의 assertEqual 의 10은 사실
New Dollar 의 5 와 five.times 의 2 의 곱이다!
…
Dollar five = new Dollar(5);
five.times(2);
…
…
int amount = 10;
…
중복이 더 잘 드러나도록
amount 에 대응된 10 을 5 * 2 로 고쳤다
이제 하나씩 하나씩 각개 격파해보자
…
Dollar five = new Dollar(5);
five.times(2);
…
…
int amount = 5 * 2;
…
중복되는 5가 등장하는 변수를
상수와 같은 값을 지니는 변수를 찾자
객체 생성자의 파라메터에 주목!
…
Dollar five = new Dollar(5);
…
…
int amount = 5 * 2;
public Dollar(int amount) {
…
변수를 이용하여 상수를 대체해
생성자의 amount 파라메터로 넘어오니까
굳이 Member variable 초기화에서 안해도 된다!
…
Dollar five = new Dollar(5);
…
…
int amount; // = 5 * 2;
public Dollar(int amount) {
this.amount = amount * 2;
}
…
Test 실행
Test 실행
매끄럽게 성공한다.
죄악의 수습
동일한 상수가 이곳 저곳에 있던 것을
최소한 상수는 중복되지 않도록 수정해 나가면
결국은 아름다운 코드가 나올 수 있게 된다!
그래도 기왕이면 죄악을 저지르지 말자.
상수 2 를 없애보자
상수와 같은 값을 갖는 변수 찾기가 관건!
…
five.times(2);
…
…
public Dollar(int amount) {
this.amount = amount * 2;
}
void times(int multiplier) {
}
…
파라메터 multiplier 발견
용서없다. 죄악은 척살이다.
…
five.times(2);
…
…
public Dollar(int amount) {
this.amount = amount;// * 2;
}
void times(int multiplier) {
this.amount *= multiplier;
}
…
Test 실행
결과
결국 성공한다.
더 리펙토링할 곳이 보이지 않는다
죄악은 전부 수습되었다
이 정도면 충분히 깔끔한 코드인 듯 싶다
테스트도 전부 통과한다
이번 TDD Rhythm 주기는 여기까지로 하자!
한 주기가 끝난 아름다운 소스
Public class Dollar {
int amount;
public Dollar(int amount) {
this.amount = amount;
}
void times(int multiplier) {
this.amount *= multiplier;
}
}
TDD 는 매우 Agile 하다
Comprehensive Documentation 보다
Working Software
Test 를 통과하는 Software == Working Software
무슨 수를 써서라도 Test 부터 통과시키자
죄악을 저질러도 상관 없다
용서받기만 하면 장땡이다
함수의 구조에 대한 복잡한 설계 과정 없이
Test Code 에 맞추는 것만으로도 Working
Software 를 얻을 수 있다
TDD 는 매우 Agile 하다
Follow Plan 보다 Respond to Change
각각의 할 일들은 한번에 하나씩만 처리한다
어느 것을 먼저 처리해도 문제될 것은 없다
Test 만 작성할 수 있으면 된다
고객 요구 조건의 변화는 결국 새로운 Test 의 추
가에 지나지 않는다
그리고 그건 TDD Rhythm 한번 타면 해결된다
Cf) TDD 에서는 기존의 Test Code 를 제거하는 것도 물
론 일어날 수 있는 일로 보고 있다.
엄청나게 기민하다!
eXtreme Programming 과의 조화
경제성
“설계” 와 “테스트” 가 하나로 합쳐짐
테스트 그 자체가 함수의 Interface 를 정의함!
점진적 설계
Test Code 는 한번에 하나씩 만드니 점진적 설계!
자동화된 테스트
TDD 는 언제나 자동화된 테스트를 만든다!
But TDD is too eXtreme!
모든 프로그래밍을 TDD 로 할 필요가 있나?
반드시 Test 를 먼저 짜야만 프로젝트가 진행될
수 있는 것만은 아니다.
TDD 주기는 지나치게 엄격하다
테스트 실패를 굳이 매번 확인할 필요는 없다
언제나 “죄악” 부터 저지르고 리펙토링해?
하나의 Test Code 만 통과시킨다고 해서 제대로
작동하는 프로그램을 못 만들 때도 있다
Just one more TDD Rhythm.
Recalling TO-DO List
아까 한 개의 할일이 처리되었지만
두 개의 할 일이 늘어났다
TO-DO List
$5 + 10YEN = $6 (환율이 1:10 일 때)
$5 = $5 (서로 다른 계좌) 어쨌든 이걸 골랐다
amount 를 private로
이건 기존 Test Code 수정으로 충분할듯
반올림?
Test Code 추가
두 개의 Dollar 객체를 만들어서
그 둘에게 equals 메소드를 적용하면
true 값이 나오는지를 확인하자.
public void testEquality() {
assertTrue(
new Dollar(5).equals(
new Dollar(5));
}
Test 실행
Test 실행
당연히 실패한다.
왜 굳이 실패해야 할까?
어차피 새로운 Test 를 추가하면 실패하는 건 너
무 당연한 일이 아닐까?
No! 이미 “잘못” 구현되어 있어야 하는 메소드가
있는데 그 메소드가 “잘못” 구현되어있다는 예측
을 벗어난다는 것을 검증하게 되는 경우도 있음
그리고 Test 실패를 관찰하는 건 0.1초면 충분!
그닥 시간낭비도 아니니까 걍 해주자. ㅋㅋ
마음놓고 죄악 저지르기
최대한 단순하게 equals 메소드를 작성하자
Public class Dollar {
…
public boolean equals(Object object) {
return true;
}
}
Test 실행과 그 후 …
결과
당연히 … 잘 통과한다.
Refactoring 할 거리를 찾아야 한다
우리에게는 죄악이 만든 true 라는 상수가 있다
분명히 이 상수는 다른 상수와 중복이다
아까 10 = 5 * 2 였던 것처럼,
이 true 도 뭔가 아주 당연한 연산에서 왔을 것
근데 … 도저히 못찾겠다면 ??
난관에 봉착했다.
원인 분석
Refactoring 스킬이 부족하다면
엄밀히 따지면 코드 중복이지만
대충 봐서는 파악하기 힘든 경우에
도저히 대처를 할 수가 없다
대응 방법
Refactoring 스킬을 조홀라 키운다
Test Code 를 좀 더 잘 짠다
대응 방법 1을 따른다면
일단 상수 true를 다른 상수로 표현해 보자.
논리값 true 가 나올 수 있는 가장 대표적인 경우
라면 역시 “==“ 연산자 사용이다.
Test Code 에는 동일한 상수 5 가 2회 나온다.
이 상수들은 서로 다른 파라메터로 전달된다
따라서 true 를 일단 5 == 5 로 고쳐보자
다시 각각을 this.amount 와
(Dollar)object.amount 로 고치는 게 가능하다
대응 방법 1의 한계점
너무 뛰어난 추론능력이 필요하다
추론은 곧 경험에서 우러나온다
경험이 부족한 사람은 대응 방법 1로 할 수 없다
경험이 쌓이기에는 시간이 걸린다
TDD 초심자가 처음부터 저런 좋은 길을 걷기는
매우 힘들다
결국 TDD 를 기피하게 될지도 …
아니, 대응 방법 2 로 가보자!
TDD 원칙을 완전히 따르려면
최초부터 테스트 코드를 작성할 적에
2가지 경우의 수를 고려해 본다
TDD 스럽게 하기만 하면 된다면
테스트 코드가 좀 부족했음을 인정하고
한꺼번에 통과시킬 테스트 코드를 … 추가하여
전체 테스트를 통과시키기 위해 노력해보자
물론 이때는 지금까지의 죄악은 싸그리 초기화해
쓸데 없는 죄악때문에 헷갈릴 수 있으니까
Test Code 추가부터 다시 간다
처음부터 2 가지 경우를 고려해 보자!
public void testEquality() {
assertTrue(
new Dollar(5).equals(
new Dollar(5));
assertFalse(
new Dollar(5).equals(
new Dollar(6));
}
Test 실행
결과는?
당연히 실패한다.
가장 단순한 해결책은?
자세히 보니 true인가 false인가를 가르는
차이점이 하나 눈에 보인다
메소드 eqlaus 의 파라메터로 들어오는 객체의
amount 값이 5인가 6인가에 달려 있다 !!
죄악을 저지르자
괜찮아 이 모든 건 초록막대를 보고싶어서…
Public class Dollar {
…
}
public boolean equals(Object object) {
return (Dollar)object.amount == 5;
}
}
Test 실행
결과
잘 통과한다.
같은 죄악이지만 급이 다르다
Test Code 가 쓸 데 없이 둘이나 있는 듯 싶다
나중에 Test Code 자체에 대한 Refactoring 도
하는 날이 올 것이다
하지만 결과적으로 좀더 나은 코드를 가져왔다
이런 Test Code 를 “삼각측량” 이라 한다
Refactoring 을 위해!
상수의 중복이 훨씬 직관적으로 보인다
…
new Dollar(5).equals(
…
new Dollar(5).equals(
…
…
public Dollar(int amount) {
this.amount = amount;
…
public boolean equals(Object object) {
return (Dollar)object.amount == 5;
…
중복이 되는 5를 제거한다
결국 이건 this.amount 다!
…
new Dollar(5).equals(
…
new Dollar(5).equals(
…
…
public Dollar(int amount) {
this.amount = amount;
…
public boolean equals(Object object) {
return (Dollar)object.amount ==
this.amount; // 5;
테스트 실행
결과
아주 잘 통과한다.
또 하나의 주기가 끝나다!
이런 식으로 계속 한 기능씩 한 기능씩 점진적
진행을 해 나가도록 하자.
아무리 거창해 보이는 프로젝트라도 TDD 주기
단위로 쪼개버리면 별거 아니다.
Google loves TDD
Google Testing Blog
http://googletesting.blogspot.com/
Testing on the Toilet
Introducing numerous Testing Techniques
http://googletesting.blogspot.com/2007/01/introd
ucing-testing-on-toilet.html
SUMMARY
Test-Driven Development (TDD) 이란,
코드 작성에 앞서 반드시 작성할 코드를 검증할
테스트 코드부터 작성한 뒤에 코드를 작성하는
것을 원칙으로 하는 프로그램 개발 방법론이다.
테스트 코드 추가, 실패, 간단한 구현, 테스트 통
과, 리펙토링의 순서로 개발을 진행한다.
TDD 철학대로만 강박관념을 갖고 코딩을 하는
것이 좋은 것만은 아니지만, 적어도 Testing 의
강력한 힘에 따라 XP 와 잘 섞어쓸 수 있다.
References
Kent Beck, 테스트 주도 개발
김창준, 강규영 역
http://www.javajigi.net/pages/viewpage.actio
n?pageId=278