[테스트 주도 개발] 29장_xUnit 패턴

xUnit 계열의 테스트 프레임워크를 위한 패턴을 정리한다.

assertion

테스트가 어떻게 잘 작동하는지 테스트 할 것인가?
boolean 수식을 이용해서, 프로그램이 코드가 동작하는지 자동으로 판단하도록 해라.

fixture

여러 테스트에서 공통으로 사용하는 객체들을 생성할 때 어떻게 하면 좋을까?
각 테스트 코드의 지역 변수를 인스턴스 변수로 바꾸고 setUp() 메서드에 재정의해라. 그리고, setUp() 메서드에서 인스턴스 변수들을 초기화하도록 해라.

외부 fixture

픽스처 중에 외부 자원이 있을 경우에 어떻게 해제할 것인가?
tearDown() 메서드를 재정의해라. 그리고, teaDown() 메서드에서 자원을 해제하라.

테스트 메서드

테스트 케이스 하나를 어떻게 표현하나?
‘test’ 로 시작하는 이름의 메서드로 읽기 쉽도록 표현해라.

예외 테스트
Read more

[테스트 주도 개발] 31장_리팩토링

시스템의 설계를 작은 단계를 통해 변화시키는 방법들을 정리한다.

차이점 일치시키기

비슷해 보이는 두 코드 조각을 합리려면?
두 코드를 단계적으로 닮아가게 수정한다. 완전히 동일해지면 둘을 합쳐라.

변화 격리하기

객체나 메서드의 일부만 바꾸려면?
일단 바꿔야할 부분을 격리해라. 격리 방법으로는,

  1. 메서드 추출
  2. 객체 추출
  3. 메서드 객체
메서드 추출하기

길고 복잡한 메서드를 읽기 쉽게 만들려면?
긴 메서드의 일부분을 별도의 메서드로 분리하고 이를 호출해라. 복잡한 코드를 이해하기 쉽고, 중복을 제거하기도 좋다.

메서드 인라인

제어 흐름이 너무 산재되어 있으면?
메서드 호출하는 부분을 호출될 메서드의 본문으로 교체해라.

Read more

[테스트 주도 개발] 30장_디자인 패턴

TDD 에서 쓰일 수 있는 디자인 패턴들을 정리한다.

커맨트 패턴

간단한 메서드 호출보다 복잡한 형태의 계산 작업에 대한 호출이 필요하다면 어떻게 할까?
계산 작업에 대한 객체를 생성해서 이를 호출하면 된다. Runnable 인터페이스가 대표적인 예이다.

값 객체

공유되어야 하지만, identity (동일성) 은 중요하지 않을 때 겍체를 어떤식으로 설계할까?
객체가 생성될 때 객체의 상태를 설정한다. 그리고, 이 상태가 절대 변할 수 없도록 해라. 이 객체에 대해 수행되는 연산은 언제나 새로운 객체를 반환하게 만들어라.
별칭 문제란, 두 객체가 제 삼의 다른 객체에 대한 참조를 공유하는데, 한 객체가 공유 객체의 상태를 변화시키면, 다른 객체가 영향을 받는 문제이다. 해결하기 위한 방법으로,

  1. 의존하는 객체애 대한 참조를 외부로 알리지 않는 방법이다. 그 대신 객체에 대한 복사본을 제공한다. 이 방법의 문제는, 수행 시간이나 메모리 공간 측면에서 비효율적이다. 또한, 공유 객체의 상태 변화를 공유할 수 없다.
  2. 옵저버 패턴. 의존하는 객체에 자기를 등록해 놓고, 객체의 상태가 변하면 통지를 받는다. 이 방법의 문제는, 제어 흐름이 어렵고 의존성을 설정하고 제거하기 위한 로직이 지저분해진다.
  3. 객체를 덜 객체답게 취급. 객체는 시간의 흐름에 따라 변할 수 있는 상태를 가진다. 그런데, 이러한 특징을 제거해버려라. 이것이 값 객체이다.

