이제 테스트는 실패까지 셀 수 있다. 하지만 테스트를 하나씩 손으로 run 하는 건 여전히 지겹다. 목록의 마지막 큰 항목 — “여러 테스트 실행하기” — 을 풀 차례다. 여러 개를 묶어 한 번에 돌리고, 결과는 하나로 합산되게 한다.
하나든 여럿이든 똑같이 run 한다
TestSuite를 만든다. 테스트들을 add로 담아 두었다가, run을 부르면 담긴 모든 테스트를 차례로 실행한다. 여기서 결정적인 설계 선택이 나온다 — TestSuite.run과 TestCase.run이 같은 모양이어야 한다는 것. 그래야 묶음 안에 묶음을 또 담을 수 있고, 부르는 쪽은 그게 낱개인지 묶음인지 신경 쓸 필요가 없다. 이게 컴포지트 패턴이다.
이 통일을 위해 run의 시그니처를 손본다. 지금까지 run은 자기 안에서 TestResult를 새로 만들었다. 하지만 여러 테스트의 결과를 하나로 합산하려면, 결과 객체를 밖에서 만들어 넘겨줘야 한다. 그래서 run(self, result) — 결과를 인자로 받는 형태로 바꾼다. 개별 테스트도, 묶음도 넘겨받은 같은 result에 자기 몫을 기록한다.
판단 기준: 개별과 묶음을 같은 인터페이스로 다루고 싶다면(컴포지트), 둘의 진입점 시그니처를 먼저 통일한다 — 여기서는 run(result). 함정: 각 테스트가 결과 객체를 스스로 만들면 합산이 불가능하다. 소유권을 밖으로 끌어내는 리팩터링이 컴포지트의 전제다.
class TestCase: def __init__(self, name): self.name = name def run(self): result = TestResult() result.testStarted() self.setUp() try: method = getattr(self, self.name) method() except: result.testFailed() self.tearDown() return resulttest = WasRun("testMethod")print(test.run().summary())
TestSuite.run이 TestCase.run과 시그니처가 같기 때문에, suite 안에 또 다른 suite를 add 해도 아무 코드도 고칠 필요가 없다. 낱개 테스트와 테스트 묶음이 동일한 타입처럼 취급된다 — 이것이 “얼마나 달콤한지”의 정체다. 작은 통일 하나가 임의 깊이의 트리를 공짜로 열어 준다.
이 장이 남긴 할 일 목록
여러 테스트 실행하기- 테스트 메서드 이름들을 모아 Suite를 자동으로 만들기
- setUp 실패도 결과에 기록하기
판단 기준: run(result)로 시그니처를 통일한 덕에 TestSuite는 열 줄 남짓으로 끝났다. 큰 기능이 작아 보이면, 대개 앞선 리팩터링이 길을 닦아 둔 것이다.
다음장으로 24장