테스트 클래스 및 메서드

완료됨

Pytest에서는 테스트 함수 작성 외에도 클래스를 사용할 수 있습니다. 앞서 언급했듯이 상속이 필요하지 않으며 테스트 클래스는 몇 가지 간단한 규칙을 따릅니다. 클래스를 사용하면 유연성과 재사용성이 향상됩니다. 다음에 보듯이 Pytest는 방해가 되지 않도록 하며 특정 방식으로 테스트를 작성하도록 강요하지 않습니다.

함수와 마찬가지로 assert 문을 사용하여 어설션을 작성할 수 있습니다.

테스트 클래스 빌드

실제 시나리오를 사용하여 테스트 클래스가 어떻게 도움이 되는지 알아보겠습니다. 다음 함수는 지정된 파일의 내용에 “yes”가 포함되어 있는지 확인합니다. 포함되어 있으면 True를 반환합니다. 파일이 없거나 파일 내용에 “no”가 포함되어 있으면 False를 반환합니다. 이 시나리오는 파일 시스템을 사용하여 완료를 나타내는 비동기 작업에서 일반적입니다.

함수의 형태는 다음과 같습니다.

import os

def is_done(path):
    if not os.path.exists(path):
        return False
    with open(path) as _f:
        contents = _f.read()
    if "yes" in contents.lower():
        return True
    elif "no" in contents.lower():
        return False

다음은 test_files.py라는 파일에 포함된 두 개의 테스트(각 조건에 대해 하나씩)가 있는 클래스입니다.

class TestIsDone:

    def test_yes(self):
        with open("/tmp/test_file", "w") as _f:
            _f.write("yes")
        assert is_done("/tmp/test_file") is True

    def test_no(self):
        with open("/tmp/test_file", "w") as _f:
            _f.write("no")
        assert is_done("/tmp/test_file") is False

주의

예제에 사용하기 더 쉽기 때문에 테스트 메서드는 임시 테스트 파일에 대해 /tmp 경로를 사용합니다. 그러나 임시 파일을 사용해야 하는 경우 파일을 안전하게 만들고 제거할 수 있는 tempfile 등의 라이브러리를 사용하는 것이 좋습니다. 모든 시스템에 /tmp 디렉터리가 있는 것은 아니며 해당 위치는 운영 체제에 따라 일시적이지 않을 수 있습니다.

세부 정보 표시를 늘리기 위해 -v 플래그를 사용하여 테스트를 실행하면 테스트 통과가 표시됩니다.

pytest -v test_files.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 
cachedir: .pytest_cache
rootdir: /private/
collected 2 items

test_files.py::TestIsDone::test_yes PASSED                               [ 50%]
test_files.py::TestIsDone::test_no PASSED                                [100%]

============================== 2 passed in 0.00s ===============================

테스트가 통과되고 있지만 반복적인 것처럼 보이고 테스트가 완료된 후에도 파일을 남깁니다. 이를 개선할 수 있는 방법을 알아보기 전에 다음 섹션에서 도우미 메서드를 살펴보겠습니다.

도우미 메서드

테스트 클래스에는 테스트 실행을 설정 및 해체하는 데 사용할 수 있는 몇 가지 방법이 있습니다. Pytest는 정의된 경우 자동으로 실행합니다. 이러한 메서드를 사용하려면 특정 순서와 동작이 있음을 알아야 합니다.

  • setup: 클래스의 각 테스트 전에 한 번 실행
  • teardown: 클래스의 각 테스트 후에 한 번 실행
  • setup_class: 클래스의 모든 테스트 전에 한 번 실행
  • teardown_class: 클래스의 모든 테스트 후에 한 번 실행

테스트에서 유사한(또는 동일한) 리소스가 작동해야 하는 경우 설치 메서드를 쓰는 데 유용합니다. 이상적으로는 테스트가 완료된 후 리소스가 남아 있으면 안 되므로 이러한 상황에서는 해체 메서드가 테스트 정리에 도움이 될 수 있습니다.

