[Test] 테스트 주도 개발 (18 ~ 21장)

DaeHoon·2022년 11월 12일
0

TDD

목록 보기
5/9

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. 테이블 차리기

  • 테스트를 작성하다보면 아래와 같은 패턴을 발견하게 될 것이다.
  1. 준비 (arrange) - 객체를 생성한다.
  2. 행동 (act) - 어떤 자극을 준다.
  3. 확인 (assert) - 결과를 검사한다.
  • 위의 패턴이 서로 다른 스케일에서 반복된다면 테스트를 위해 새로운 객체를 얼마나 자주생성해야 하는가 하는 문제에 직면하게 된다. 이 때 다음 두 가지 제약이 상충한다.
  1. 성능 - 테스트가 될 수 있는 한 빨리 실행되길 원한다. 여러 테스트에서 같은 객체를 사용하면 객체 하나만 생성해서 모든 테스트가 이 객체를 쓰게 할 수 있다.
  2. 격리 - 테스트끼리 영향을 끼치지 않는다. 객체를 공유하는 상태에서 하나의 테스트가 공유 객체를 변경한다면 다음 테스트의 결과에도 영향을 미친다.

테스트 커플링

  • 테스트 끼리 커플링을 두지 말 것!
  • 예를 들어 테스트 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
  • 위와 같이 run 코드 수정
TestCaseTest
    def testFailedResult(self):
        test = WasRun("testBrokenMethod")
        result = test.run()
        assert ("1 run, 1 failed" == result.summary())
        
  • 함수 호출이 실패하는 것에 대해 집계하는 기능이 없으므로 위와 같이 테스트를 작성했다.
WasRun
    def testBrokenMethod(self):
        raise Exception
profile
평범한 백엔드 개발자

0개의 댓글