모든 값 객체는 동등성을 구현해야한다. 만약,

  1. 계약을 표현하는 객체가 두 개가 있는데, 둘이 서로 같은 객체가 아니면 이 둘은 동등한것이 아니라 다른것이다.
  2. 하지만, 5프랑 짜리 동전 두개가 있는데, 이 동전들이 동일한 (identity) 동전인지는 중요하지 않다. 5프랑은 5프랑이다. 이들은 동등해야한다.(equality)
널 객체

객체의 특별한 상황은 어떻게 표현하나?

Read more

[테스트 주도 개발] 32장_TDD 마스터하기

좋은 테스트인지는 어덯게 알 수 있나?

다음은 설계에 문제가 있음을 알려준다.

  1. 긴 setUp 코드
  2. setUp 중복
  3. 실행 시간이 오래 걸리는 테스트
  4. 깨지기 쉬운 테스트
테스트를 지워야할 때는?
  1. 테스트를 삭제하고 자신감이 줄어들 것 같으면 삭제하지마라.
  2. 두 테스트 코드가 동일한 부분을 실행해도, 서로 다른 시나리오를 말한다면 그대로 남겨둬라.
프로젝트 중반에 TDD 를 도입하려면?
  1. 우선, 변경의 범위를 제한해라. 극적으로 단순화 될 수 있지만 지금 당장 변할 필요가 없으면 그냥 그대로 둬라.
  2. 그리고나서, 테스트와 리팩토링 사이의 교착 상태를 풀어줘라. 파트너와 같이 작업을 하거나, 아주 조심스럽게 작업을 할 수 있다.

시간이 지나면, 시스템에서 늘 변화하는 부분들은 테스트 주도로 된 것 처럼 보이게 될 것이다.

TDD 와 패턴의 관계는?

단순히 시스템에서 무슨 일을 할지 생각하고, 나중에 설계가 알아서 정해지도록 해라.

Read more

[테스트 주도 개발] 25장_테스트 주도 개발 패턴

어떻게 테스트할 것인지에 대해 이야기하기 전에, 다음 질문들을 정리해보자.

  1. 테스트한다는 것은 무엇을 뜻하는가 ?
  2. 테스트를 언제 해야하는가 ?
  3. 테스트할 로직을 어떻게 고를 것인가 ?
  4. 테스트할 데이터를 어떻게 고를 것인가?
격리된 테스트

테스트는 전체 애플리케이션을 대상으로 하는 것 보다, 작은 스케일로 하는게 좋다.
각각의 테스트는 다른 테스트와 독립적이어야한다. 즉, 문제가 하나면 테스트도 하나만 실패해야하고 둘이면 두개만 실패해야한다.격리된 테스트가 내포하는 특징은, 테스트가 실행 순서에 독립적이게 된다는 것이다. 테스트를 격리하기 위한 작업은, 시스템이 응집도는 높고 결합도는 낮은 객체의 모음으로 구성되도록 한다.

테스트 목록

뭘 테스트 해야하나 ? 시작하기 전에 작석해야할 테스트 목록을 모두 적어둬라.
우선 구현할 필요가 있는 모든 오퍼레이션의 사용 예들을 적어라. 그 다음, 이미 존재하지 않는 오퍼레이션에 대해서는 해당 오퍼레이션의 null 버젼 (아무 일도 하지 않는 버젼) 을 리스트에 적어라. 마지막으로 깔끔한 코드를 얻기 위해 이번 작업을 끝내기 전에 반드시 해야할 리펙토링 목록을 적어라.

테스트 우선

테스트는 언제 작성하는 것이 좋은가 ? 테스트 대상이 되는 코드를 작성하기 직전에 작성해라.

assertion 우선

테스트 작성할 때 단언은 언제쯤 쓸까 ? 단언를 먼저 쓰고 시작하라.
단언을 먼저 작성하면 작업을 단순하게 만들 수 있다. 소켓을 통해 다른 시스템과 통신하려고 한다고 해보자. 통신을 마친 후 소켓은 닫혀 있고, 소켓에서 문자열 abc 를 읽어와야한다고 하자.

Read more

[테스트 주도 개발] 26장_빨간 막대 패턴

