Chapter 4

xUnit 구현

  • 4.1 부트스트래핑과 첫 번째 테스트
  • 4.2 setUp과 로그 문자열
  • 4.3 tearDown과 안전한 정리
  • 4.4 TestResult와 실패 처리
  • 4.5 TestSuite와 Composite 패턴
  • 4.6 2부 회고

테스트 프레임워크를 TDD로 만든다니, 거울 안의 거울 같은 재귀적인 상황이지. 테스트를 테스트하는 테스트를 만드는 거야. 2부에서는 xUnit 프레임워크 자체를 밑바닥부터 구현해.

부트스트래핑 문제가 먼저야. 테스트 프레임워크가 없는 상태에서 테스트 프레임워크를 TDD로 만들어야 하거든. 닭이 먼저냐 달걀이 먼저냐 같은 문제인데, 켄트 벡의 해결책은 단순해 -- 처음에는 수동으로 확인하고, 프레임워크가 조금 만들어지면 그걸로 자기 자신을 테스트하는 거야. 첫 번째 목표는 "테스트 메서드를 호출할 수 있는가"지. TestCase 클래스를 만들고, 테스트 메서드 이름을 문자열로 받아서 Python의 getattr()로 메서드를 찾아 호출해. WasRun이라는 테스트용 클래스를 만들어서, 메서드가 실제로 호출됐는지 플래그로 확인하지. 테스트 프레임워크의 가장 기본적인 기능은 메서드를 호출하는 것이야. 대단한 게 아니거든.

다음은 setUp 메서드야. 테스트마다 공통으로 필요한 객체를 만드는 코드가 있잖아. 매 테스트 메서드 안에서 반복하면 중복이니까, setUp()을 만들어서 테스트 메서드 실행 전에 자동으로 호출되게 하지. TestCase의 run() 메서드를 수정해서, 테스트 메서드를 호출하기 전에 setUp()을 먼저 호출해. 여기서 켄트 벡이 깔끔한 리팩토링을 하나 해. wasRun, wasSetUp 같은 플래그가 점점 늘어나니까, 로그 문자열로 바꾸는 거야. setUp()이 호출되면 로그에 "setUp ", 테스트 메서드가 호출되면 "testMethod "를 추가해. 로그를 확인하면 호출 순서까지 알 수 있지. 플래그 대신 로그를 쓰면 테스트의 표현력이 확 좋아져.

tearDown은 테스트 실행 후 뒷정리야. setUp이 "테이블 차리기"였다면, tearDown은 "설거지하기"지. 파일을 열었으면 닫아야 하고, DB 연결을 만들었으면 끊어야 해. run() 메서드에서 테스트 메서드 실행 후에 tearDown()을 호출하도록 추가하면, 최종 로그는 "setUp testMethod tearDown "이 돼. 여기서 중요한 점이 있어 -- 테스트가 실패하더라도 tearDown은 반드시 실행되어야 해. 안 그러면 자원이 누수되거든. 그래서 try/finally 구조를 사용하지. 테스트 메서드가 예외를 던져도 finally 블록에서 tearDown이 호출돼.

테스트 실행 결과를 수집하는 것도 자동화해야지. TestResult 클래스를 만들어서 run() 메서드가 TestResult를 반환하도록 바꿔. result.summary()를 호출하면 "1 run, 0 failed" 같은 문자열을 반환해. 사람이 읽기 좋은 형태지. run() 메서드 안에서 result.testStarted()를 호출해서 실행 횟수를 올리고. 그 다음엔 실패하는 테스트 처리야. 일부러 실패하는 테스트를 만들었을 때, 예외가 나면 프로그램이 그냥 멈추면 안 되잖아. run() 메서드에서 테스트 메서드 호출을 try/except로 감싸서, 예외가 발생하면 result.testFailed()를 호출해서 실패 횟수를 올려. 실패한 테스트가 다른 테스트의 실행을 막으면 안 돼. 하나가 실패해도 나머지는 계속 돌아가야 하거든.

테스트가 늘어나면 하나씩 실행하는 건 귀찮잖아. TestSuite 클래스를 만들어서 여러 테스트를 모아두고 한 번에 실행하지. suite.add(test)로 테스트를 추가하고, suite.run(result)로 전부 실행해. 내부적으로는 리스트에 테스트를 담아두고 순서대로 run()을 호출하는 거야. 여기서 Composite 패턴이 자연스럽게 등장해. TestCase와 TestSuite 모두 run() 메서드를 가지거든. TestSuite 안에 TestCase를 넣을 수도 있고, 다른 TestSuite를 넣을 수도 있지. 클라이언트는 둘을 구분할 필요가 없어. 패턴을 의도적으로 적용한 게 아니라, TDD를 하다 보니 자연스럽게 나온 거야.

2부 전체를 돌아보면, 우리가 만든 xUnit 프레임워크는 TestCase(테스트 메서드 실행 기본 단위), TestSuite(여러 TestCase를 모아서 실행), TestResult(실행 결과 수집), setUp/tearDown(테스트 전후 준비/정리)으로 구성돼. 놀라운 건 이 전체가 코드 몇십 줄밖에 안 된다는 거야. 테스트 프레임워크의 핵심은 메서드를 호출하고, 예외를 잡고, 결과를 세는 것. 본질은 이게 전부지. 켄트 벡은 독자에게 남은 것들(setUp에서 예외 처리 등)을 직접 구현해보라고 남겨둬. xUnit을 직접 만들어보는 경험이 테스트를 이해하는 가장 좋은 방법이거든. 사용하기만 하면 블랙박스로 남지만, 직접 만들어보면 속까지 보이니까.


정리

4장 읽고 기억할 거 세 가지:

  1. 테스트 프레임워크의 핵심은 단순해. 메서드 호출, 예외 처리, 결과 수집. 이 세 가지가 전부야. 코드 몇십 줄로 xUnit의 핵심을 만들 수 있지
  2. setUp/tearDown은 쌍이야. 준비한 건 반드시 정리해야 하고, tearDown은 테스트 성공 여부와 관계없이 항상 실행돼야 해. try/finally가 이걸 보장하지
  3. 직접 만들어봐. xUnit을 한 번이라도 구현해본 사람은 테스트에 대한 이해가 근본적으로 달라져. Composite 패턴 같은 설계도 TDD 하다 보면 자연스럽게 나와