코드 테스트하기

코드 테스트는 아주 중요합니다.

테스트 코드를 능숙하게 작성하고 이 코드를 패러랠하게 돌리는 것은 이제 좋은 습관으로 인정받고 있습니다. 이 방법을 현명하게 사용하면 코드의 의도를 보다 명확히하는데 좋을 뿐 아니라, 아키텍처의 결합도를 낮출 수 있습니다.

테스트의 일반 원칙 몇 가지:

  • 테스트 유닛은 각 기능의 가장 작은 단위에 집중하여, 해당 기능이 정확히 동작하는지를 증명해야 합니다.

  • 각 테스트 유닛은 반드시 독립적이어야 합니다. 각 테스트는 혼자서도 실행 가능해야하고, 테스트 슈트로도 실행 가능해야 합니다. 이 때, 호출되는 순서와 무관하게 잘 동작해야 합니다. 이 규칙이 뜻하는 바, 새로운 데이터셋으로 각각의 테스트를 로딩해야 하고, 그 실행 결과는 꼭 삭제해야합니다. 보통 setUp()tearDown() 메소드로 이런 작업을 합니다.

  • 테스트가 빠르게 돌 수 있도록 만들기 위해 노력해야 합니다. 테스트 하나가 실행하는데 몇 밀리세컨드 이상의 시간이 걸린다면, 개발 속도가 느려지거나 테스트가 충분히 자주 수행되지 못할 것입니다. 테스트에 필요한 데이터 구조가 너무 복잡하고, 테스트를 하려면 매번 이 복잡한 데이터를 불러와야 해서 테스트를 빠르게 만들 수 없는 경우도 있습니다. 이럴 때는 무거운 테스트는 따로 분리하여 별도의 테스트 슈트를 만들어 두고 스케쥴 작업을 걸어두면 됩니다. 그리고 그 외의 다른 모든 테스트는 필요한 만큼 자주 수행하면 됩니다.

  • 지금 사용하고 있는 툴이 개별 테스트나 테스트 케이스를 어떻게 수행하는지 배우셔야 합니다. 모듈 안에 들어있는 함수를 개발하고 있다면, 그 함수의 테스트를 자주, 가능하다면 코드를 저장할 때마다 자동으로 돌려야 합니다.

  • 그날의 코딩을 시작하기 전에 항상 풀 테스트 슈트를 돌려야 합니다. 끝난 후에도 마찬가지입니다. 이 작업은 당신이 다른 코드를 망가뜨리지 않았다는 더 큰 자신감을 심어줄 것입니다.

  • 모두가 공유하는 저장소에다가 코드를 집어넣기 전에 자동으로 모든 테스트를 수행하도록 하는 훅을 구현하는 것도 좋은 생각입니다.

  • 지금 한창 개발 중인데 그만두고 잠시 다른 일을 해야한다면, 다음에 개발할 부분에다가 일부러 고장난 유닛 테스트를 작성하는 것도 좋은 생각입니다.

  • 코드를 디버깅할 때 가장 먼저 시작할 일은 버그를 찝어내는 새로운 테스트를 작성하는 것입니다. 이런 일이 언제나 가능한 것은 아니지만, 이런 버그 잡이 테스트들이야말로 당신의 프로젝트에서 가장 가치있는 코드 조각이 될 것입니다.

  • 테스트 함수에는 길고 서술적인 이름을 사용하셔야 합니다. 테스트에서의 스타일 안내서는 짧은 이름을 보다 선호하는 다른 일반적인 코드와는 조금 다릅니다. 테스트 함수는 절대 직접 호출되지 않기 때문입니다. 실제로 돌아가는 코드에서는 square() 라든가 심지어 sqr() 조차도 괜찮습니다. 하지만 테스트 코드에서는 test_square_of_number_2(), test_square_negative_number() 같은 이름을 붙여야 합니다. 이런 함수명들은 테스트가 실패할 때나 보입니다. 그러니 반드시 가능한 한 서술적인 이름을 붙여야 합니다.

  • 무언가 잘못되었거나 뜯어고쳐야만 할 경우, 괜찮은 코드에 테스트 셋이 있다면 당신이나 다른 유지보수 담당자들은 오류를 수정하거나 프로그램의 동작을 수정할 때 필시 그 테스트 슈트에 전적으로 의지할 것입니다.

  • 테스트 코드의 또다른 사용 방법은 새로운 개발자들을 위한 안내서로 쓰는 방법입니다. 이미 만들어져 있는 코드에서 작업해야할 경우, 관련 테스트 코드를 돌려보고 읽어보는 것이야말로 가장 좋은 시작점일 경우가 많습니다. 이렇게 테스트 코드를 돌려보면 어느 지점이 문제인지, 수정하기 어려운 곳은 어디일지, 막다른 골목은 어디일지를 발견하게 됩니다. 몇 가지 기능을 추가해야 한다면 가장 먼저 해야할 일은, 그 새로운 기능이 아직 돌아가지 않음을 확인할 수 있는 테스트를 붙여넣는 것입니다.

기본

Unittest

unittest 는 파이썬 표준 라이브러리 중 아주 유용한 테스트 모듈입니다. JUnit/nUnit/CppUnit 시리즈와 같은 툴을 써본 사람이라면 unittest 의 API에도 익숙할 것입니다.

테스트 케이스를 만드려면 unittest.TestCase 를 상속받는 하위 클래스를 만들어야 합니다.

import unittest

