ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] Pytest Fixtures에 대한 가이드
    Python/Python 2024. 7. 2. 15:51
    반응형

    일단 해당글은 https://betterstack.com/community/guides/testing/pytest-fixtures-guide/를 읽고 나름 정리한 내용을 바탕으로 정리하는 것 에 목적이 있슴ㅁ_ㅁ

     

    A Complete Guide to Pytest Fixtures | Better Stack Community

    Learn how to use Pytest fixtures for writing maintainable and isolated tests.

    betterstack.com

    단위 테스트는 소프트웨어 품질에 필수적이지만 반복적인 설정과 데이터 복제로 인해 이를 번거롭게 만들 수 있다. Pytest픽스처는 이러한 문제에 대한 솔루션을 제공함. 

    Pytest Fixtures란?

    일반적 테스트 설정을 캡슐화하고 재사용함으로써 픽스처는 테스트 작업 흐름을 크게 향상시킴

    • 중복된 코드를 제거하고 테스트 설정 코드를 복사하여 붙여넣는 일을 없앤다
    • 중앙 위치에서 테스트 데이터를 업데이트하여 테스트 유지 관리를 단순하게 함
    • 테스트를 더욱 간결하고 이해하기 쉽게 만들어 가독성을 향상 시킴
    • 각 테스트가 독립적이고 일관되게 실행되도록 테스트 환경을 격리시킴

     

    실습

    실습하기 위해 기본적인 클래스와 일반적인 pytest를 작성해봄

    tree

    ├── app.py
    ├── tests
    │   ├── __init__.py
    │   ├── test_library.py

    app.py
    class Library:
        def __init__(self):
            self.books = []
    
        def add_book(self, title, author):
            self.books.append({"title": title, "author": author})
            return "Book added successfully"
    
        def get_book(self, index):
            if 0 <= index < len(self.books):
                book = self.books[index]
                return f"Title: {book['title']}, Author: {book['author']}"
            else:
                return "Index out of range"
    
        def update_book(self, index, title, author):
            if 0 <= index < len(self.books):
                self.books[index]["title"] = title
                self.books[index]["author"] = author
                return "Book updated successfully"
            else:
                return "Index out of range"
    
        def list_books(self):
            if not self.books:
                return "No books in the library"
            return "\n".join(
                f"Title: {book['title']}, Author: {book['author']}" for book in self.books
            )
    
        def clear_books(self):
            self.books = []
    
    # Example usage:
    if __name__ == "__main__":
        library = Library()
        print(library.add_book("1984", "George Orwell"))
        print(library.add_book("To Kill a Mockingbird", "Harper Lee"))
        print(library.update_book(0, "Nineteen Eighty-Four", "George Orwell"))
        print(library.list_books())

    라이브러리 CRUD에 관한 클래스다.

    tests/test_library.py
    from app import Library  
    
    
    def test_add_book():
        library = Library()
    
        library.add_book("The Great Gatsby", "F. Scott Fitzgerald")
        assert library.books == [
            {"title": "The Great Gatsby", "author": "F. Scott Fitzgerald"}
        ]
    
    
    def test_get_book():
        library = Library()
        library.add_book("1984", "George Orwell")
        assert library.get_book(0) == "Title: 1984, Author: George Orwell"
    
    
    def test_update_book():
        library = Library()
        library.add_book("1984", "George Orwell")
        library.update_book(0, "The Catcher in the Rye", "J.D. Salinger")
    
        assert library.books[0] == {
            "title": "The Catcher in the Rye",
            "author": "J.D. Salinger",
        }
    
    
    
    def test_list_books():
        library = Library()
        library.add_book("To Kill a Mockingbird", "Harper Lee")
        library.add_book("1984", "George Orwell")
        assert library.list_books() == (
            "Title: To Kill a Mockingbird, Author: Harper Lee\n"
            "Title: 1984, Author: George Orwell"
        )

     

    위 클래스에 대한 CRUD 테스트이다.

    pytest -v
    이러한 결과물이 나온다.

     

    collected 4 items
    
    tests/test_library.py::test_add_book PASSED                             [ 25%]
    tests/test_library.py::test_get_book PASSED                             [ 50%]
    tests/test_library.py::test_update_book PASSED                          [ 75%]
    tests/test_library.py::test_list_books PASSED                           [100%]
    
    ============================== 4 passed in 0.02s ===============================
     

    파이테스트 픽스처에 대한 4단계 프로세스

    • Arrange:  테스트에 필요한 서비스, 데이터베이스 레코드 ,URL 자격증명 등을 조건을 설정하여 테스트환경을 준비
    • Act: 함수 호출과 같이 테스트하려는 작업을 수행
    • Assert: 작업 결과가 기대와 일치하는지 확인
    • Cleanup: 다른 테스트에 영향을 주지 않도록 환경을 복원

     

    Arrange단계에대한 픽처 설계 Pytest에게 데코레이터를 통해 픽스처임을 알려줄 수 있다.

    pytest fixtures에 대해서 사용해보자.

    import pytest
    from app import Library
    
    
    @pytest.fixture
    def library():
        return Library()
    
    
    @pytest.fixture
    def library_with_books():
        library = Library()
        library.add_book("To Kill a Mockingbird", "Harper Lee")
        library.add_book("1984", "George Orwell")
        return library
    
    
    def test_add_book(library):
        library.add_book("The Great Gatsby", "F. Scott Fitzgerald")
        assert library.books == [
            {"title": "The Great Gatsby", "author": "F. Scott Fitzgerald"}
        ]
    
    
    def test_get_book(library):
        library.add_book("1984", "George Orwell")
        assert library.get_book(0) == "Title: 1984, Author: George Orwell"
    
    
    def test_update_book(library_with_books):
        library_with_books.update_book(0, "The Catcher in the Rye", "J.D. Salinger")
    
        assert library_with_books.books[0] == {
            "title": "The Catcher in the Rye",
            "author": "J.D. Salinger",
        }
    
    
    
    def test_list_books(library_with_books):
        assert library_with_books.list_books() == (
            "Title: To Kill a Mockingbird, Author: Harper Lee\n"
            "Title: 1984, Author: George Orwell"
        )

    자 pytest-fixtures로 적용했을 때 이다. 기존테스트 코드에서 library변수를 fixture로 함수 바깥으로 뺐따. 그래서 test_add_book 메서드나 test_get_book 메서드는 Library() 클래스를 따로 호출하지 않고 픽스처로 된 library를 변수로 받게 하고 바로 실행하여 코드가 2줄씩 줄어들었다.

    또한 test_update_book 메서드와 test_list_books 메서드는 library_with_books 라는 픽스처를 만들어  add_book 하는 과정을 줄 일 뿐만 아니라 독립적인 환경을 구축하여서 테스트를 실행할 수 있게 되었다.

    pytest -v
     
    collected 4 items
    
    tests/test_library.py::test_add_book PASSED                             [ 25%]
    tests/test_library.py::test_get_book PASSED                             [ 50%]
    tests/test_library.py::test_update_book PASSED                          [ 75%]
    tests/test_library.py::test_list_books PASSED                           [100%]
    
    ============================== 4 passed in 0.02s ===============================
    픽스처로 정리했을 때 도 동일한 결과가 나오게 된다.
     

    픽스처가 또 픽스처로 부터 할당 받을 수 있나?
    가능하다. 아래처럼 변경하여도 결과 자체는 동일하게 나온다.

    @pytest.fixture
    def library():
        return Library()
    
    
    @pytest.fixture
    def library_with_books(library):
        library.add_book("To Kill a Mockingbird", "Harper Lee")
        library.add_book("1984", "George Orwell")
        return library

     

    하나의 test_파일에 픽스처를 저렇게 작성하여도 어차피 결과적으로는 코드에 중복이 불가피한 것 이 아닌가 이를 효율적으로 관리하는 방법은 무엇일까?

    픽스처를 다른 파일에 작성하여서 거기서 자동으로 할당받게 하는 방법이다. 이를 위해서 conftest.py파일을 작성한다.

     

    tests/conftest.py
    import pytest
    from app import Library
    
    
    @pytest.fixture
    def library():
        return Library()
    
    
    @pytest.fixture
    def library_with_books(library):
        library.add_book("To Kill a Mockingbird", "Harper Lee")
        library.add_book("1984", "George Orwell")
        yield library
        # Teardown code
        library.clear_books()

    위에 작성했던 pytest 픽스처를 conftest에 작성한 예시이다. 

    그 후 test_library에 있는 pytest픽스처를 지워도 결과는 동일하게 나온다.

     

    Pytest 픽스처에 범위 

    Pytest에서 픽스처는 테스트에 필요한 리소스를 설정한다 픽스처가 작동하는 기간과 설치 및 다운되는 시기를 아라보자.순서대로 낮은순서부터 높은 범위다.

    • Function Scope: 기본 범위이며 픽스처가 각 테스트 기능 이전에 설정되고 테스트 기능이 종료된 후에 해체됨
    • Class Scope: 클래스당 한 번씩 설계되며 클래스 내의 모든 테스트 방법에서 사용 됨.
    • Module Scope: 픽스처는 테스트 모듈당 한 번 설정되며 해당 모듈 내의 모든 테스트 기능에 사용할 수 있음
    • Package Scope: 패키지당 한번 설정되며 패키지내의 모든 테스트에서 사용 가능.
    • Session Scope: 전체 테스트 세션마다 한 번씩 설정되며 해당 세션 동안 실행되는 모든 테스트에서 사용
    conftest.py
    import pytest
    from app import Library
    
    
    @pytest.fixture
    def library():
        lib = Library()
        lib.add_book("To Kill a Mockingbird", "Harper Lee")
        lib.add_book("1984", "George Orwell")
        yield lib
        # Teardown code
        lib.clear_books()
    
    
    @pytest.fixture
    def library_with_books(library):
        library.add_book("To Kill a Mockingbird", "Harper Lee")
        library.add_book("1984", "George Orwell")
        yield library
        # Teardown code
        library.clear_books()
    test_library.py
    def test_add_book(library):
        library.add_book("The Great Gatsby", "F. Scott Fitzgerald")
        expected_books = [
            {"title": "The Great Gatsby", "author": "F. Scott Fitzgerald"},
            {"title": "To Kill a Mockingbird", "author": "Harper Lee"},
            {"title": "1984", "author": "George Orwell"},
        ]
        assert sorted(library.books, key=lambda x: x["title"]) == sorted(
            expected_books, key=lambda x: x["title"]
        )
    
    
    def test_get_book(library):
        library.add_book("1984", "George Orwell")
        assert library.get_book(2) == "Title: 1984, Author: George Orwell"
    
    
    def test_update_book(library):
        library.update_book(0, "The Catcher in the Rye", "J.D. Salinger")
    
        assert library.books[0] == {
            "title": "The Catcher in the Rye",
            "author": "J.D. Salinger",
        }
    
    
    def test_list_books(library):
        assert library.list_books() == (
            "Title: To Kill a Mockingbird, Author: Harper Lee\n"
            "Title: 1984, Author: George Orwell"
        )

    파일을 변경한다.

    코드를 변경함으로써 library에 픽스처가 올바르게 추가되었는지 확인하기 위해 테스트에서는 기존 책과 새로운 책이 모두 있는지 확인하는 역할을 한다.

    pytest -v 를 사용하면 결과는 통과로 나온다.

     

    이제 스코프(범위)의 차이를 이해하기 위해서 테스트할 것이다.

    test_library_operations.py
    def test_add_book(library):
        library.add_book("The Great Gatsby", "F. Scott Fitzgerald")
        expected_books = [
            {"title": "The Great Gatsby", "author": "F. Scott Fitzgerald"},
            {"title": "To Kill a Mockingbird", "author": "Harper Lee"},
            {"title": "1984", "author": "George Orwell"},
        ]
        assert sorted(library.books, key=lambda x: x["title"]) == sorted(
            expected_books, key=lambda x: x["title"]
        )
    
    
    def test_get_book(library):
        library.add_book("1984", "George Orwell")
        assert library.get_book(2) == "Title: 1984, Author: George Orwell"

    test_library_management.py

    def test_update_book(library):
        library.update_book(0, "The Catcher in the Rye", "J.D. Salinger")
    
        assert library.books[0] == {
            "title": "The Catcher in the Rye",
            "author": "J.D. Salinger",
        }
    
    
    def test_list_books(library):
        assert library.list_books() == (
            "Title: To Kill a Mockingbird, Author: Harper Lee\n"
            "Title: 1984, Author: George Orwell"
        )

    두 개의 파일을 새로 만들고

    test_library.py

    파일을 제거한다.

    자 이제. pytest 픽스처에 대한 범위(Socpe)를 아라보자

    Function Scope는 기본 설정값으로 다른 설정은 따로 안해줘도 된다.

    조금 더 자세한 로그를 보기위해 pytest -v --setup-show 커맨드에 옵션을 준다.

    tests/test_library_management.py::test_update_book
            SETUP    F library
            tests/test_library_management.py::test_update_book (fixtures used: library)PASSED
            TEARDOWN F library
    tests/test_library_management.py::test_list_books
            SETUP    F library
            tests/test_library_management.py::test_list_books (fixtures used: library)PASSED
            TEARDOWN F library
    tests/test_library_operations.py::test_add_book
            SETUP    F library
            tests/test_library_operations.py::test_add_book (fixtures used: library)PASSED
            TEARDOWN F library
    tests/test_library_operations.py::test_get_book
            SETUP    F library
            tests/test_library_operations.py::test_get_book (fixtures used: library)PASSED
            TEARDOWN F library

    SETUP F library 그리고 TEARDOWN F library 라는 구문이 종종 보인다.
    이는 테스트가 생기고 소멸됨에 따라 fresh한 상태를 보장한다는 것을 나타낸다 또한 여기서 F 는 Function Scope에 약자 이므로 이것은 기본 함수 스코프를 의미한다. 이는 테스트 기능 별로 픽스처가 적용됨을 의미한다.

    MODULE SCOPE

     

    pytest를 통해 다음을 정의할 수 있습니다. module 스코프는 모듈 내의 테스트에서 픽스처를 공유할 수 있도록 하여 일관된 상태를 보장함, 이는 픽스처가 한 번 인스턴스화되어 모듈에서 테스트 코드를 실행하는 동안 지속됨을 의미하며 마지막 테스트가 실행되고 나면 픽스처가 소멸된다.

    이 스코프는 다음과 같은 시나리오에 적합하다.

    • 설정 또는 해체 프로세스에는 데이터베이스 연결 또는 모듈별 구성 로드와 같은 리소스 집약접인 작업
    • 인스턴스가 공유 픽스처 장치가 필요한 모든 테스트에 재사용되므로 반복적인 설정과 소멸을 줄 일 수 있어 성능이 향상됨
    import pytest
    from app import Library
    
    
    @pytest.fixture(scope="module")
    def library():
        lib = Library()
        lib.add_book("To Kill a Mockingbird", "Harper Lee")
        lib.add_book("1984", "George Orwell")
        yield lib
        # Teardown code
        lib.clear_books()
    
    
    @pytest.fixture
    def library_with_books(library):
        library.add_book("To Kill a Mockingbird", "Harper Lee")
        library.add_book("1984", "George Orwell")
        yield library
        # Teardown code
        library.clear_books()
    pytest -v --setup-show
    tests/test_library_management.py::test_update_book
        SETUP    M library
            tests/test_library_management.py::test_update_book (fixtures used: library)PASSED
    tests/test_library_management.py::test_list_books
            tests/test_library_management.py::test_list_books (fixtures used: library)FAILED
        TEARDOWN M library
    tests/test_library_operations.py::test_add_book
        SETUP    M library
            tests/test_library_operations.py::test_add_book (fixtures used: library)PASSED
    tests/test_library_operations.py::test_get_book
            tests/test_library_operations.py::test_get_book (fixtures used: library)FAILED
        TEARDOWN M library
    
    ========================================================= FAILURES ========================================================== 
    ______________________________________________________ test_list_books ______________________________________________________ 
    
    library = <app.Library object at 0x00000166327D1690>
    
        def test_list_books(library):
    >       assert library.list_books() == (
                "Title: To Kill a Mockingbird, Author: Harper Lee\n"
                "Title: 1984, Author: George Orwell"
            )
    E       AssertionError: assert 'Title: The C...George Orwell' == 'Title: To Ki...George Orwell'
    E         - Title: To Kill a Mockingbird, Author: Harper Lee
    E         + Title: The Catcher in the Rye, Author: J.D. Salinger
    E           Title: 1984, Author: George Orwell
    
    tests\test_library_management.py:11: AssertionError
    _______________________________________________________ test_get_book _______________________________________________________ 
    
    library = <app.Library object at 0x000001663282A990>
    
        def test_get_book(library):
            library.add_book("1984", "George Orwell")
    >       assert library.get_book(2) == "Title: 1984, Author: George Orwell"
    E       AssertionError: assert 'Title: The G...tt Fitzgerald' == 'Title: 1984,...George Orwell'
    E         - Title: 1984, Author: George Orwell
    E         + Title: The Great Gatsby, Author: F. Scott Fitzgerald
    
    tests\test_library_operations.py:15: AssertionError
    ================================================== short test summary info ================================================== 
    FAILED tests/test_library_management.py::test_list_books - AssertionError: assert 'Title: The C...George Orwell' == 'Title: To Ki...George Orwell'
    FAILED tests/test_library_operations.py::test_get_book - AssertionError: assert 'Title: The G...tt Fitzgerald' == 'Title: 1984,...George Orwell'
    ================================================ 2 failed, 2 passed in 0.19s ================================================

    pytest 를 통해 로그를 출력해보면 2개는 성공했고 2개는 실패했다. 일단  Function Module 과 마찬가지로 SETUP M libraryTEARDOWN M library 이는 모듈의 모든 테스트가 실행되기 전에 픽스처를 한 번 설치하고 모듈의 모든 테스트가 완료된 후 소멸한다는 것을 의미한다. 이를 통해 각 테스트에 대해 라이브러리를 생성하고 해체하는 오버헤드를 줄여 보다 효율적인 설정을 제공하는 동시에 서로 다른 테스트 파일 간의 격리를 보장함

    그러나 픽스처는 모듈 내에서 공유되므로 한 테스트에서 픽스처 상태에 대한 변경 사항이 후속 테스트에 영향을 줄 수 있음, 이 때문에 픽스처가 깨끗한 상태에서 시작되지만 그렇지 않다고 가정하기 때문에 일부 테스트가 실패 할 수 있다.

     

    따라서, test_update_book가 먼저 실행되어서 후속 결과에 영향을 주므로 test_list_book 메서드 테스트에서 실패했다. 마찬가지 이유로 test_add_book이 후속 결과에 영향을 미쳐 test_get_book 메서드 테스트가 실패했다.

     

    PackageScope

    패키지 스코프는 여러모듈에 걸쳐 픽스처를 공유하는데 유용함,  __init__.py file 및 해당 패키지 내의 모든 하위 디렉터리에서 공유 되며 마지막 테스트가 중단되는 동안 픽스처가 파괴됨

    실습

    test/conftest.py
    import pytest
    from app import Library
    
    
    @pytest.fixture(scope="package")
    def library():
        lib = Library()
        lib.add_book("To Kill a Mockingbird", "Harper Lee")
        lib.add_book("1984", "George Orwell")
        yield lib
        # Teardown code
        lib.clear_books()
    
    
    @pytest.fixture()
    def library_with_books(library):
        library.add_book("To Kill a Mockingbird", "Harper Lee")
        library.add_book("1984", "George Orwell")
        yield library
        # Teardown code
        library.clear_books()
    pytest -v --setup-show

    결과를 출력하면

     

    collected 4 items
    
    tests/test_library_management.py::test_update_book
      SETUP    P library
            tests/test_library_management.py::test_update_book (fixtures used: library)PASSED
    tests/test_library_management.py::test_list_books
            tests/test_library_management.py::test_list_books (fixtures used: library)FAILED
    tests/test_library_operations.py::test_add_book
            tests/test_library_operations.py::test_add_book (fixtures used: library)FAILED
    tests/test_library_operations.py::test_get_book
            tests/test_library_operations.py::test_get_book (fixtures used: library)FAILED
      TEARDOWN P library

     

    로그를 확인해보면 시작부분( SETUP P  library)에서 한 번 설정된 것을 볼 수 있음, 그리고 마지막에 TEARDOWN P library 로 소멸됨, 결과적으로 일단. test_update_book 픽스처가 업데이트 됨 모듈 픽스처와 마찬가지로 마찬가지로 앞에 결과값이 뒤에 결과 값에 영향을 미침 ,  다른 모든 테스트는 픽스처가 깨끗하게  슬레이트될 것으로 작성 됨 

     

    SessionScope

    Session 스코프는 동일한 테스트 설정을 공유하기를 원할 때 도움이 될 수 있는 또 다른 스코프임, Session 스코프 픽스처는 테스트 실행 초기에 생성되며 모든 테스트가 끝나면 파괴 됨, 이러한 기능은 전체 테스트 세션동안 지속되며 보통 데이터베이스 연결 같은 비싼 리소스를 설정하는 픽스처에 유용함

    tests/conftest.py
    import pytest
    from app import Library
    
    
    @pytest.fixture(scope="session")
    def library():
        lib = Library()
        lib.add_book("To Kill a Mockingbird", "Harper Lee")
        lib.add_book("1984", "George Orwell")
        yield lib
        # Teardown code
        lib.clear_books()
    
    
    @pytest.fixture()
    def library_with_books(library):
        library.add_book("To Kill a Mockingbird", "Harper Lee")
        library.add_book("1984", "George Orwell")
        yield library
        # Teardown code
        library.clear_books()

    로그를 출력하면, 

    collected 4 items
    
    tests/test_library_management.py::test_update_book
    SETUP    S library
            tests/test_library_management.py::test_update_book (fixtures used: library)PASSED
    tests/test_library_management.py::test_list_books
            tests/test_library_management.py::test_list_books (fixtures used: library)FAILED
    tests/test_library_operations.py::test_add_book
            tests/test_library_operations.py::test_add_book (fixtures used: library)FAILED
    tests/test_library_operations.py::test_get_book
            tests/test_library_operations.py::test_get_book (fixtures used: library)FAILED
    TEARDOWN S library
    ...
    
    =========================================================================================== 3 failed, 1 passed in 0.04s ===========================================================================================

    패키지 스코프나 모듈 스코프와 같이 실패한다 이 또한 마찬가지로 선행결과가 후속 결과에 영향을 미치는것을 알 수 있음

    세션 스코프는 library 픽스처에서는 전체 테스트 세션이 시작 될 때 한번 설정 되며 모든 테스트가 완료 된 후 해체 됨 이 스코프는 관련된 테스트 패키지 또는 모듈의 수에 관계없이 전체 테스트동안 한 번만 수행하므로 설정 및 해체 작업을 최소화하는데 효율적임

     

    이 외에도  다른 범위는 ClassScope 도 존재한다, class 스코프는 각 테스트 기능 후에 소멸되지 않고 테스트 클래스의 모든 메서드에 대해 한 번 픽스처를 설정하려는 경우에 유용함, 모든 메서드에서 재사용할 수 있으므로 특히 테스트가 분리되어 있거나  설정에 자주 설정 및 해체해야 하는 비싼 것이 포함되어 있는 경우 효율적임

     

    픽스처에 변수화(Parametrizing fixtures)

    Python에서는 픽스처를 사용하는 메서드에 매개변수화 하여 간결하고 읽기 쉬운 테스트를 작성할 수 있음 이러한 픽스처를 사용하는 테스트 메서드는 여러번 호출되며 매번 다른 인수를 사용하여 실행 됨, 이 접근 방식은 다양한 데이터베이스의 연결 값, 여러 파일 등을 다룰 때 유용함. 

    매개 변수화를 사용하려면 다음을 통과함, params 다음과 같은 입력 값의 집합을 사용하여 픽스처 데코레이터에 대한 키워드 인수로 사용 함

    실습

    새 파일을 작성한다.

    tests/test_parametrization.py
    import pytest
    
    @pytest.fixture(params=["image_1.jpg", "document_1.pdf", "image_2.png", "image_3.jpeg"])
    def original_file_path(request):
        return request.param
    
    def convert_to_hyphens(file_path):
        return file_path.replace("_", "-")
    
    def test_convert_to_hyphens(original_file_path):
        converted_file_path = convert_to_hyphens(original_file_path)
        assert "-" in converted_file_path

    original_file_path()메서드에서는  픽스처에 대하여 다양한 파일 경로를 제공하고 있으며 params를 리스트로 받아서 사용 함 test_convert_to_hyphens() 메서드는  픽스처에 따라 달라지며 목록의 각 파일 경로에 대하여 한 번씩 여러번 실행 되며 _ 를 - 문자로 바꾸어 테스트가 진행 됨

    픽스처에 내장 메서드 (Using built-in fixtures)

    파이테스트는 일반적인 테스트 시나리오를 다루는 내장형 픽스처와 함께 제 공 됨 이러한  픽스처는 보일러 플레이트(boilerplate: 변경 없이[최소한 수정] 계속하여 재사용할 수 있는 코드 코드를 줄이고 표준 기능을 통해 일관성을  유지 함

    내장 메서드 일부는 다음과 같다.

    • monkeypath: Functions, classes dictionaries, 등 을 일시적으로 수정함
    • request: 픽스처를 요청하는 테스트 기능에 대한 정보를 제공함
    • tmpdir: 각 테스트 메서드에 고유한 임시 디렉터리 경로 개체를 반환
    • tmppathfactory: 공통 기본 임시 디렉터리 아래의 임시 디랙터리를 반환
    • recwarn: 테스트 기능에서 발생한 경고를 기록함
    • capsys: sysdout, sysderr에 대한 쓰기를 캡처

    tmp_path 픽스처는 임시 디렉터리를 관리하는데 필수적이므로 실제 디렉터리와의 작업을 피할 수 있으며 다양한 OS에서 서로 다른 파일 경로로 인해 복잡해 질 수 있음을 방지함

    실습

    tests/test_builtin_fixtures.py
    def test_create_and_verify_temp_file(tmp_path):
        # Create a temporary directory within tmp_path
        temporary_directory = tmp_path / "example_temp_dir"
        temporary_directory.mkdir()
    
        # Create a file inside the temporary directory
        temporary_file = temporary_directory / "example_file.txt"
        temporary_file.write_text("Temporary file content")
    
        # Verify that the file exists
        assert temporary_file.is_file()
    
        # Verify the file's contents
        assert temporary_file.read_text() == "Temporary file content"

    이 코드는 임시 디렉터리와 파일을 만든다. tmp_path 픽스처 파일의 존재와 내용을 확인하여 테스트 환경이 서로 다른 OS에서 격리되고 일관성있는지 확인 함,

    예시) 만약에 내장 매서드 (픽스처)를 사용하지 않고 수동으로 하면 다음과 같이 복잡해진다. 

    import tempfile
    import os
    import pytest
    
    
    def create_and_verify_temp_file(temp_dir):
        # Create a file inside the temporary directory
        temp_file_path = os.path.join(temp_dir, "example_file.txt")
    
        with open(temp_file_path, "w") as temp_file:
            temp_file.write("Temporary file content")
    
        # Verify that the file exists
        assert os.path.isfile(temp_file_path)
    
        # Verify the file's contents
        with open(temp_file_path, "r") as temp_file:
            content = temp_file.read()
            assert content == "Temporary file content"
    
    
    def test_create_and_verify_temp_file_without_fixture():
        # Create a temporary directory
        with tempfile.TemporaryDirectory() as temp_dir:
            # Call the function to create and verify the file
            create_and_verify_temp_file(temp_dir)

    이 코드는 읽기 어려울 뿐만 아니라 장황함, 픽스처를 사용하면 이를 단순화 하고 테스트를 더 쉽게 읽을 수 있고 유지 관리할 수 있음

    내장된 픽스처가 충분하지 않으면 pytest에는 플러그인 목록이 있음 이러한 플러그인 중에서 유용한 고정 픽스처를 제공하는 것 들도 있다.

     

     

    python weekly 뉴스레터로 메일이 와서 이 글을 읽으면서 좋은 내용인 것 같아 글을 작성하였땅

    반응형

    댓글

Designed by Tistory.