이 패턴들은 테스트를 언제 어디서 작성할 건지, 테스트 작성을 언제 멈출지에 대한 것이다.

한 단계 테스트

목록에서 다음 테스트를 고를 때 무엇을 기준으로 골라야 할까? 새로운 무언가를 가르쳐 줄 수 있고, 구현할 수 있는 확신이 드는 테스트를 골라라.

시작 테스트

어떤 테스트부터 시작하는게 좋을까? 오퍼레이션이 아무 일도 하지 않는 경우를 먼저 테스트해라.

설명 테스트

자동화된 테스트가 널리 쓰이게 하려면 어떻게 해야할까? 테스트로 설명을 요청하고 테스트로 설명해라.
예를 들어, 누군가 시퀀스 다이엄드램을 설명하라고 하면, 시퀀스 다이어그램의 모든 요소를 포함하는 테스트케이스를 작성해라.

학습 테스트

외부에서 만든 소프트웨어에 대한 테스트를 작성해야 할 때도 있나? 패키지의 새로운 기능을 처음 사용해보기 전에 할 수 있다.
자바의 모바일 정보 기기 프로파일 라이브러리르 기반으로 뭔가를 만든다고 하자. 그냥 바로 사용하는 대신 API 가 우리 예상대로 실행된다는 것을 확인해줄만한 테스트를 먼저 만들어라. 만약, 테스트가 통과하지 않으면 애플리케이션 역시 실행되지 않을것이므로 애플리케이션을 실행해볼 필요도 없다.

또 다른 테스트
Read more

[테스트 주도 개발] 27장_테스팅 패턴

이 패턴들은 더 상세한 테스트 작성법에 대한 것이다.

자식 테스트

지나치게 큰 테스트 케이스를 어떻게 돌아갈 수 있을까 ? 원래 테스트 케이스의 깨지는 부분에 해당하는 작은 테스트 케이스를 작성하고 그 작은 테스트케이스가 실행되도록 해라. 그 후에 다시 원래의 큰 테스트 케이스를 추가해라.

Mock Object

비용이 많이 들거나 복잡한 리소스에 의존하는 객체를 테스트하려면 어떻게 해야할까 ? 상수를 반환하게끔 만든 속임수 버젼의 리소스를 만들면 된다.
예를 들어, 데이터베이스는 시작 시간이 오래 걸린고 깨끗한 상태를 유지하기 어렵다. 그리고 만약 데이터베이스가 원격 서버에 있다면 테스트 성공 여부가 네트워크 상의 물리적인 영향을 받게 된다. 또, 데이터베이스는 개발 중 많은 오류의 원인이다. 해법은, 진짜 데이터베이스를 사용하지 않는 것이다.
모의 객체를 사용하면 성능과 견고함 이외에, 가독성이 좋아진다. 또한, 가시성에 대해 고민하도록 격려해서 설계에서 커플링이 감소하도록 한다.
하지만 모의 객체를 사용하면, 프로젝트에 위험 요소가 추가된다. 모의 객체가 진짜 객체와 동일하게 동작하지 않으면 어떻게 될까 ? 모의 객체용 테스트 집합을 진짜 객체가 사용 가능해질때 그대로 적용해서 이 위험을 줄일 수 있다.

Crash Test Dummy

호출되지 않을 것 같은 에러코드를 어떻게 테스트할 것인가 ? 실제 작업을 수행하는 대신 그냥 예외를 발생시키기만 하는 특수한 객체를 만들어서 호출한다.
파일 시스템에 여유 공간이 없을 경우 발생할 문제에 대해 테스트를 해보자. 자바의 익명 내부 클래스는 테스트하기 원하는 메서드만이 오류를 발생하게끔 하기 위해 유용하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
void testFileSystemError(){
File file = new File("foo") {
public boolean createNeweFile() throws IOException {
throw new IOException();
}
};

try {
saveAs(file);
fail();
} catch (IOException e){

}
}
깨진 테스트