def fun(x):
    return x + 1

class MyTest(unittest.TestCase):
    def test(self):
        self.assertEqual(fun(3), 4)

파이썬 2.7부터는 unittest도 자체적인 테스트 탐색 매커니즘이 생겼습니다.

Doctest

doctest 모듈은 독스트링 안에 대화형 파이썬 세션처럼 보이는 텍스트가 있는지를 검색한 후, 해당 세션들을 실행하여 텍스트에 써진대로 정확히 동작하는지를 확인합니다.

doctest는 다른 단위 테스트와는 사용 방법이 다릅니다: doctest는 일반적으로 상세하지 않고 특이한 케이스나 회귀 테스트에서의 버그를 잡아내지도 못합니다. 하지만 각 모듈과 그 컴포넌트의 주된 사용법을 알려주는 문서로써는 아주 유용합니다. doctest는 전체 테스트 슈트를 실행할 때마다 자동으로 돌려야 합니다.

함수에서 doctest를 돌리는 간단한 예시:

def square(x):
    """Return the square of x.

    >>> square(2)
    4
    >>> square(-2)
    4
    """

    return x * x

if __name__ == '__main__':
    import doctest
    doctest.testmod()

커맨드 라인에서 python module.py 를 쳐서 해당 모듈을 실행하면 doctest가 실행되고, 무언가 doctest에 기술한대로 동작하지 않는 경우에는 경고를 해줍니다.

도구

py.test

py.test는 보일러플레이트가 없는 파이썬 표준 unittest의 대체품입니다.

$ pip install pytest

모든 기능을 갖추지는 않았습니다. 확장 가능한 테스트 도구도 아닙니다. 하지만 단순한 신텍스를 자랑합니다. 테스트 슈트를 만드는 것은 함수 몇 개가 있는 모듈 하나를 작성하는 것 만큼이나 간단합니다.

# content of test_sample.py
def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5

그리고 명령창에서 py.test 를 실행하면 됩니다.

$ py.test
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.1
collecting ... collected 1 items

test_sample.py F

================================= FAILURES =================================
_______________________________ test_answer ________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================

unittest 모듈로 같은 기능을 구현하기 위해 필요한 것보다 훨씬 덜 작업해도 됩니다!

Nose

nose는 테스트를 쉽게 하도록 unittest를 확장한 것입니다.

$ pip install nose

nose는 자동으로 테스트를 발견합니다. 덕분에 수작업으로 테스트 슈트를 만드는 수고를 덜 수 있습니다. 또한 xUnit 호환 테스트 결과, 코드 커버리지 보고서, 선택 테스트와 같이 다양한 플러그인도 제공합니다.

tox

tox는 자동화된 테스트 환경 관리와 다양한 인터프리터 설정 하에서의 테스트를 위한 도구입니다.

$ pip install tox

tox는 간단한 ini 스타일의 설정 파일을 통해 복잡한 멀티 파라미터 테스트 메트릭을 설정할 수 있도록 해줍니다.

Unittest2

unittest2는 향상된 API와 단정문을 가지고 있는 파이썬 2.7의 unittest의 백포트 버전입니다. 2.7 이전 버전의 파이썬에서 사용 가능합니다.

파이썬 2.6이나 그 이전 버전을 사용하고 있다면 pip로 설치할 수 있습니다.

$ pip install unittest2

unittest라는 이름으로 모듈을 임포트하여 나중에 새로운 파이썬 버전의 모듈로 포팅하기 쉽도록 만들고 싶을 수도 있습니다.

import unittest2 as unittest

class MyTest(unittest.TestCase):
    ...

이 방법을 쓰면 나중에 새로운 버전의 파이썬으로 바꾸거나 unittest2 모듈이 더이상 필요하지 않더라도, 코드 수정 없이 테스트 모듈의 임포트를 변경할 수 있습니다.

mock

unittest.mock 은 파이썬의 테스트 라이브러리입니다. 파이썬 3.3부터는 표준 라이브러리 가 되었습니다.

예전 버전의 파이썬에서:

$ pip install mock

를 하면 mock 오브젝트로 시스템의 테스트 파트를 변경할 수 있습니다. 그리고 테스트가 어떻게 쓰일지에 대한 단정문을 만들 수 있습니다.

이렇게 메소드를 몽키 패치할 수도 있습니다:

from mock import MagicMock
thing = ProductionClass()
thing.method = MagicMock(return_value=3)
thing.method(3, 4, 5, key='value')

thing.method.assert_called_with(3, 4, 5, key='value')

테스트하는 모듈에서 모의 클래스나 모의 객체를 만들 경우에는 patch 데코레이터를 사용하세요. 아래의 예시에서는 언제나 같은 결과값을 내는(아무튼 테스트 중에는) 모의 객체가 외부 검색 시스템 하나 대신 쓰였습니다.

def mock_search(self):
    class MockSearchQuerySet(SearchQuerySet):
        def __iter__(self):
            return iter(["foo", "bar", "baz"])
    return MockSearchQuerySet()

# SearchForm here refers to the imported class reference in myapp,
# not where the SearchForm class itself is imported from
@mock.patch('myapp.SearchForm.search', mock_search)
def test_new_watchlist_activities(self):
    # get_search_results runs a search and iterates over the result
    self.assertEqual(len(myapp.get_search_results(q="fish")), 3)

mock은 다양하게 설정하여 동작 방법을 컨트롤 할 수 있습니다.