파이썬 프로젝트 시작하기 - Nose

이 글은 프로젝트의 틀을 구성하는 방법에 관한 시리즈의 네 번째 글입니다. 앞선 포스트들의 목록은 다음과 같습니다.

오늘 다룰 내용은 Nose 를 사용한 테스트입니다.

파이썬의 테스트 지원

지금까지는 프로그램의 설치에 필요한 최소한의 요건을 갖추는데 초점을 맞췄습니다. 이제부터는 선택사항이라 볼 수도 있지만, 사실상 모든 프로젝트가 갖춰야 하는 다른 측면들을 살필 것입니다. 먼저 테스트입니다.

요리를 시작하기 전에 주어진 재료들부터 살펴봅시다.

파이썬의 표준 라이브러리에는 테스트 관련 모듈이나 패키지가 3개 있습니다. doctest, test, unittest 가 그 주인공들입니다.

doctest

doctest 는 Doctest 를 실행하는데 필요한 도구를 제공합니다. Doctest 는 파이썬의 docstring 에 파이썬의 대화형 세션처럼 보이는 일련의 텍스트를 포함시키는 것인데, doctest 는 이 텍스트를 실제로 실행시켜 결과와 비교해줍니다. 저희는 Doctest 자체를 작성하는 작업은 문서화 작업의 일환으로 보고 있기 때문에, 다른 글에서 따로 다루기로 하겠습니다.

test

test 는 파이썬 스스로를 테스트하기 위해 사용하는 패키지이고, 하위 패키지를 포함하고 있습니다. 지금 이 글이 다룰 내용은 아닙니다.

unittest

unittest 는 단위 테스트를 지원하기 위한 모듈로, 파이썬이 테스트와 관련하여 표준적으로 지원하는 가장 중요한 모듈입니다. 저희는 대부분의 테스트 케이스를 이 모듈을 사용하여 작성하고 있습니다. 하지만 이 모듈에도 한가지 문제가 있는데 파이썬 버전에 따른 호환성이 그 것입니다. 파이썬 2.7 에서 이 모듈은 기능이 대폭 개선되었습니다. 지금 저희가 지원하는 파이썬 버전은 2.6 에서 시작하기 때문에 문제가 됩니다.

unittest2

이 문제를 해결하기 위해 저희가 채택한 방법은 unittest2 라는 외부 패키지를 설치하는 것입니다. unittest2 는 파이썬 2.7 의 unittest 에 추가된 기능들을 그 이전의 버전들로 역 포팅한 것으로, unittest2 라는 이름의 모듈을 제공합니다.

Setuptools 의 테스트 지원

좀 의외지만 Distutils 은 테스트 관련 명령을 지원하지 않습니다. 대신 저희가 사용하는 Setuptools 에서는 test 명령이 지원됩니다. 이 명령과 관련된 setup 함수의 키워드 인자가 3개 있습니다.

  • test_loader
  • test_suite
  • tests_require

test_loader

Setuptools 가 사용하는 테스트 로더는 unittest 모듈의 TestLoader 입니다. 다른 로더로 바꾸고 싶다면 이 인자로 지정할 수 있습니다. 현재 저희는 사용하지 않고 있습니다.

test_suite

테스트 스위트를 지정하는 곳인데, setup.py test 명령을 사용하려면 반드시 지정해 줘야 합니다. 저희가 사용하는 값은 뒤에 나옵니다.

tests_require

setup.py test 명령을 수행하기 위해 설치되어야 하는 패키지들을 나열합니다. install_requires 에 나열된 패키지들과는 달리 완전히 설치되지는 않고, setup.py test 를 실행하는 동안에만 사용 가능합니다. 저희는 프로그램의 기본 동작에는 필요 없고, 테스트에만 필요한 패키지들을 이 곳에 모아둡니다. 앞서 언급한 unittest2 가 이 경우에 해당합니다.

Nose

테스트는 제품의 품질에 결정적인 영향을 주지만, 테스트에 사용하는 도구는 제품과 함께 배포될 필요가 없는 경우가 많기 때문에, 테스트용 도구를 선택할 때는 제품에 사용되는 라이브러리를 선택할 때에 비해 자유도가 높은 편입니다. 그래서 테스트 도구는 적극적으로 활용하는 편이 좋은데, 파이썬에서 널리 사용되는 도구 중 하나는 Nose 입니다. 저희는 모든 경우에 Nose 로 테스트가 동작할 수 있도록 구성합니다.

nose

Nose 를 Setuptools 와 연결하려면, nose 패키지를 설치하고 test_suite'nose.collector' 를 전달하면 됩니다.

