속성 기반 테스트
기존 테스트를 보완하는, 속성 기반 테스트를 알아보자. 2023-05-29

안녕하세요? 오늘은 속성 기반 테스트에 대해서 알아 보도록 하겠습니다.

기존 테스트 방식의 문제점

기존 테스트 방식인 예제 기반 테스트(Example-Based Test)의 방법은 다음과 같습니다.

  • 테스트 코드를 먼저 작성 합니다.
  • 해당 테스트 코드가 작동 할 수 있게 끔, 구현부를 작성 합니다.
  • 구현부 작성이 완료 되면, 테스트 코드를 구동 시킵니다.
  • 테스트 코드가 구동에 성공하면, 개발 성공!

하지만, 이는 다음과 같은 문제점을 내재 하고 있습니다.

  • 예상 하지 못한 Edge Case가 발생 할 수 있다.
  • 우리가 테스트 코드를 작성 했기 때문에, 테스트 코드편향 된 코드를 작성 할 가능성이 높다.

하나의 극단적인 예시를 들어 보겠습니다. 다음과 같이, list 내의 최대값과 최소값을 더한 값을 return 하는 테스트 코드를 작성 했다고 가정 하겠습니다.

def get_max_plus_min(li: List[int]) -> int:
    return max(li) + min(li)

def test():
    assert get_max_plus_min([1, 2, 3, 4]) == 5

하지만, 해당 코드는 이렇게 해도 잘 동작 합니다.

def get_max_plus_min(li: List[int]) -> int:
    if len(li) == 4:
        return 5
    else:
        return 2

def test():
    assert get_max_plus_min([1, 2, 3, 4]) == 5
    assert get_max_plus_min([1]) == 2

우리는 어떤 Input과 어떤 Output이 주어 지는지 잘 알고 있기 때문에, 이런 극단적인 코드도 작동 하게 할 수 있는 것 입니다.

그렇기 때문에, 우리는 논리적인 동작을 테스트에 반영 할 수 있는 기법이 필요하게 되는 것이지요.

속성 기반 테스트

속성 기반 테스트 (Property-Based Test)는 함수를 테스트 할 때, 함수를 실행한 결과값의 기반이 아닌, 속성 값을 기반으로 Assert를 수행 하는 테스팅 방식으로, 결과가 입력 데이터의 어떤 속성을 만족 하여 결과를 만들어 냈는지를 검증 하는 것이라고 생각하면 됩니다.

만약 덧셈을 하는 함수를 만든다는 가정을 한다면, 다음과 같은 조건들이 있을 수 있겠군요. (Input으로 a, b가 들어온다는 가정)

  • 교환 법칙이 성립 하는가? => func(a, b) == func(b, a)
  • 겹합 법칙이 성립 하는가? => func(a, func(b, b)) == func(a, 2 * b)

a와 b가 어떤 값이 들어올지는 아무도 모릅니다. 그저, 저 위에 있는 식들이 성립 하는지 확인 하면 되는 것이죠. 덧셈에서 중요한 원리들을 저희가 검증 하는 거에요.

속성 기반 테스트에서 중요한 것은, 함수의 입력과 출력에서, 검증해야 하는 것이 무엇이냐? 에 집중 하여, 검증해야 하는 논리에 대해, 이를 테스트 하는 것입니다.

우리가 테스트를 하려고 하는 중요한 논리가 무엇인지 알아 내는 것은 난이도가 있는 일이지만, 항상 성립해야 하는 조건을 찾거나, 항상 성립해서는 안되는 조건을 기반으로 생각 한다면, 답을 찾기가 쉬울 것입니다. 또한, 테스트가 완료 된 부분은 추상화 하여, 다른 테스트를 수행하는 데 사용 하면, 테스트 코드를 작성 하기가 더욱 쉬울 것 입니다!

이번엔 두 개의 Array를 붙이는 경우에 대해, 테스트 조건을 한 번 만들어 볼까요? (array_a, array_b) => new_array

  • 두 개의 Array를 붙인다면, 길이가 원본 배열 두 개의 합이어야 함. => array_a.length + array_b.length == new_array.length
  • 최소 값 원칙 => min(min(array_a), min(array_b)) == min(new_array)
  • 최대 값 원칙 => max(max(array_a), max(array_b)) == max(new_array)
  • 합 원칙 => sum(array_a) + sum(array_b) == sum(new_array)

여기서 min, max sum 같은 경우는 기본 라이브러리의 함수 이기 때문에, 테스트가 완료 되었다고 가정합니다. 그렇기 때문에 따로 검증을 수행 하지는 않습니다.

코드 직접 쳐보기

이번엔 Python 예시를 만들어 보죠! 일단, pip을 통해 테스트를 위한 pytest와, 속성 기반 테스트를 위한 hypothesis를 설치 하겠습니다. hypothesis에 대한 자세한 내용은 공식 Document를 참고 해 주세요!

$ pip install pytest hypothesis

그 다음, test-script.py에 다음과 같은 스크립트를 작성 합니다.

from hypothesis import given, strategies as st


def add(x, y):
    return x + y


@given(
    x=st.integers(min_value=1, max_value=500),  # x와 y에 1~500 까지의 random integer를 넣어 Test를 수행 해 줍니다.
    y=st.integers(min_value=1, max_value=500)
)
def test_add(x, y):
    assert add(x, y) == add(y, x)
    assert add(x, add(y, y)) == add(x, 2 * y)

테스트를 수행 하면, 결과를 확인 할 수 있습니다.

$ pytest test-script.py
plugins: hypothesis-6.75.6
collected 1 item                                                                                                                                      

test-script.py .                                                                                                                                [100%]

================================================================== 1 passed in 0.37s ==================================================================

다른 언어 같은 경우에도, 구글에서 [사용 언어] property based test library 키워드로 검색 하게 되면, 쉽게 찾을 수 있을 것 입니다!