630 likes | 1.01k Views
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 코딩을 시작하기에 앞서 , 실패하는 , 자동화된 테스트 코드 부터 작성하라 . 중복 을 제거 하라.
E N D
Combacsa’s SPARCS Web Seminar Software development #2: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 … 할일 i TEST still SUCCESS TEST Code for 할일 i Refactored Code for 할일 i TEST SUCCESS 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 • 모든 테스트를 통과한다 • 개발 진행 = 새로운 빨간막대 파란막대
제한점 • 어디까지나 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 thenonfunctional 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 만 통과시킨다고 해서 제대로 작동하는 프로그램을 못 만들 때도 있다
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 를 좀 더 잘 짠다