제리의 배움 기록

[테스트] PBT(Property Based Testing) 본문

자바

[테스트] PBT(Property Based Testing)

제리92 2021. 11. 12. 13:58

PBT(Property Based Testing)

PBT는 함수의 인자들을 자동화하여 테스트하기 위한 방법론입니다.

PBT는 개발자가 미처 다루지 못했거나 예상치 못한 값들까지(ex. edge case, 빈값, 비즈니스 로직상 유효하지 않은 값 등) 광범위한 property 값으로 테스트 커버리지를 확장하는 것을 얘기합니다.

PBT 라이브러리는 두가지 핵심 개념으로 구성되어있습니다.

  • Runner : 테스트를 실행하여 결과를 검증
  • Arbitrary : 실패하는 케이스를(counter-example)를 찾기 위해 "shrinking" 이란 알고리즘 기반으로 랜덤 값을 발생

Java에서 PBT 적용해보기

PBT 방법론은 QuickCheck 라이브러리로 인해 유명해졌습니다.

QuickCheck는 함수형 프로그래밍 언어인 Haskell로 작성되었는데, Java에서 바로 사용할 수 없기 때문에 junit과 호환이 가능하도록 변환된 라이브러리들이 있습니다.

이 중 가장 대표적인 두 라이브러리가 junit-quickcheckjqwik 입니다.

junit-quickcheck (2021.10.29. 현재 1.0 버전 기준)는 junit4에 dependency를 두고 있다고 명시되어있어서, junit5와의 호환이 가능하다 명시되어있는 jqwik를 사용해보겠습니다.

특별한 설정을 하지 않으면 jqwik은 1000번의 다른 파라미터 set으로 테스트를 진행합니다.

테스트 진행중 실패하면 이후 테스트는 중단합니다.

Gradle 설정

testImplementation 'net.jqwik:jqwik:1.5.6'

[참고] maven repository : jqwik

 

@Property

PBT 테스트를 할 메서드에 @Property 어노테이션을 설정합니다.

이 어노테이션이 설정되면 최소 한개의 파라미터는 @ForAll 어노테이션을 사용해야합니다.

 

@ForAll

테스트 런타임시에 jqwik에 의해 파라미터 값이 입력될 것을 명시합니다.

@Property 어노테이션이 메서드에 설정된 경우에만 사용가능합니다.

@Provide가 설정되어 있지 않으면 기본 provider가 사용됩니다.

@ForAll 어노테이션이 사용되지 않은 파라미터는 jqwik이 관여하지 않습니다.

 

리턴 타입

property 메서드는 두가지 리턴 타입을 가질 수 있습니다.

1) boolean :  property에 대해 success(true) or failure(false)

    @Property
    boolean absoluteValueOfAllNumbersIsPositive(@ForAll int anInteger) {
        return Math.abs(anInteger) >= 0;
    }

주의! 위 결과는 실패합니다.

why?
PBT에서 실패케이스를 찾다가 -2147483648 으로 테스트하는 경우, 절대값으로 변환했을 때 int 타입으로 표현할 수 없으므로(int 범위 : -2147483648 ~ 2147483647) Math.abs 함수를 사용해도 값이 양수로 변환되지 않고 그대로 출력됩니다.

[Math.abs 정의 참고]
Note that if the argument is equal to the value of Integer.MIN_VALUE, the most negative representable int value, the result is that same value, which is negative.

 

2) void : assertion 라이브러리를 이용하여 property 값 검증 표현을 만들어야 합니다.

@Property
    void lengthOfConcatenatedStringIsGreaterThanLengthOfEach(
        @ForAll String string1, @ForAll String string2
    ) {
        String conc = string1 + string2;
        Assertions.assertThat(conc.length()).isGreaterThan(string1.length());
        Assertions.assertThat(conc.length()).isGreaterThan(string2.length());
    }

주의! 위 결과는 실패합니다.

why?
string1, string2중 하나가 empty string인 경우 합쳐도 길이 변화가 없기 때문에 검증을 통과하지 못합니다.
- string1 : "" , string2 : "abcd"
- string1 : "abcd" , string2 : ""

 

@EnableFootnotes

property 테스트에 대한 오류 결과를 보다 편리하게 보려면 jqwik에서 제공하는 Footnotes 기능을 사용할 수 있습니다.

Footnotes를 사용하기 위해서는 테스트 클래스에 @EnableFootnotes을 명시하여야 합니다.

@EnableFootnotes
public class JqwikTest { 
      @Property
    void differenceShouldBeBelow42(@ForAll int number1, @ForAll int number2, Footnotes footnotes) {
        int difference = Math.abs(number1 - number2);
        footnotes.addFootnote(Integer.toString(difference));
        Assertions.assertThat(difference).isLessThan(42);
    }
}

 

[참고]

Property-based testing: what is it?

junit-quickcheck v1.0 documentation

typeable.io - QuickCheck

hackage.haskell - QuickCheck

Comments