개발/Python

[Python] 파이썬 pytest의 test fixture를 활용해보자

growing-dev 2023. 3. 1. 22:12
반응형

pytest에서 test fixture를 활용해서 조금 더 복잡한 테스트가 효율적으로 가능하도록 구현해 보자

 

파이썬 pytest의 fixture를 활용해 보자

 

 

test fixture가 무엇인가

아래 위키피디아에 따르면 소프트웨어를 일관되게 테스트할 수 있는 환경이라고 한다.

 테스트 픽스처는 일부 항목, 장치 또는 소프트웨어를 일관되게 테스트하는 데 사용되는 환경입니다. 

https://en.wikipedia.org/wiki/Test_fixture#Software

 

Test fixture - Wikipedia

From Wikipedia, the free encyclopedia Type of testing environment A test fixture is an environment used to consistently test some item, device, or piece of software. Test fixtures can be found when testing electronics, software and physical devices. Electr

en.wikipedia.org

 

테스트는 결정적이어야 한다. 다시 말하면 테스트의 결과가 항상 일정해야 된다. 항상 실패하거나 항상 성공해야 한다. 이를 위해서는 각각의 테스트가 서로 독립적으로 수행될 수 있어야 하고 사전 조건과 실행 환경, 종료 이후 정리 과정이 잘 정의되어 있어야 한다. 이를 위해서 테스트 픽스쳐가 사용될 수 있다. 테스트 픽스처를 활용해서 비슷한 종류의 테스트를 효율적으로 실행할 수 있고 사전 조건이나 정리 과정을 일관성 있게 맞출 수 있다.

 

pytest에서 test fixture 활용법

 

우선 기존의 factorial.py를 활용해서 아래와 같이 수정해 보았다.

factorial.py는 기존 단순한 함수에서 class로 변경하였다. 특별히 추가된 내용은 없다.

factorial_test.py는 MyFactorial class를 만들고 my_class로 받은 다음 factorial(xx)로 호출하는 식으로 동작하도록 했다.

 

factorial_test.py

from factorial import MyFactorial
import math


def test_factorial_zero():
    my_class = MyFactorial()
    assert my_class.factorial(0) == 1

def test_factorial_10():
    my_class = MyFactorial()
    assert my_class.factorial(10) == math.factorial(10)

def test_factorial_1000():
    my_class = MyFactorial()
    assert my_class.factorial(1000) == math.factorial(1000)

 

factorial.py

class MyFactorial:
    def __init__(self):
        pass
    
    def factorial(self, n):
        if n < 0:
            n = -n
        val = 1

        while n > 0:
            val *= n
            n -= 1

        return val

 

문제는 테스트가 계속 늘어나면서 my_Class = MyFactorial() 이 반복적으로 사용된다는 것이다. 반복적인 객체 생성의 코드가 중복되면서 불필요하게 코드 양이 늘어나고 보기에 불편하다. 이를 개선할 수 있는 것이 test fixture 사용이다.

 

test fixture 사용은 @pytest. fixture라는 데코레이터를 원하는 함수에 붙여주면 된다. 아래의 def my_class()의 경우가 test fixture가 될 수 있다. 해당하는 fixture를 테스트에서 사용하고 싶을 때는 인자로 전달받아서 사용하면 된다. 아래 예제에서 각각의 테스트가 my_class를 인자로 받아서 사용하는 것을 볼 수 있고 결과도 잘 나오는 것을 확인할 수 있다.

import pytest
from factorial import MyFactorial
import math

@pytest.fixture
def my_class():
    my_class = MyFactorial()
    return my_class
    
def test_factorial_zero(my_class):
    assert my_class.factorial(0) == 1

def test_factorial_10(my_class):
    assert my_class.factorial(10) == math.factorial(10)

def test_factorial_1000(my_class):
    assert my_class.factorial(1000) == math.factorial(1000)

테스트 결과

 

 

공식 예제 보기

pytest 공식 사이트에서 제공하는 예제이다. 이해하기 쉽게 간단한 코드를 보여주어서 한번 공부해 보았다.

우선 fixture는 2개가 있다. 하나는 첫 번째 entry를 a로 리턴하도록 하는 first_entry()가 있고 이 인자를 전달받으면 list로 만들어서 반환하는 order(first_entry)가 있다. 여기서 first_entry를 전달받기 때문에 fixture에 존재하는 first_entry가 호출되고 따라서 order가 호출되면 항상 a로 시작하는 list가 만들어진다.

import pytest


@pytest.fixture
def first_entry():
    return "a"


@pytest.fixture
def order(first_entry):
    return [first_entry]


def test_string(order):
    # Act
    order.append("b")

    # Assert
    assert order == ["a", "b"]


def test_int(order):
    # Act
    order.append(2)
    # Assert
    assert order == ["a", 2]

테스트 수행 결과

 

테스트 string와 테스트 int가 있다. string을 추가하거나 int를 추가해서 항상 a로 시작하고 추가된 인자가 존재하는 리스트인지 비교하는 테스트가 되겠다. 공식 예제는 이후 이런 동작을 fixture가 없이 실행한다면 어떻게 코드를 구성해야 하는지 보여준다.

 

first_entry()와 order()가 pytest.fixture가 아니었다면 맨 아래 코드에서 보다시피 매번 first entry와 order를 사용해서 객체를 생성하고 전달해 주는 과정의 코드가 필요하다. 아니면 매번 테스트 내에서 객체를 생성하는 과정이 필요한데 이것도 굉장히 비효율적일 수밖에 없다.

만약 테스트가 추가되거나 기능이 추가된다면 코드가 기하급수적으로 늘어날 수가 있다. 특히 좀 더 복잡한 과정이라면 프로세스를 빠트릴 수 있어서 테스트 자체가 제대로 안되어서 문제가 될 수도 있다.

def first_entry():
    return "a"


def order(first_entry):
    return [first_entry]


def test_string(order):
    # Act
    order.append("b")

    # Assert
    assert order == ["a", "b"]


def test_int(order):
    # Act
    order.append(2)

    # Assert
    assert order == ["a", 2]


entry = first_entry()
the_list = order(first_entry=entry)
test_string(order=the_list)

entry = first_entry()
the_list = order(first_entry=entry)
test_int(order=the_list)

 

 

결론

구글테스트에도 TEST_F로 test fixture를 사용하고 있다. class로 선언해서 setup(), teardown()을 활용해서 사전 조건과 끝난 이후 정리를 일관되게 할 수 있는 도구를 제공한다. 즉 test fixture는 테스트에서 필수적인 요소라고 할 수 있고 pytest에서는 이를 굉장히 쉽게 활용할 수 있도록 데코레이터 형태로 제공한다는 것을 알았다. 위에서 내가 공부한 심플한 예제여서 크게 도움이 되는지 감이 안 올 수도 있지만 좀 더 복잡한 예제나 프로그램을 테스트한다면 fixture를 더 효과적으로 활용해 볼 수 있어서 필요성을 더 느낄 수 있을 것이다.

반응형