테스팅

테스팅 (testing)

코드를 테스트하는 것은 소프트웨어 개발의 핵심 부분이에요. 테스트는 코드 동작을 자동으로·반복적으로 검증해 주고, 코드가 기대대로 동작한다는 것을 보장해 줘요.

출처: Raku Docs - Testing

Raku에서 Test 모듈이 테스팅 프레임워크를 제공하는데, Raku 공식 스펙테스트 스위트(spectest)도 이걸 사용해요. 테스팅 함수는 Test Anything Protocol에 맞는 출력을 내보내요. 보통은 싱크 컨텍스트(sink context)에서 쓰죠.

ok check-name($meta, :$relaxed-name), "name has a hyphen rather than '::'"

하지만 모든 함수는 테스트가 성공했는지 나타내는 Boolean도 돌려줘서, 테스트가 실패했을 때 메시지를 출력하는 데 쓸 수 있어요.

ok check-name($meta, :$relaxed-name), "name has a hyphen rather than '::'" \
  or diag "\nTo use hyphen in name, pass :relaxed-name to check-name\n";

테스트 작성하기(Writing tests)

테스트를 다르게 구성할 수도 있지만, 전형적인 Raku 관례는 테스트가 프로젝트 기본 디렉터리의 t 디렉터리 아래에 있다는 점이에요. 전형적인 테스트 파일은 이렇게 생겼습니다.

use Test;      # a Standard module included with Rakudo
use lib 'lib';

plan $num-tests;

# .... tests

done-testing;  # optional with 'plan'

내장 Test 모듈을 로드하고, 다른 라이브러리가 어디 있는지 지정해요. 그런 다음 실행할 테스트 수를 plan으로 선언하죠. (그래야 프레임워크가 기대보다 많거나 적은 테스트가 실행됐는지 알려줘요.) 테스트가 끝나면 done-testing으로 프레임워크에 끝났다고 알려줘요.

스레드 안전성(Thread safety)

Test 모듈의 루틴은 스레드 안전하지 않아요. 여러 스레드에서 동시에 테스팅 루틴을 쓰려고 하면 안 돼요. TAP 출력이 순서가 뒤섞여서 그것을 해석하는 프로그램을 혼란스럽게 만들 수 있기 때문이에요.

현재로서는 스레드 안전하게 만들 계획이 없어요. 스레드 테스팅이 꼭 필요하다면, 테스팅 요구에 Test 대신 쓸 만한 에코시스템 모듈을 찾아볼 수 있어요.

테스트 실행하기(Running tests)

테스트는 명령줄에서 테스트 파일 이름을 지정해 개별적으로 실행할 수 있어요.

$ raku t/test-filename.rakutest

디렉터리의 모든 테스트를 재귀적으로 실행하려면 prove6를 쓰면 돼요. 사용 전에 zef로 설치해야 해요.

$ zef install App::Prove6

배포 디렉터리에서 prove6를 이렇게 실행할 수 있어요.

$ prove6 --lib t/

t/ 인자는 테스트가 들어 있는 디렉터리를 지정하고, --lib 옵션은 lib 디렉터리를 Raku 배포 경로에 포함시키기 위해 전달돼요. raku 명령의 -Ilib 인자와 같은 뜻이죠. prove6 사용에 대한 더 많은 문서는 그 페이지를 참고하세요.

첫 번째 실패에서 테스트 스위트를 중단하려면 RAKU_TEST_DIE_ON_FAIL 환경 변수를 설정하세요.

$ RAKU_TEST_DIE_ON_FAIL=1 raku t/test-filename.rakutest

같은 변수를 테스트 파일 안에서도 쓸 수 있어요. Test 모듈을 로드하기 전에 설정하세요.

BEGIN %*ENV<RAKU_TEST_DIE_ON_FAIL> = 1;
use Test;
...

참고: Rakudo 2020.05 릴리스 이전에는 이 기능을 켜는 데 PERL6_TEST_DIE_ON_FAIL 환경 변수를 사용했는데, 여전히 지원되지만 더 이상 사용되지 않아요(deprecated).

마이크로초 단위의 테스트 타이밍을 출력하려면 RAKU_TEST_TIMES 환경 변수를 설정하세요.

$ env RAKU_TEST_TIMES=1 raku -e 'use Test; plan 1; pass sleep(1);'
1..1
# between two timestamps 0 microseconds
ok 1 -
# t=1000721

같은 변수를 테스트 파일 안에서도 쓸 수 있어요. Test 모듈을 로드하기 전에 설정하세요.

BEGIN %*ENV<RAKU_TEST_TIMES> = 1;
use Test;
...