nose 는 테스트를 할 때만 필요하기 때문에 tests_require 에 지정해 주는 것으로 충분합니다.

nosetests

test_require 대신 setup_require 에 지정하는 것을 선호하는 사람들도 있습니다. 주된 이유는 이렇게 할 경우 setup.py nosetests 라는 명령을 사용할 수 있기 때문입니다. 하지만 테스트가 필요 없을 때도 nose 가 설치되는 부작용이 있기 때문에 저희는 사용하지 않는 방법입니다. setup.py nosetests 명령은 뒤에서 설명할 다른 방법으로도 실행 가능합니다.

setup.py nosetests 명령은 setup.py test 에 비해, Nose 가 제공하는 모든 기능을 사용할 수 있다는 장점이 있습니다. setup.py test 명령은 Nose 의 모든 기능을 사용하지 못합니다. 하지만 Nose 가 제공하는 nosetests 콘솔 명령을 사용하면 Nose 이 모든 기능을 활용하는 것이 가능합니다. 테스트 도중 임시로 설치되는 형태로는 콘솔 명령을 사용할 수 없습니다. 이 때문에 저희는 tests_require 에 지정한 패키지들을 미리 설치할 수 있는 방법을 제공합니다.

extras_require

extras_require 라는 키워드 인자를 사용하면 test 라는 확장 기능을 정의할 수 있고, 이 기능이 필요로 하는 패키지를 지정할 수 있습니다. 물론 저희들은 이 패키지들을 tests_require 와 동일하게 유지합니다.

extras_require={
    'test': tests_require,
    },

이렇게 지정해두면, 이런 명령으로 테스트에 필요한 패키지들을 미리 설치할 수 있습니다.

pip install -e .[test]

Nose 를 미리 설치해두면 nosetests 콘솔 명령을 사용할 수 있음과 동시에, setup.py nosetests 명령도 사용할 수 있게 됩니다.

coverage

테스트를 모두 통과하는 것도 중요하지만, 충분히 촘촘한 테스트를 작성했는지를 확인하는 것도 중요합니다. 이 것을 테스트 커버리지라고 하는데, 구체적으로는 테스트를 수행하는 도중 실행된 코드와 실행되지 않은 코드를 확인하는 작업을 뜻합니다. 널리 쓰이는 패키지는 coverage 입니다. 이 패키지도 tests_require 에 등록해둡니다. Nose 역시 coverage 를 지원합니다.

setup.cfg

Nose 를 사용하려면 각종 파라미터를 지정해주어야 하는데, 표준적인 테스트 방법을 파일에 기술해 둘 수 있으면 좋습니다. 이 때 사용하는 파일이 setup.cfg 입니다. 예를 들면 이렇습니다.

[nosetests]
verbosity=2
with-coverage=1
cover-package=flowdas

이 파일을 setup.py 와 같은 디렉터리에 저장해두면 됩니다. setup.py nosetestsnosetests 모두 이 파일을 사용하게 됩니다.

with-coverage=1 은 커버리지를 조사하라는 뜻이고, cover-package=flowdas 는 그 대상을 flowdas 라는 패키지로만 한정하라는 의미입니다. 범위를 제한해주지 않으면 테스트 대상이 아닌 패키지들도 조사 대상에 포함돼버리는 경우가 흔합니다.

setup.py

테스트 지원을 추가한 setup.py 은 이렇게 됩니다.

import sys
from setuptools import setup, find_packages

setup_requires = [
    ]

install_requires = [
    'django==1.5.4',
    'markdown==2.3.1',
    'pyyaml==3.10',
    'pillow==2.1.0',
    'lxml==3.2.3',
    'beautifulsoup4==4.3.1',
    ]

tests_require = [
    'nose==1.3.0',
    'coverage==3.7',
    ]

if sys.version_info < (2, 7):
    tests_require.append('unittest2==0.5.1')

dependency_links = [
    ]

setup(
    name='Flowdas-Books',
    version='0.1',
    description='Flowdas Books',
    author='Flowdas',
    author_email='spammustdie@flowdas.com',
    packages=find_packages(),
    install_requires=install_requires,
    setup_requires=setup_requires,
    tests_require=tests_require,
    extras_require={
        'test': tests_require,
        },
    dependency_links=dependency_links,
    test_suite='nose.collector',
    scripts=['manage.py'],
    entry_points={
        'console_scripts': [
            'publish = flowdas.books.script:main',
            'scan = flowdas.books.script:main',
            'update = flowdas.books.script:main',
            ],
        },
    )

예전에 예시용으로 베타버전을 사용했던 장고는 안정한 버전으로 돌려두었습니다.