참조 자료
pytest 사용법
pytest는 xUnit 계열과는 다른 설계 철학을 가진 외부 라이브러리입니다. 하지만 unittest의 테스트 케이스와 호환되며, unittest로 작성된 스크립트를 pytest로 실행할 수 있습니다.
이러한 호환성은 대부분의 테스트 실행기에서 공통적인 설계 특징입니다. 예를 들어, 대부분의 테스트 실행기는 JUnit의 XML 형식 테스트 보고서를 지원하며, 일부는 기본 기능으로 제공하고 일부는 플러그인을 통해 구현합니다. 기존 도구와의 호환성은 신규 도구의 확산에 유리하므로, 유명한 테스트 실행기들은 설계 단계부터 호환성을 고려합니다.
테스트 실행기의 주요 기능 중 하나인 fixture 개념을 중심으로 pytest의 활용 방법을 설명하겠습니다.
작업1. pytest 설치
Python 패키지와 마찬가지로 pip install pytest 명령어로 설치합니다.
작업2. 공식 문서 확인
문제 발생 시 참고할 수 있는 공식 문서입니다.
테스트 파일명 규칙
pytest에서는 unittest처럼 클래스 상속 없이 테스트 메서드를 정의할 수 있습니다.
예시1. test_calculator.py, pytest의 예제입니다. 파일명에 주의하세요.
import pytest
def add_one(x):
return x + 1
def test_addition():
assert add_one(3) == 4
if __name__ == '__main__':
pytest.main()
이 예제를 분석해보겠습니다:
첫 번째 줄은 pytest 라이브러리를 임포트합니다.
두 번째~세 번째 줄은 입력 값을 1 증가시키는 add_one 함수를 정의합니다.
네 번째~다섯 번째 줄은 테스트 메서드를 정의하며, 테스트 메서드명은 test로 시작해야 합니다.
여섯 번째~일곱 번째 줄은 프로그램 진입점으로, 생략 가능합니다.
검증
pytest에서는 assert 문만으로 검증이 가능하며, self.assertXXXX와 같은 메서드는 사용하지 않습니다.
pytest는 다음과 같은 오류 정보를 제공합니다:
============================FAILURES============================
_______________________________ test_addition ________________________________
def test_addition():
> assert add_one(3) == 4
E assert 4 == 4
E + where 4 = add_one(3)
test_calculator.py:7: AssertionError
=================== 1 failed in 0.07 seconds=================
이전 예제에서 사용했던 세부 오류 정보 출력 방식도 동일하게 적용됩니다.
설정 및 정리
xUnit 계열 테스트 실행기에서 사용하는 설정(setup) 및 정리(teardown) 개념은 pytest에서도 지원됩니다.
예를 들어, 3개의 테스트 메서드가 온라인 쇼핑몰 장바구니를 테스트한다고 가정하면, 모든 테스트에 공통적으로 사용되는 사전 조건이 있을 수 있습니다. 이 경우 xUnit 계열 실행기는 다음과 같이 처리합니다:
테스트 세트(테스트 케이스 그룹) 내부 로직은 다음 순서로 진행됩니다:
세트 설정 ====>
케이스1 설정 ====> 케이스1 테스트 메서드 ====> 케이스1 정리 ====>
케이스2 설정 ====> 케이스2 테스트 메서드 ====> 케이스2 정리 ====>
케이스3 설정 ====> 케이스3 테스트 메서드 ====> 케이스3 정리 ====>
세트 정리
pytest에서는 위와 같은 전통적인 설정 및 정리 기능을 지원하며, 관심이 있는 독자는 공식 문서를 참조하시기 바랍니다:
https://docs.pytest.org/en/latest/xunit_setup.html#xunitsetup
본문에서는 pytest의 fixture 개념과 이를 이용한 설정/정리 기능에 대해 설명하겠습니다.
pytest의 Fixture
fixture란 무엇인가?
fixture는 테스트 메서드에 미리 준비된 객체를 제공하는 역할을 합니다.
예를 들어 웹 테스트를 수행할 때, 먼저 브라우저를 열어야 하며, 이후 모든 작업은 해당 브라우저에서 이루어집니다. fixture는 각 테스트 메서드에 대해 미리 준비된 브라우저 객체를 제공합니다.
또한 특정 테스트에서 Excel 파일을 읽어들이고, 모든 테스트 메서드가 해당 데이터를 사용해야 할 경우, fixture는 각 테스트 메서드에 대해 이미 읽힌 Excel 파일 객체를 제공합니다.
공식 문서 예제를 함께 살펴보겠습니다:
예시2. 공식 문서에서 제시한 fixture 예제
# conftest.py 내용
import pytest
import smtplib
@pytest.fixture(scope="module")
def smtp_client():
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
# test_module.py 내용
def test_ehlo(smtp_client):
response, msg = smtp_client.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
assert 0 # 예시용
def test_noop(smtp_client):
response, msg = smtp_client.noop()
assert response == 250
assert 0 # 예시용
이 예제는 conftest.py와 test_module.py 두 파일로 구성되어 있습니다.
conftest.py에서는 smtp_client라는 이름의 fixture를 정의하며, 이는 smtplib 라이브러리를 사용하여 Gmail 서버 연결을 생성합니다.
@pytest.fixture(scope="module") 어노테이션은 smtp_client 메서드가 module 범위의 fixture임을 나타냅니다. module 범위는 각 module당 한 번만 실행됩니다. 이 예제에서는 module내에 두 개의 테스트 메서드가 존재하므로:
smtp_client 메서드는 전체 테스트 실행 중 한 번만 실행됩니다. 즉, 전체 테스트 세트의 설정 메서드 역할을 합니다.
test_module.py에서는 두 개의 테스트 메서드를 정의하며, 모두 smtp_client 파라미터를 받습니다. 이 smtp_client는 conftest.py의 smtp_client 반환 값입니다.
공식 문서의 예제는 실행할 수 없지만, 전체 테스트 실행 과정을 함께 살펴보겠습니다:
- 모든 test_로 시작하는 파일을 테스트 스크립트 파일로 식별합니다.
- 테스트 스크립트 파일과 동일한 디렉토리에서 conftest.py 파일을 찾습니다.
- 임의의 순서로 테스트 스크립트 파일 내의 테스트 메서드를 실행합니다.
- 첫 번째 테스트 메서드를 실행하면서 smtp_client 파라미터를 발견합니다. 이 파라미터는 conftest.py의 smtp_client fixture를 찾아야 합니다.
- conftest.py의 smtp_client 메서드를 실행하여 반환 값을 저장합니다.
- 이 반환 값을 4단계의 테스트 메서드 파라미터에 대입하여 실행합니다.
- 두 번째 테스트 메서드를 실행하면서 다시 smtp_client 파라미터를 발견합니다. 이 파라미터는 conftest.py의 smtp_client fixture를 찾습니다.
- fixture 범위가 module이므로 중복 실행 없이 5단계의 반환 값을 계속 사용합니다.
위 과정을 통해 fixture는 설정 메서드 역할을 하며 더 유연하게 사용될 수 있습니다:
fixture의 scope 값을 module, class 또는 function으로 변경함으로써 각 메서드나 클래스에 맞춤형 fixture를 제공할 수 있습니다. fixture는 또한 정리 메서드도 정의할 수 있습니다.
예시3. 정리 메서드 추가한 예제
@pytest.fixture(scope="module")
def smtp_client():
yield smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
print("테스트 메서드 종료 후 실행되는 정리 메서드")
이 예제에서 return 문을 yield로 변경하고, yield 아래의 문장은 테스트 메서드 종료 후 실행됩니다. 즉, 정리 메서드 역할을 합니다.
실행 가능한 예제를 함께 살펴보겠습니다:
예시4. 메서드 단위 설정 및 정리 예제
# conftest.py 내용
import pytest
@pytest.fixture(scope="function", autouse=True)
def setup_fixture():
print("메서드 단위 설정")
yield 100
print("메서드 단위 정리")
# test_calculator.py 내용
import pytest
def add_one(x):
return x + 1
def test_addition():
assert add_one(3) == 4
def test_multiplication(setup_fixture):
print(setup_fixture)
assert add_one(98) == setup_fixture
if __name__ == '__main__':
pytest.main()
이 예제를 분석해보겠습니다:
conftest.py 파일에서,
두 번째 줄은 pytest.fixture 데코레이터를 사용하며, 이는 fixture의 범위가 메서드 단위(function)임을 나타냅니다. 즉, 각 메서드 실행 전후에 실행됩니다. autouse=True 매개변수는 fixture를 자동으로 사용하도록 설정하며, 테스트 메서드의 파라미터 목록에서 fixture 이름을 생략할 수 있습니다. 그러나 파라미터 목록에서 생략하면 fixture의 반환값을 사용할 수 없습니다. 따라서 일반적으로 자동 사용되는 fixture는 반환값이 없는 경우가 많습니다.
세 번째~여섯 번째 줄에서는 setup_fixture fixture를 정의하며, 반환값은 항상 100입니다. 반환값은 yield로 반환되며, yield 이후의 문장은 테스트 메서드 실행 후 실행됩니다.
이 예제의 실행 결과는 다음과 같습니다:
rootdir: C:\Users\colin.zt\Desktop\Homework_20180914\15.0, inifile:
collected 2 items
test_calculator.py FF
================ FAILURES==========================
________________________________ test_addition ________________________________
def test_addition():
> assert add_one(3) == 4
E assert 4 == 4
E + where 4 = add_one(3)
test_calculator.py:9: AssertionError
---------------------------- Captured stdout setup ----------------------------
메서드 단위 설정
-------------------------- Captured stdout teardown ---------------------------
메서드 단위 정리
________________________________ test_multiplication ________________________________
setup_fixture = 100
def test_multiplication(setup_fixture):
print(setup_fixture)
> assert add_one(98) == setup_fixture
E assert 99 == 100
E + where 99 = add_one(98)
test_calculator.py:14: AssertionError
---------------------------- Captured stdout setup ----------------------------
메서드 단위 설정
---------------------------- Captured stdout call -----------------------------
100
-------------------------- Captured stdout teardown ---------------------------
메서드 단위 정리
========== 2 failed in 0.08 seconds ================
설명할 점은 다음과 같습니다: Captured stdout setup과 teardown은 pytest가 포착한 설정 및 정리 로그이며, 이는 setup_fixture 메서드 내에서 출력한 내용입니다. 결과에서 두 번의 설정과 두 번의 정리 로그가 나타났는데, 이는 setup_fixture의 범위가 function이기 때문입니다. 두 개의 테스트 메서드가 있으므로 각각 실행 전후에 setup_fixture가 실행됩니다.
test_addition() 메서드는 setup_fixture를 명시적으로 전달하지 않았지만, autouse=True로 인해 메서드 실행 전후에 setup_fixture가 실행되었습니다.
test_multiplication(setup_fixture) 메서드는 setup_fixture를 명시적으로 전달했으므로, setup_fixture 실행 외에도 전달된 파라미터는 setup_fixture의 반환값인 100을 포함합니다.