정리

각 테스트 후 파일을 정리하는 업데이트된 테스트 클래스를 살펴보겠습니다.

class TestIsDone:

    def teardown(self):
        if os.path.exists("/tmp/test_file"):
            os.remove("/tmp/test_file")

    def test_yes(self):
        with open("/tmp/test_file", "w") as _f:
            _f.write("yes")
        assert is_done("/tmp/test_file") is True

    def test_no(self):
        with open("/tmp/test_file", "w") as _f:
            _f.write("no")
        assert is_done("/tmp/test_file") is False

teardown() 메서드를 사용했으므로 이 테스트 클래스는 더 이상 /tmp/test_file을 남기지 않습니다.

설정

이 클래스에서 수행할 수 있는 또 다른 개선 사항은 파일을 가리키는 변수를 추가하는 것입니다. 이제 파일이 6개 위치에서 선언되었으므로 경로를 변경하면 해당 모든 위치에서 변경됩니다. 이 예제에서는 경로 변수를 선언하는 setup() 메서드를 추가한 클래스의 형태를 보여 줍니다.

class TestIsDone:

    def setup(self):
        self.tmp_file = "/tmp/test_file"

    def teardown(self):
        if os.path.exists(self.tmp_file):
            os.remove(self.tmp_file)

    def test_yes(self):
        with open(self.tmp_file, "w") as _f:
            _f.write("yes")
        assert is_done(self.tmp_file) is True

    def test_no(self):
        with open(self.tmp_file, "w") as _f:
            _f.write("no")
        assert is_done(self.tmp_file) is False

사용자 지정 도우미 메서드

클래스에서 사용자 지정 도우미 메서드를 만들 수 있습니다. 이러한 메서드는 이름 test를 접두사로 지정해서는 안 되며 설치 또는 정리 메서드로 이름을 지정할 수 없습니다. TestIsDone 클래스에서는 사용자 지정 도우미에서 임시 파일 만들기를 자동화할 수 있습니다. 해당 사용자 지정 도우미 메서드는 다음 예제와 같습니다.

    def write_tmp_file(self, content):
        with open(self.tmp_file, "w") as _f:
            _f.write(content)

Pytest는 write_tmp_file() 메서드를 자동으로 실행하지 않으며, 다른 메서드에서 이를 직접 호출하여 파일에 쓰는 것과 같은 반복적인 작업에 소요되는 시간을 줄일 수 있습니다.

사용자 지정 도우미를 사용하도록 테스트 메서드를 업데이트한 후 이 예제의 전체 클래스는 다음과 같이 표시됩니다.

class TestIsDone:

    def setup(self):
        self.tmp_file = "/tmp/test_file"

    def teardown(self):
        if os.path.exists(self.tmp_file):
            os.remove(self.tmp_file)

    def write_tmp_file(self, content):
        with open(self.tmp_file, "w") as _f:
            _f.write(content)

    def test_yes(self):
        self.write_tmp_file("yes")
        assert is_done(self.tmp_file) is True

    def test_no(self):
        self.write_tmp_file("no")
        assert is_done(self.tmp_file) is False

함수 대신 클래스를 사용해야 하는 경우

함수 대신 클래스를 사용해야 하는 시기에 대한 엄격한 규칙은 없습니다. 항상 작업 중인 현재 프로젝트 및 팀의 규칙을 따르는 것이 좋습니다. 다음은 클래스 사용 시기를 결정하는 데 도움이 되는 몇 가지 일반적인 질문입니다.

  • 테스트에 유사한 설정 또는 정리 도우미 코드가 필요한가요?
  • 테스트를 함께 그룹화하는 것이 논리적으로 괜찮나요?
  • 테스트 도구 모음에 일부 테스트가 있나요?
  • 일반적인 도우미 함수 집합을 사용할 경우 테스트에 도움이 되었나요?