[테스트 주도 개발] 20장_뒷정리하기

  • 테스트 메서드 호출하기
  • 먼저 setUp 호출하기
  • 나중에 tearDown 호출하기
  • 테스트 메서드가 실패해도 tearDown 호출하기
  • 여러 개의 테스트 실행하기
  • 수집된 결과를 출력하기
  • WasRun 에 로그 문자열 남기기

setUp() 은 테스트 메서드 실행되기 전에 호출, tearDown() 은 테스트 메서드가 실행된 후에 호출되어야한다.

1
2
3
4
5
class WasRun(TestCase):
def setUp(self):
self.wasRun= None
self.wasSetUp = 1
self.log = "setUp"

이제 testSetUp이 플래그 대신 로그를 검사하자.

1
2
3
4
class TestCaseTest(TestCase):
def testSetUp(self):
self.test.run()
assert("sepUp" == self.test.log)
1
2
3
4
class TestCaseTest(TestCase):
def testSetUp(self):
self.test.run()
assert("sepUp testMethod" == self.test.log)

이제 testSetUp 의 이름을 바꾸자.

1
2
3
4
5
class TestCaseTest(TestCase):
def testTemplateMethod(self):
teset = WasRun("testMethod")
test.run()
assert("sepUp testMethod" == self.test.log)
  • 테스트 메서드 호출하기
  • 먼저 setUp 호출하기
  • 나중에 tearDown 호출하기
  • 테스트 메서드가 실패해도 tearDown 호출하기
  • 여러 개의 테스트 실행하기
  • 수집된 결과를 출력하기
  • WasRun 에 로그 문자열 남기기

이제 tearDown() 을 테스트할 준비가 됐다.

Read more

[테스트 주도 개발] 17장_Money 회고

1. 다음에 할 일은 무엇인가

Sum.plus() 와 Money.plus() 사이에 중복이 남았다. Expressoin 을 인터페이스가 아니라 클래스가 바꾼다면 공통 코드를 담아낼 적절한 곳이 될 것이다.
작업을 끝낸 후에 SmallLint 같은 코드 감정 프로그램을 실행해보면 좋다.
“다음에 할일은 무엇인가” 에 관련된 또 다른 질문은 “어떤 테스트들이 추가로 필요할까” 이다.
할일 목록이 빌 때가 그때까지 설계한 것을 검토하기에 적절한 시기이다. 말과 개념이 잘 통하는가 ? 현재의 설꼐로 제거하기 힘든 중복이 있는가 ?

2. 메타포

“통화가 다른 여러 금전” 에 대해 사용한 메타포는 벡터였다. 그 전엔, MoneySum 을 사용하다가 적절하고 물리전인 MoneyBag 으로 바꿨다. 그리고 마지막에는 많은 사람에게 익숙한 Wallet 으로 바꿧다. 이 모든 메타포는 Money 의 집합이 딱 떨어지는숫자로 된다는 것을 암시하다. 즉, 같은 통화의 값은 합칠 수 있다. (2USD + 3USD + 5CHF = 5USD + 5CHF)
Expression 메타포는 중복되는 통화를 합치는 세세한 일단의 문제에서 해방시켰다. 코드도 그 어느 때보다 명확하다.

3. 코드 메트릭스

  1. 코드와 테스트 사이에 대략 비슷한 양의 함수와 줄이 있다.
  2. 테스트 코드에 분기나 반복문이 없기 때문에 테스트 복잡도는 1 이다. 명시적인 흐름 제어 대신에 다형성을 사용해서 실제 코드의 복잡도 역시 낮다.

4. 프로세스

TDD 의 주기는,

  1. 작은 테스트 추가
  2. 모든 테스트 실행, 실패 하는 것 확인
  3. 코드에 변화
  4. 모든 테스트 실행, 성공 하는 것 확인
  5. 중복 제거 위해 리펙토링

5. 테스트의 질

Read more

[테스트 주도 개발] 13장_진짜로 만들기

모든 중복을 제거하기 전에는 “5달러 + 5달러” 테스트는 끝난 것이 아니다. 코드 중복은 없지만, 가짜 구현에 있는 10 달러는 사실 테스트 코드에 있는 “5달러 + 5달러” 와 같다.

1
2
3
Money reduce(Expression source, String to) {
return Money.dollar(10);
}
1
2
3
4
5
6
7
8
@Test
void testSimpleAddition() {
Money five = Money.dollar(5);
Expression sum = five.plus(five); // here
Bank bank = new Bank();
Money reduced = bank.reduce(sum, "USD");
assertEquals(Money.dollar(10), reduced);
}