참고: Rakudo 2020.05 릴리스 이전에는 이 기능을 켜는 데 PERL6_TEST_TIMES 환경 변수를 사용했는데, 여전히 지원되지만 더 이상 사용되지 않아요.

테스트 플랜(Test plans)

테스트 플랜은 plan으로 앞으로 할 테스트 수를 선언해요(경우에 따라 건너뛸 수도 있음). 플랜이 선언되지 않았다면 done-testing으로 테스트가 끝났음을 선언해요.

반환 값 테스트하기(Testing return values)

Test 모듈은 주어진 표현식의 반환 값을 검사하고 표준 테스트 출력을 만드는 다양한 함수를 내보내요. 실제로 그 표현식은 보통 단위 테스트하려는 함수나 메서드 호출이에요. oknokTrueFalse를 검사해요. 하지만 가능하면 아래의 전문화된 비교 테스트 함수를 쓰는 편이 좋아요. 비교가 실패할 때 더 유용한 진단 출력을 찍어줄 수 있기 때문이에요.

문자열 비교

isisnt는 다루는 객체(또는 클래스)에 따라 적절한 연산자로 동등성을 검사해요.

근사 수치 비교

is-approx는 일정한 정밀도(절대 또는 상대)로 숫자를 비교해요. 정밀도가 내부 표현에 따라 달라지는 수치 값에 유용할 수 있어요.

구조 비교

구조 역시 is-deeply로 비교할 수 있는데, 비교된 객체의 내부 구조가 같은지 검사해요.

임의 비교

cmp-ok로 어떤 종류의 비교든 쓸 수 있어요. 비교에 사용할 함수나 연산자를 인자로 받죠.

객체 타입

isa-ok는 객체가 특정 타입인지 검사해요.

메서드 이름

can-ok는 객체에 그 특정 메서드가 있는지 검사하는 데 써요.

역할

does-ok는 주어진 변수가 특정 Role을 수행할 수 있는지 검사해요.

정규식

likeunlike는 정규식으로 검사해요. 전자는 일치가 있으면 통과하고, 후자는 일치가 없으면 통과해요.

모듈 테스트하기(Testing modules)

모듈은 use-ok로 시험적으로 로드되며, 로드에 실패하면 테스트가 실패해요.

예외 테스트하기(Testing exceptions)

dies-oklives-ok는 코드를 테스트하는 상반된 방법이에요. 전자는 예외를 던지는지, 후자는 그렇지 않은지를 검사하죠. throws-like는 코드가 인자로 받은 특정 예외를 던지는지 검사하고, fails-like는 코드가 특정 타입의 Failure를 돌려주는지 비슷하게 검사해요. eval-dies-okeval-lives-ok는 테스트하기 전에 평가되는 문자열에 대해 비슷하게 동작해요.

의도적인 종료 테스트하기(Testing intentional exits)

Raku 패키지 개발자에게는 때로 특정 조건에서 코드 경로의 의도적인 종료를 제공하는 것이 유용할 수 있어요. 그 종료는 POSIX 범위 1-127의 고유한 숫자 종료 코드로 나타낼 수 있어요. test exits-ok가 바로 그것을 제공해요.

Rakudo 2026.01 릴리스부터 유효해요. 사용법은 다음 서브루틴에서 보여줘요.

sub apply-factor-to(Numeric $num --> Numeric) {
    unless $num > 1 {
        say "FATAL: sub 'apply-factor-to' input is $num, it must be > 1";
        exit(3);
    }
    $num * 2.0;
}

이렇게 테스트할 수 있어요.

use Test;

exits-ok {
    my $x = apply-factor-to -2;
}, 3, "got the expected signal 3 for invalid input";

테스트 묶기(Grouping tests)

하위 테스트(subtests) 묶음의 결과는 모든 하위 테스트가 ok일 때만 ok예요. subtest로 묶습니다.

테스트 건너뛰기(Skipping tests)

때때로 테스트는 아직 실행할 준비가 안 됐어요. 예를 들어 기능이 아직 구현되지 않았을 수도 있는데, 그 경우 테스트를 todo로 표시할 수 있어요. 아니면 어떤 기능이 특정 플랫폼에서만 동작하는 경우도 있어요. 그렇다면 다른 플랫폼에서는 테스트를 skip하면 돼요. skip-rest는 인자로 주어진 특정 개수 대신 나머지 테스트를 건너뛰고, bail-out은 메시지와 함께 테스트를 그냥 종료해요.

수동 제어(Manual control)

위에서 문서화한 편의 기능이 여러분의 요구에 맞지 않는다면, 다음 함수로 테스트 하니스 출력을 직접 제어할 수 있어요. pass는 테스트가 통과했음을 말하고, diag는 (어쩌면 유용한) 메시지를 출력해요.