혼자서 프로그래밍할 때 프로그래밍 세션을 어떤 상태로 끝내는게 좋을까 ? 마지막 테스트가 깨진 상태로 끝내라.
나중에 다시 코딩할 때, 어느 작업부터 시작할 것인지 명백히 알 수 있다. 전에 하고 있던 생각에 대한 명확하고 구체적인 책갈피를 가지는 것이다.

Read more

[테스트 주도 개발] 28장_초록 막대 패턴

코드가 테스트를 통과하게 만들기 위해 이 패턴들을 사용해라.

가짜로 구현하기 ( 진짜로 만들기 전까지만 )

실패하는 테스트를 만든 후 첫 번째 구현은 어떻게 하는게 좋을까? 상수를 반환하게 하라. 일단 테스트가 통과하면 단계적으로 상수를 변수를 사용하는 수식으로 변형한다.

삼각 측량

오로지 예가 두개 이상일 때만 추상화를 해라.
두 정수 합을 반환하는 함수를 예로 보자.

1
2
3
4
@Test
void testSum(){
assertEquals(4, plus(3,1));
}
1
2
3
private int plus(int augend, int addend) {
return 4;
}

삼각 층량을 사용해서 바른 설계로 간다면, 다음과 같이 작성해야한다.

1
2
3
4
5
@Test
void testSum(){
assertEquals(4, plus(3,1));
assertEquals(7, plus(3,4));
}

그러면, 우리는 plus 의 구현을 추상화 할 수 있다.

Read more

[테스트 주도 개발] 22장_실패 처리하기

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

실패한 테스트를 발견하면, 세밀한 단위의 테스트를 작성해서 올바른 결과를 출력해보자.

1
2
3
4
5
6
// TestCaseTest
def testFailedResultFormatting(self):
result = TestResult()
result.testStarted()
result.testFailed()
assert ("1 run, 1 failed" == result.summary())

실패 횟수를 세어보자.

1
2
3
4
5
6
// TestResult
def __init__(self):
self.runCount = 0
self.failureCount = 0
def testFailed(self):
self.failureCount = self.failureCount + 1

이제 출력하자.

1
2
3
// TestResult
def summary(self):
return "%d run, %d failed" % (self.runCount, self.failureCount)

이제 테스트 메서드에서 예외를 잡으면, testFailed() 를 호출하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
// TestCase
def run(self):
result = TestResult()
result.testStarted()
self.setUp()

try:
method = getattr(self, self.name)
method()
except:
result.testFailed()
self.tearDown()
return result

run() 에는 문제가 있다. 바로 setUp()에서 문제가 발생한 경우에는 예외가 잡히지 않는다는 것이다. todo list 에 넣어두자.

Read more

[테스트 주도 개발] 21장_셈하기

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

테스트 메서드에에서 예외가 발생하건 말건 tearDown() 이 호출되도록 구현했다. 하지만,테스트가 작동하기 위해선 예외를 작아야한다.
여러 테스트를 실행하고 그 결과를 다음과 같이 보길 원한다.
“5개 테스트가 실행됨. 2개 실패. TestCaseTest.testFooBar-ZeroDivide Exception, MoneyTest.testNegation-AssertionError”
TestCase.run()이 테스트 하나의 실행 결과를 기록하는 TestResult 객체를 반환하게 만들자.

1
2
3
4
5
// TestCaseTest
def test_result(self):
test = WasRun("testMethod")
result = test.run()
assert("1 run, 0 failed" == result.summary())

가짜 구현하자.

1
2
3
class TestResult:
def summary(self):
return "1 run, 0 failed"

이제 TestCase.run() 이 TestResult 를 결과로 반환한다.

1
2
3
4
5
6
7
// TestCase
def run(self):
self.setUp()
method = getattr(self, self.name)
method()
self.tearDown()
return TestResult()

이제 테스트가 실행된다. 이제 summary()의 구현을 실체화하자. 우선 실행된 테스트의 수를 상수로 만들자.

1
2
3
4
5
// TestResul
def __init__(self):
self.runCount = 1
def summary(self):
return "%d run, 0 failed" % self.runCount

runCount를 0으로 초기화하고 테스트가 실행될 때마다 1씩 증가하도록 만들 수 있다.

Read more