이전에는, 가짜 구현이 있을 때 진짜 구현으로 작업해가는 것이 명확했다. 이번에는 어떻게 거꾸로 작업해야할지 명확하지 않다. 그래서 조금 불확실하지만 순방향으로 작업해보자.
우선, Money.plus() 는 그냥 Money 가 아닌, Expression(Sum) 을 반환해야한다. 두 Money 의 합은 Sum 이어야한다.

1
2
3
4
5
6
7
8
@Test
void testPlusReturnsSum(){
Money five = Money.dollar(5);
Expression result = five.plus(five);
Sum sum = (Sum) result;
assertEquals(five, sum.augend);
assertEquals(five, sum.addend);
}

위 코드를 컴파일하기위해서는, augend, addend 필드를 가진 Sum 클래스가 필요하다.

1
2
3
4
class Sum {
Money augend;
Money addend;
}

다시 실행해보면, Money.plus() 는 Sum 이 아닌 Money 를 반환하게 되어 있어서, ClassCastExceptoin 을 발생시킨다. 그래서, 다음 처럼 수정하자.

1
2
3
4
// Money
Expression plus(Money addend){
return new Sum(this, addend)
}

Sum 생성자도 필요하다.

Read more

[테스트 주도 개발] 14장_바꾸기

이번에는 2프랑을 달러로 바꾸고 싶다.

1
2
3
4
5
6
7
@Test
void testReduceMoneyDifferentCurrency(){
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Money result = bank.reduce(Money.franc(2), "USD");
assertEquals(Money.dollar(1), result);
}

프랑을 달러로 바꿀때 나누기 2를 하자. 다음 코드를 추가하자.

1
2
3
4
5
6
// Money
public Money reduce(String to){
int rate = (currency.equals("CHF") && to.equals("USD")) ? 2 : 1;

return new Money(amount / rate, to);
}

환율에 대한 일은 모두 Bank 가 처리해야한다. Expression.reduce() 의 인자로 Bank 를 넘겨야할 것이다. 우선 호출하는 부분을 작성하자.

1
2
3
4
5
6
class Bank {

Money reduce(Expression source, String to) {
return source.reduce(this, to);
}
}

그리구 구현 부분,

1
2
3
interface Expression {
Money reduce(Bank bank, String to);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Sum implements Expression {
Money augend;
Money addend;

Sum(Money augend, Money addend) {
this.augend = augend;
this.addend = addend;
}

public Money reduce(Bank bank, String to) {
int amount = augend.amount + addend.amount;
return new Money(amount, to);
}
}
1
2
3
4
5
6
// Money
public Money reduce(Bank bank, String to){
int rate = (currency.equals("CHF") && to.equals("USD")) ? 2 : 1;

return new Money(amount / rate, to);
}
Read more

[테스트 주도 개발] 15장_서로 다른 통화 더하기

이제 드디어, “5달러 + 10프랑” 테스트할 준비가 모두 되었다.
우리가 원하는 코드는,

1
2
3
4
5
6
7
8
9
10
11
@Test
void testMixedAddition() {
Expression fiveBucks = Money.dollar(5);
Expression tenFrancs = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Money result = bank.reduce(
fiveBucks.plus(tenFrancs), "USD"
);
assertEquals(Money.dollar(10), result);
}

컴파일 에러가 많다. 천천히 해결하자.

1
2
3
4
5
6
7
8
9
10
11
@Test
void testMixedAddition() {
Money fiveBucks = Money.dollar(5);
Money tenFrancs = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Money result = bank.reduce(
fiveBucks.plus(tenFrancs), "USD"
);
assertEquals(Money.dollar(10), result);
}

실패한다. 10 USD 대신, 15 USD 가 나왔다. Sum.reduce() 가 인자를 축약하지 않은 것으로 보인다. 다음과 같이 두 인자를 모두 축약하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Sum implements Expression {
Money augend;
Money addend;

Sum(Money augend, Money addend) {
this.augend = augend;
this.addend = addend;
}

public Money reduce(Bank bank, String to) {
int amount = augend.reduce(bank, to).amount +
addend.reduce(bank, to).amount;
return new Money(amount, to);
}
}

그리고, Expression 이어야하는 Money 들을 조금씩 없앨 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Sum implements Expression {
Expression augend;
Expression addend;

Sum(Expression augend, Expression addend) {
this.augend = augend;
this.addend = addend;
}

public Money reduce(Bank bank, String to) {
int amount = augend.reduce(bank, to).amount +
addend.reduce(bank, to).amount;
return new Money(amount, to);
}
}

Money 의 plus() 인자도 바꾸자.

1
2
3
Expression plus(Expression addend){
return new Sum(this, addend);
}
Read more