해당 내용은 Datacamp의 Data engineering track을 정리했습니다.
Unit Testing for Data Science in Python의 chapter 3에 대한 내용입니다.
해당 포스팅에는 아래의 내용을 포함하고 있습니다.
- unit test 관리하기(클래스 형태)
- 원하는 부분만 테스트하기
- xfail, skipif 데코레이터
- Travis CI, Codecov
1. How to organize a growing set of tests?
이전 강의들을 통해서 다양한 unit test를 작성해봤습니다. 이러한 unit test를 체계적으로 유지하기 위한 전략이 필요합니다.
src 폴더 안에 이전 강의에서 작성했던 다양한 함수들을 포함하게 됩니다. 하지만, 이전에 작성했던 unit test를 활용하게 되면 어떤 함수가 언제끝나는 지 알 수 없습니다.
위처럼 이것을 class로 만든다면, 각각의 테스트들을 구분할 수 있습니다. 객체지향에 대한 자세한 내용은 이 강의에서 다루지는 않습니다.
2. Mastering test execution
tests 폴더 내부에는 data, features, models에 대한 unit test들이 존재하며, 위의 그림처럼 구성을 표현할 수 있습니다. 여기에 존재하는 모든 unit test를 한꺼번에 실행하기 위해서는 현재 디렉토리를 tests로 변경한 뒤에 pytest를 실행하면 됩니다. pytest에 의해 폴더 내부에 존재하는 unit test를 검색합니다. 검색할 때에는 test_로 시작하는 파일을 테스트 모듈로 인식합니다. 클래스의 경우에는 카멜케이스로 작성하기 때문에 Test로 시작하는 클래스를 식별합니다. 마지막으로 test_로 시작하는 function을 unit test로 인식합니다. 이렇게 unit test를 모두 인식한 뒤 실행합니다.
결과는 위에 보시는 것과 같습니다. 총 16개 test 중에 1개를 제외하고 모두 통과한 것을 볼 수 있습니다. 일반적으로 이 명령어는 CI Server에서 실행됩니다. 1개라도 실패하는 테스트가 있다면, 수정한 뒤에 다시 commit을 해야합니다. 그렇기 때문에 매번 동일하게 unit test를 전부 실행하는 것보다는 중간에 실패하면 멈추도록 하는 것이 시간과 자원을 아낄 수 있습니다. pytest -x를 통해서 실패한 테스트가 있는 경우 pytest를 멈추도록 합니다.
-x 플래그를 추가하여 돌린 결과는 위와 같습니다. test_on_tab_with_missing_value에서 멈춘 것을 확인할 수 있습니다.
물론, tests 파일의 일부만 테스트하려고 한다면, pytest 원하는 py파일명을 작성하면 원하는 py파일 내부의 unit test만 실행할 수 있습니다. pytest에 의해서 발견되는 모든 테스트 클래스 및 unit test에는 노드 ID가 할당됩니다. 테스트 클래스의 노드 ID는 테스트 모듈의 경로로 <path to test module>::<test class name>으로 주어집니다. unit test의 노드 ID는 <path to test module>::<test class name>::<unit test name>의 형식을 따릅니다.
그래서 특정 클래스의 테스트만 실행한다고 할 때, pytest data/test_preprocessing_helpers.py::TestRowToList로 실행할 수 있습니다. 특정 unit test만 실행할 때에도 위와 동일한 방법으로 실행할 수 있습니다. 추가적으로 -k 옵션을 활용할 수도 있습니다. pytest -k "TestRowToList"로 쓸 때에는 TestRowToList 클래스 내부의 테스트만 실행합니다. -k를 사용할 때에는 클래스 이름의 일부만 입력하는 것도 가능합니다. 추가적으로 논리 연산자를 통해서 추가하고 빼는 것도 가능합니다. pytest -k "TestSplit and not test_on_one_row"처럼 사용할 수 있습니다.
3. Expected failures and conditional skipping
훈련 데이터에 가장 적합한 모델을 반환하는 새로운 함수인 train_model()을 구현한다고 가정해 봅니다. TDD를 적용할 것이기 때문에 가장 먼저 테스트를 하는 TestTrainModel 클래스를 만들고 테스트를 추가합니다.
import pytest
class TestTrainModel(object):
def test_on_linear_data(self):
...
이렇게 테스트 클래스만 만들어 놓은 경우에는 CI 서버로부터 테스트 실패에 대한 경고를 얻게 됩니다. 아직 train_model()이 구현도 안되었기 때문이죠. 그래서 xfail 데코레이터를 활용해서 해당 테스트가 틀릴 것임을 미리 알려줄 수 있습니다.
import pytest
class TestTrainModel(object):
@pytest.mark.xfail
def test_on_linear_data(self):
...
이런 경우에는 해당 함수가 실패했으나, 이미 틀릴 것을 알고 있었기에 경고를 받지 않습니다.
특정 조건에서 fail이 뜨는 경우가 종종 있습니다. 예를 들면, python version에 따른 에러가 해당됩니다. 그런 경우에는 skipif라는 데코레이터를 통해 테스트 실행을 건너 뛰도록 할 수 있습니다. 위의 xfail과 동일하게 예외처리가 필요한 함수 위에 @pytest.mark.skipif(boolean_expression) 를 적어주면 됩니다. 만약 boolean_expression이 True면 테스트를 건너 뜁니다. python 정보에 대한 조건을 주기 위해서는 sys.version_info로 확인할 수 있고, 2.7버전보다 높아야 한다면 @pytest.mark.skipif(sys.version_info > (2, 7), reason="requires Python 2.7") 를 적어주시면 됩니다. 또한 reason을 통해서 건너 뛰는 이유를 추가할 수 있습니다.
테스트 결과에서 이유를 보고 싶을 때에는 -r flag를 추가할 수 있습니다. -r 뒤에는 다양한 문자가 올 수 있습니다. -rs는 건너 뛴 테스트를 표시합니다. 위에서 활용했던 xfail 또한 reason에 대한 정보를 추가할 수 있습니다. -rx는 xfail 테스트에 대한 이유를 출력합니다. -rsx는 xfail과 건너뛴 테스트에 대한 이유를 출력해줍니다. 물론, 클래스 전체에 대해 데코레이터를 적용할 수 있습니다.
4. Continuous integration and code coverage
일반적으로 Numpy와 같은 오픈 소스들은 codecov와 같은 배지를 통해 신뢰성을 보여주려고 합니다. 이번 강의에서는 Github에 이러한 배지를 구현하는 방법에 대해서 소개합니다. 배지는 Continuous Integration 서버를 사용합니다. 모든 테스트에 대해 통과 했다면, passing 배지를 얻을 수 있고, 실패한 경우에는 failing 배지를 얻게 됩니다.
이번 강의에서는 Travis CI를 활용하여 배지를 만들어봅니다. 가장 먼저 configuration file을 만들어야 합니다. 저장소 root 내부에 src와 tests 폴더와 .travis.yml 파일을 만듭니다.
.travis.yml 파일 안에는 언어, 파이썬 버전 정보, CI서버에 프로젝트 및 dependency를 설치하기 위한 명령 목록입니다. 스크립트 섹션에는 모든 것이 설치된 후 테스트를 실행하는 데 필요한 명령어를 작성합니다.
다 작성이 되었다면 해당 파일을 Github로 push합니다. 그리고나서 Github의 Marketplace에서 travis CI를 검색하고 앱을 설치합니다. 필요한 저장소(public repository)에 대한 앱 액세스를 허용합니다. 이제부터는 Github 저장소에 commit이 될 때마다 Travis CI 대시 보드에 빌드가 표시됩니다.
Code coverage는 테스트에 사용하는 전체 코드 라인 수를 application code의 전체라인 수로 나눈 값을 백분율로 표현합니다. percentage가 높을 수록 잘 테스트된 코드라는 것을 나타냅니다.
우선 .travis.yml 파일의 install 부분에 codecov을 추가합니다. 또한 pytest script 뒤에 --cov=src 라는 flag를 추가합니다. 이렇게 하면 커버리지 리포트도 생성합니다. 마지막으로 after_success라는 설정에 codecov 명령을 추가합니다. 이렇게 하면 Travis CI는 모든 빌드 후 코드 커버리지 결과를 Codecov에 보내줍니다. Codecov도 Travis CI와 동일하게 Marketplace에서 설치하면 됩니다.