18. xUnit으로 가는 첫걸음
xUnit?
- xUnit이란 유닛 테스트 프레임워크들을 통틀어서 칭하는 명칭을 말한다.
테스트 메서드 호출하기
test = WasRun("testMethod")
print(test.wasRun)
test.testMethod()
print(test.wasRun)
- 위의 테스트를 통과시키기 위해 WaasRun 객체를 생성하고 testMethod를 정의해준다.
- 초록 막대!
리팩토링
TestCase
class TestCase:
def __init__(self, name):
self.name = name
def run(self):
method = getattr(self, self.name)
method()
WasRun
class WasRun(TestCase):
def __init__(self, name):
self.wasRun = None
TestCase.__init__(self, name)
def testMethod(self):
self.wasRun = 1
- TestCase를 상위 클래스를 만들고 WasRun이 이를 상속받게 만들었다.
class TestCaseTest(TestCase):
def testRunning(self):
test = WasRun("testMethod")
assert(not test.wasRun)
test.run()
assert(test.wasRun)
TestCaseTest("testRunning").run()
- 리팩토링을 통해 테스트를 이렇게 재정의할수 있다.
19. 테이블 차리기
- 테스트를 작성하다보면 아래와 같은 패턴을 발견하게 될 것이다.
- 준비 (arrange) - 객체를 생성한다.
- 행동 (act) - 어떤 자극을 준다.
- 확인 (assert) - 결과를 검사한다.
- 위의 패턴이 서로 다른 스케일에서 반복된다면 테스트를 위해 새로운 객체를 얼마나 자주생성해야 하는가 하는 문제에 직면하게 된다. 이 때 다음 두 가지 제약이 상충한다.
- 성능 - 테스트가 될 수 있는 한 빨리 실행되길 원한다. 여러 테스트에서 같은 객체를 사용하면 객체 하나만 생성해서 모든 테스트가 이 객체를 쓰게 할 수 있다.
- 격리 - 테스트끼리 영향을 끼치지 않는다. 객체를 공유하는 상태에서 하나의 테스트가 공유 객체를 변경한다면 다음 테스트의 결과에도 영향을 미친다.
테스트 커플링
- 테스트 끼리 커플링을 두지 말 것!
- 예를 들어 테스트 B를 실행하기 전에 테스트 A를 실행하면 둘 다 제대로 작동하지만, A를 실행하기 전에 B를 실행하면 테스트가 실패할 수 있다.
setUp 호출하기
TestCase
def setUp(self):
pass
WasRun
def setUp(self):
self.wasRun = None
self.wasSetUp = 1
- TestCase에 setUp을 정의해주고 WasRun에서 setUp을 구현하자.
class TestCaseTest(TestCase):
def setUp(self):
self.test = WasRun("testMethod")
def testRunning(self):
test = WasRun("testMethod")
assert(not test.wasRun)
test.run()
assert(test.wasRun)
def testSetUp(self):
self.test.run()
assert(self.test.wasSetUp)
TestCaseTest("testRunning").run()
- setUp이 추가되면서 테스트도 위와 같이 단순화해서 정의할 수 있다.
- 두 경우(testRunning, testSetUp) 모두 인스턴스를 생성하는데, WasRun을 setUp에서 생성하고 테스트 메서드에서 그걸 사용할 수 있게 했다.
- 각 테스트 메서드는 깨끗한 새 TestCaseTest 인스턴스를 사용하므로 두 개의 테스트가 커플링 될 가능성은 없다.
20장. 뒷정리하기
- 가끔 setUp 메서드에서 외부 자원을 할당하는 경우가 있다. 테스트가 서로 독립적이길 바란다면 외부 자원을 할당 받은 테스트들은 작업을 마치기 전에 자원을 다시 반환할 필요가 있다.
- 테스트가 끝난 후 호출되는 tearDown 메서드를 만들면 어떨까?
WasRun에 로그 문자열 남기기
WasRun
def setUp(self):
self.wasRun = None
self.wasSetUp = 1
self.log = "setUp "
- 메서드의 호출 순서를 표현하기 위해 setUp 함수에 로그 변수를 초기화하게 해보자.
def testTemplateMethod(self):
test = WasRun("testMethod")
test.run()
assert("setUp testMethod " == test.log)
TearDown 호출하기
def testTemplateMethod(self):
test = WasRun("testMethod")
test.run()
assert("setUp testMethod tearDown " == test.log)
- 로그 테스트 코드의 단언에 tearDown을 추가해준다.
TestCase
def run(self):
self.setUp()
method = getattr(self, self.name)
method()
self.tearDown()
def tearDown(self):
pass
WasRun
def tearDown(self):
self.log = self.log + "tearDown "
21. 셈하기
수집한 결과를 출력하기
def testResult(self):
test = WasRun("testMethod")
result = test.run()
assert("1 run, 0 failed" == result.summary())
class TestResult:
def summary(self):
return "1 run, 0 failed"
- 테스트 결과를 표현한 TestResult 객체를 만들었다.
def run(self):
self.setUp()
method = getattr(self, self.name)
method()
self.tearDown()
return TestResult()
- run 함수에서 TestResult 객체를 반환하도록 구현했다.
리팩토링
TestResult
def testStarted(self):
self.runCount = self.runCount + 1
def summary(self):
return "%d run, 0 failed" % self.runCount
- 실행될 때 마다 카운트 해주는 testStarted 함수 정의
- summary에서 1로 하드코딩된 부분을 runCount를 받게 수정
TestCase
def run(self):
result = TestResult()
result.testStarted()
self.setUp()
method = getattr(self, self.name)
method()
self.tearDown()
return result
TestCaseTest
def testFailedResult(self):
test = WasRun("testBrokenMethod")
result = test.run()
assert ("1 run, 1 failed" == result.summary())
- 함수 호출이 실패하는 것에 대해 집계하는 기능이 없으므로 위와 같이 테스트를 작성했다.
WasRun
def testBrokenMethod(self):
raise Exception