Unit Testing

Testing Base Julia

Julia는 빠른 개발이 진행 중이며 여러 플랫폼에서 기능을 검증하기 위한 광범위한 테스트 스위트를 갖추고 있습니다. 소스에서 Julia를 빌드하면 make test를 사용하여 이 테스트 스위트를 실행할 수 있습니다. 바이너리 설치에서는 Base.runtests()를 사용하여 테스트 스위트를 실행할 수 있습니다.

Base.runtestsFunction
Base.runtests(tests=["all"]; ncores=ceil(Int, Sys.CPU_THREADS / 2),
              exit_on_error=false, revise=false, [seed])

tests에 나열된 Julia 단위 테스트를 실행합니다. tests는 문자열 또는 문자열 배열일 수 있으며, ncores 프로세서를 사용합니다. exit_on_errorfalse인 경우, 하나의 테스트가 실패하더라도 다른 파일의 나머지 테스트는 여전히 실행됩니다. 그렇지 않으면 exit_on_error == true일 때는 무시됩니다. revisetrue인 경우, 테스트를 실행하기 전에 Base 또는 표준 라이브러리에 대한 수정 사항을 로드하기 위해 Revise 패키지가 사용됩니다. 키워드 인수로 시드가 제공되면, 테스트가 실행되는 컨텍스트에서 전역 RNG의 시드로 사용됩니다. 그렇지 않으면 시드는 무작위로 선택됩니다.

source

Basic Unit Tests

Test 모듈은 간단한 단위 테스트 기능을 제공합니다. 단위 테스트는 결과가 예상한 대로인지 확인하여 코드가 올바른지 확인하는 방법입니다. 변경 후에도 코드가 여전히 작동하는지 확인하는 데 유용하며, 개발 중에 코드가 완성되었을 때 가져야 할 동작을 지정하는 방법으로 사용할 수 있습니다. 또한 adding tests to your Julia Package에 대한 문서도 확인해 보시기 바랍니다.

간단한 단위 테스트는 @test@test_throws 매크로를 사용하여 수행할 수 있습니다:

Test.@testMacro
@test ex
@test f(args...) key=val ...
@test ex broken=true
@test ex skip=true

표현식 extrue로 평가되는지 테스트합니다. @testset 내에서 실행되면, true일 경우 Pass Result를 반환하고, false일 경우 Fail Result를 반환하며, 평가할 수 없을 경우 Error Result를 반환합니다. @testset 외부에서 실행되면, Fail 또는 Error를 반환하는 대신 예외를 발생시킵니다.

예제

julia> @test true
Test Passed

julia> @test [1, 2] + [2, 1] == [3, 3]
Test Passed

@test f(args...) key=val... 형태는 @test f(args..., key=val...)를 작성하는 것과 동등하며, 이는 표현식이 약식 비교와 같은 중위 구문을 사용하는 호출인 경우 유용할 수 있습니다:

julia> @test π ≈ 3.14 atol=0.01
Test Passed

이는 더 복잡한 테스트인 @test ≈(π, 3.14, atol=0.01)와 동등합니다. 첫 번째가 호출 표현식이고 나머지가 할당(k=v)인 경우를 제외하고는 두 개 이상의 표현식을 제공하는 것은 오류입니다.

key=val 인수에 대해 어떤 키를 사용할 수 있지만, brokenskip@test의 맥락에서 특별한 의미를 가지므로 사용할 수 없습니다:

  • broken=condcond==true일 때 현재 일관되게 실패해야 하는 테스트를 나타냅니다. 표현식 exfalse로 평가되거나 예외를 발생시키는 경우. true로 평가되면 Broken Result를 반환하고, true로 평가되면 Error Result를 반환합니다. cond==false일 때 일반 @test ex가 평가됩니다.
  • skip=condcond==true일 때 실행되지 않아야 하지만 테스트 요약 보고서에 Broken으로 포함되어야 하는 테스트를 표시합니다. 이는 간헐적으로 실패하는 테스트나 아직 구현되지 않은 기능의 테스트에 유용할 수 있습니다. cond==false일 때 일반 @test ex가 평가됩니다.

예제

julia> @test 2 + 2 ≈ 6 atol=1 broken=true
Test Broken
  Expression: ≈(2 + 2, 6, atol = 1)

julia> @test 2 + 2 ≈ 5 atol=1 broken=false
Test Passed

julia> @test 2 + 2 == 5 skip=true
Test Broken
  Skipped: 2 + 2 == 5

julia> @test 2 + 2 == 4 skip=false
Test Passed
Julia 1.7

brokenskip 키워드 인수는 최소한 Julia 1.7이 필요합니다.

source
Test.@test_throwsMacro
@test_throws exception expr

표현식 exprexception을 발생시키는지 테스트합니다. 예외는 타입, 문자열, 표시된 오류 메시지에 발생하는 정규 표현식 또는 문자열 목록, 일치하는 함수 또는 값(필드를 비교하여 동등성을 테스트함)으로 지정할 수 있습니다. @test_throws는 후행 키워드 형식을 지원하지 않습니다.

Julia 1.8

타입이나 값을 제외한 다른 것을 exception으로 지정하는 기능은 Julia v1.8 이상이 필요합니다.

예제

julia> @test_throws BoundsError [1, 2, 3][4]
Test Passed
      Thrown: BoundsError

julia> @test_throws DimensionMismatch [1, 2, 3] + [1, 2]
Test Passed
      Thrown: DimensionMismatch

julia> @test_throws "Try sqrt(Complex" sqrt(-1)
Test Passed
     Message: "DomainError with -1.0:\nsqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x))."

마지막 예제에서는 단일 문자열과 일치하는 대신 다음과 같이 수행할 수 있었습니다:

  • ["Try", "Complex"] (문자열 목록)
  • r"Try sqrt\([Cc]omplex" (정규 표현식)
  • str -> occursin("complex", str) (일치하는 함수)
source

예를 들어, 새로운 함수 foo(x)가 예상대로 작동하는지 확인하고 싶다고 가정해 보겠습니다:

julia> using Test

julia> foo(x) = length(x)^2
foo (generic function with 1 method)

조건이 참이면 Pass가 반환됩니다:

julia> @test foo("bar") == 9
Test Passed

julia> @test foo("fizz") >= 10
Test Passed

조건이 거짓이면 Fail이 반환되고 예외가 발생합니다:

julia> @test foo("f") == 20
Test Failed at none:1
  Expression: foo("f") == 20
   Evaluated: 1 == 20

ERROR: There was an error during testing

조건을 평가할 수 없었던 경우, 이 경우 length가 기호에 대해 정의되지 않았기 때문에 예외가 발생하면 Error 객체가 반환되고 예외가 발생합니다:

julia> @test foo(:cat) == 1
Error During Test
  Test threw an exception of type MethodError
  Expression: foo(:cat) == 1
  MethodError: no method matching length(::Symbol)
  The function `length` exists, but no method is defined for this combination of argument types.

  Closest candidates are:
    length(::SimpleVector) at essentials.jl:256
    length(::Base.MethodList) at reflection.jl:521
    length(::MethodTable) at reflection.jl:597
    ...
  Stacktrace:
  [...]
ERROR: There was an error during testing

우리가 표현식을 평가할 때 예상대로 예외가 발생해야 한다고 생각한다면, @test_throws를 사용하여 이것이 발생하는지 확인할 수 있습니다:

julia> @test_throws MethodError foo(:cat)
Test Passed
      Thrown: MethodError

Working with Test Sets

일반적으로 많은 수의 테스트가 사용되어 함수가 다양한 입력에 대해 올바르게 작동하는지 확인합니다. 테스트가 실패할 경우 기본 동작은 즉시 예외를 발생시키는 것입니다. 그러나 일반적으로는 나머지 테스트를 먼저 실행하여 테스트 중인 코드에서 얼마나 많은 오류가 있는지 더 잘 파악하는 것이 바람직합니다.

Note

@testset는 그 안에서 테스트를 실행할 때 자체적인 로컬 범위를 생성합니다.

@testset 매크로는 테스트를 세트로 그룹화하는 데 사용할 수 있습니다. 테스트 세트의 모든 테스트가 실행되며, 테스트 세트가 끝나면 요약이 출력됩니다. 테스트 중 하나라도 실패하거나 오류로 인해 평가할 수 없는 경우, 테스트 세트는 TestSetException을 발생시킵니다.

Test.@testsetMacro
@testset [CustomTestSet] [options...] ["설명"] begin test_ex end
@testset [CustomTestSet] [options...] ["설명 $v"] for v in itr test_ex end
@testset [CustomTestSet] [options...] ["설명 $v, $w"] for v in itrv, w in itrw test_ex end
@testset [CustomTestSet] [options...] ["설명"] test_func()
@testset let v = v, w = w; test_ex; end

begin/end 또는 함수 호출과 함께

@testset가 사용될 때, begin/end 또는 단일 함수 호출과 함께 매크로는 주어진 표현식을 평가하기 위한 새로운 테스트 세트를 시작합니다.

사용자가 지정한 테스트 세트 유형이 없으면 기본적으로 DefaultTestSet이 생성됩니다. DefaultTestSet은 모든 결과를 기록하며, Fail 또는 Error가 있는 경우 최상위(비중첩) 테스트 세트의 끝에서 예외를 발생시키고 테스트 결과 요약을 함께 제공합니다.

사용자가 지정한 테스트 세트 유형(AbstractTestSet의 하위 유형)을 제공할 수 있으며, 이는 모든 중첩된 @testset 호출에도 사용됩니다. 주어진 옵션은 제공된 테스트 세트에만 적용됩니다. 기본 테스트 세트 유형은 세 가지 부울 옵션을 수용합니다:

  • verbose: true인 경우, 모든 테스트가 통과하더라도 중첩된 테스트 세트의 결과 요약이 표시됩니다(기본값은 false입니다).
  • showtiming: true인 경우, 표시된 각 테스트 세트의 지속 시간이 표시됩니다(기본값은 true입니다).
  • failfast: true인 경우, 테스트 실패 또는 오류가 발생하면 테스트 세트와 모든 자식 테스트 세트가 즉시 반환됩니다(기본값은 false입니다). 이는 환경 변수 JULIA_TEST_FAILFAST를 통해 전역적으로 설정할 수도 있습니다.
Julia 1.8

@testset test_func()는 최소한 Julia 1.8이 필요합니다.

Julia 1.9

failfast는 최소한 Julia 1.9가 필요합니다.

설명 문자열은 루프 인덱스에서 보간을 허용합니다. 설명이 제공되지 않으면 변수에 따라 하나가 구성됩니다. 함수 호출이 제공되면 해당 이름이 사용됩니다. 명시적 설명 문자열은 이 동작을 무시합니다.

기본적으로 @testset 매크로는 테스트 세트 객체 자체를 반환하지만, 이 동작은 다른 테스트 세트 유형에서 사용자 정의할 수 있습니다. for 루프가 사용되면 매크로는 finish 메서드의 반환 값 목록을 수집하고 반환하며, 기본적으로는 각 반복에서 사용된 테스트 세트 객체의 목록을 반환합니다.

@testset 본문 실행 전에 Random.seed!(seed)에 대한 암시적 호출이 있으며, 여기서 seed는 전역 RNG의 현재 시드입니다. 또한 본문 실행 후 전역 RNG의 상태는 @testset 이전 상태로 복원됩니다. 이는 실패 시 재현성을 용이하게 하고, 전역 RNG 상태에 대한 부작용과 관계없이 @testset의 원활한 재배치를 허용하기 위한 것입니다.

예시

julia> @testset "삼각 함수 항등식" begin
           θ = 2/3*π
           @test sin(-θ) ≈ -sin(θ)
           @test cos(-θ) ≈ cos(θ)
           @test sin(2θ) ≈ 2*sin(θ)*cos(θ)
           @test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2
       end;
테스트 요약:            | 통과  총계  시간
삼각 함수 항등식 |    4      4  0.2s

@testset for

@testset for가 사용될 때, 매크로는 제공된 루프의 각 반복에 대해 새로운 테스트를 시작합니다. 각 테스트 세트의 의미는 begin/end 경우와 동일합니다(각 루프 반복에 대해 사용된 것처럼).

@testset let

@testset let이 사용될 때, 매크로는 주어진 객체를 포함하여 실패한 테스트에 대한 컨텍스트 객체로 추가된 투명한 테스트 세트를 시작합니다. 이는 하나의 더 큰 객체에 대해 관련된 테스트 세트를 수행할 때 유용하며, 개별 테스트 중 하나라도 실패할 경우 이 더 큰 객체를 인쇄하는 것이 바람직합니다. 투명한 테스트 세트는 테스트 세트 계층에서 추가적인 중첩 수준을 도입하지 않으며, 부모 테스트 세트로 직접 전달됩니다(컨텍스트 객체가 실패한 테스트에 추가됨).

Julia 1.9

@testset let은 최소한 Julia 1.9가 필요합니다.

Julia 1.10

여러 let 할당은 Julia 1.10부터 지원됩니다.

예시

julia> @testset let logi = log(im)
           @test imag(logi) == π/2
           @test !iszero(real(logi))
       end
테스트 실패: none:3
  표현식: !(iszero(real(logi)))
     컨텍스트: logi = 0.0 + 1.5707963267948966im

ERROR: 테스트 중 오류가 발생했습니다.

julia> @testset let logi = log(im), op = !iszero
           @test imag(logi) == π/2
           @test op(real(logi))
       end
테스트 실패: none:3
  표현식: op(real(logi))
     컨텍스트: logi = 0.0 + 1.5707963267948966im
              op = !iszero

ERROR: 테스트 중 오류가 발생했습니다.
source
Test.TestSetExceptionType
TestSetException

테스트 세트가 완료되었지만 모든 테스트가 통과하지 않았을 때 발생합니다.

source

우리는 foo(x) 함수에 대한 테스트를 테스트 세트에 넣을 수 있습니다:

julia> @testset "Foo Tests" begin
           @test foo("a")   == 1
           @test foo("ab")  == 4
           @test foo("abc") == 9
       end;
Test Summary: | Pass  Total  Time
Foo Tests     |    3      3  0.0s

테스트 세트는 중첩될 수도 있습니다:

julia> @testset "Foo Tests" begin
           @testset "Animals" begin
               @test foo("cat") == 9
               @test foo("dog") == foo("cat")
           end
           @testset "Arrays $i" for i in 1:3
               @test foo(zeros(i)) == i^2
               @test foo(fill(1.0, i)) == i^2
           end
       end;
Test Summary: | Pass  Total  Time
Foo Tests     |    8      8  0.0s

함수 호출 외에도:

julia> f(x) = @test isone(x)
f (generic function with 1 method)

julia> @testset f(1);
Test Summary: | Pass  Total  Time
f             |    1      1  0.0s

이것은 테스트 세트의 인수 분해를 허용하는 데 사용될 수 있으며, 관련 함수를 대신 실행하여 개별 테스트 세트를 실행하는 것이 더 쉬워집니다. 함수의 경우, 테스트 세트는 호출된 함수의 이름이 부여됩니다. 중첩된 테스트 세트에 실패가 없는 경우, 여기서 발생한 것처럼 요약에서 숨겨지며, verbose=true 옵션이 전달되지 않는 한 그렇습니다:

julia> @testset verbose = true "Foo Tests" begin
           @testset "Animals" begin
               @test foo("cat") == 9
               @test foo("dog") == foo("cat")
           end
           @testset "Arrays $i" for i in 1:3
               @test foo(zeros(i)) == i^2
               @test foo(fill(1.0, i)) == i^2
           end
       end;
Test Summary: | Pass  Total  Time
Foo Tests     |    8      8  0.0s
  Animals     |    2      2  0.0s
  Arrays 1    |    2      2  0.0s
  Arrays 2    |    2      2  0.0s
  Arrays 3    |    2      2  0.0s

테스트 실패가 발생할 경우, 실패한 테스트 세트에 대한 세부 정보만 표시됩니다:

julia> @testset "Foo Tests" begin
           @testset "Animals" begin
               @testset "Felines" begin
                   @test foo("cat") == 9
               end
               @testset "Canines" begin
                   @test foo("dog") == 9
               end
           end
           @testset "Arrays" begin
               @test foo(zeros(2)) == 4
               @test foo(fill(1.0, 4)) == 15
           end
       end

Arrays: Test Failed
  Expression: foo(fill(1.0, 4)) == 15
   Evaluated: 16 == 15
[...]
Test Summary: | Pass  Fail  Total  Time
Foo Tests     |    3     1      4  0.0s
  Animals     |    2            2  0.0s
  Arrays      |    1     1      2  0.0s
ERROR: Some tests did not pass: 3 passed, 1 failed, 0 errored, 0 broken.

Testing Log Statements

@test_logs 매크로를 사용하여 로그 문을 테스트하거나 TestLogger를 사용할 수 있습니다.

Test.@test_logsMacro
@test_logs [log_patterns...] [keywords] 표현식

collect_test_logs를 사용하여 표현식에 의해 생성된 로그 레코드 목록을 수집하고, 이들이 log_patterns 시퀀스와 일치하는지 확인한 후 표현식의 값을 반환합니다. keywords는 로그 레코드의 간단한 필터링을 제공합니다: min_level 키워드는 테스트를 위해 수집될 최소 로그 수준을 제어하고, match_mode 키워드는 매칭이 어떻게 수행될지를 정의합니다(기본값 :all은 모든 로그와 패턴이 쌍으로 일치하는지 확인합니다; :any를 사용하여 패턴이 시퀀스의 어딘가에서 최소한 한 번 일치하는지 확인합니다).

가장 유용한 로그 패턴은 (level,message) 형식의 간단한 튜플입니다. 다른 수의 튜플 요소를 사용하여 AbstractLoggerhandle_message 함수를 통해 전달된 인수에 해당하는 다른 로그 메타데이터와 일치시킬 수 있습니다: (level,message,module,group,id,file,line). 존재하는 요소는 기본적으로 ==를 사용하여 로그 레코드 필드와 쌍으로 일치하며, Symbol은 표준 로그 수준에 사용할 수 있고, 패턴의 Regex는 문자열 또는 Symbol 필드를 occursin을 사용하여 일치시킵니다.

예제

경고를 기록하고 여러 디버그 메시지를 기록하는 함수를 고려해 보겠습니다:

function foo(n)
    @info "Doing foo with n=$n"
    for i=1:n
        @debug "Iteration $i"
    end
    42
end

정보 메시지를 테스트할 수 있습니다:

@test_logs (:info,"Doing foo with n=2") foo(2)

디버그 메시지도 테스트하고 싶다면, min_level 키워드로 이를 활성화해야 합니다:

using Logging
@test_logs (:info,"Doing foo with n=2") (:debug,"Iteration 1") (:debug,"Iteration 2") min_level=Logging.Debug foo(2)

특정 메시지가 생성되는지 테스트하고 나머지는 무시하고 싶다면, match_mode=:any 키워드를 설정할 수 있습니다:

using Logging
@test_logs (:info,) (:debug,"Iteration 42") min_level=Logging.Debug match_mode=:any foo(100)

매크로는 반환된 값을 테스트하기 위해 @test와 함께 연결할 수 있습니다:

@test (@test_logs (:info,"Doing foo with n=2") foo(2)) == 42

경고가 없는지 테스트하고 싶다면 로그 패턴을 지정하지 않고 min_level을 적절히 설정할 수 있습니다:

# 로거 수준이 warn일 때 표현식이 메시지를 기록하지 않는지 테스트:
@test_logs min_level=Logging.Warn @info("Some information") # 통과
@test_logs min_level=Logging.Warn @warn("Some information") # 실패

@warn에 의해 생성되지 않은 stderr에서 경고(또는 오류 메시지)의 부재를 테스트하려면 @test_nowarn을 참조하십시오. ```

source
Test.TestLoggerType
TestLogger(; min_level=Info, catch_exceptions=false)

logs::Vector{LogRecord} 필드에 기록된 메시지를 캡처하는 TestLogger를 생성합니다.

min_level을 설정하여 LogLevel을 제어하고, catch_exceptions는 로그 이벤트 생성의 일환으로 발생하는 예외를 포착할지 여부를 설정하며, respect_maxlogmaxlog=n으로 메시지를 기록하는 관습을 따를지 여부를 설정합니다. 여기서 n은 최대 n회까지의 정수입니다.

자세한 내용은 LogRecord를 참조하세요.

예제

julia> using Test, Logging

julia> f() = @info "Hi" number=5;

julia> test_logger = TestLogger();

julia> with_logger(test_logger) do
           f()
           @info "Bye!"
       end

julia> @test test_logger.logs[1].message == "Hi"
Test Passed

julia> @test test_logger.logs[1].kwargs[:number] == 5
Test Passed

julia> @test test_logger.logs[2].message == "Bye!"
Test Passed
source
Test.LogRecordType
LogRecord

단일 로그 이벤트의 결과를 저장합니다. 필드:

  • level: 로그 메시지의 LogLevel
  • message: 로그 메시지의 텍스트 내용
  • _module: 로그 이벤트의 모듈
  • group: 로깅 그룹(기본적으로 로그 이벤트가 포함된 파일의 이름)
  • id: 로그 이벤트의 ID
  • file: 로그 이벤트가 포함된 파일
  • line: 로그 이벤트의 파일 내 줄
  • kwargs: 로그 이벤트에 전달된 모든 키워드 인수
source

Other Test Macros

부동 소수점 값에 대한 계산이 부정확할 수 있으므로, @test a ≈ b를 사용하여 근사 동등성 검사를 수행할 수 있습니다(여기서 , \approx의 탭 완성을 통해 입력됨, isapprox 함수임) 또는 4d61726b646f776e2e436f64652822222c20226973617070726f782229_40726566를 직접 사용할 수 있습니다.

julia> @test 1 ≈ 0.999999999
Test Passed

julia> @test 1 ≈ 0.999999
Test Failed at none:1
  Expression: 1 ≈ 0.999999
   Evaluated: 1 ≈ 0.999999

ERROR: There was an error during testing

상대 및 절대 허용 오차는 비교 후 isapproxrtolatol 키워드 인수를 설정하여 지정할 수 있습니다:

julia> @test 1 ≈ 0.999999  rtol=1e-5
Test Passed

의 특정 기능이 아니라 @test 매크로의 일반적인 기능임을 유의하십시오: @test a <op> b key=val은 매크로에 의해 @test op(a, b, key=val)로 변환됩니다. 그러나 이는 테스트에 특히 유용합니다.

Test.@inferredMacro
@inferred [허용된유형] f(x)

호출 표현식 f(x)가 컴파일러에 의해 추론된 동일한 유형의 값을 반환하는지 테스트합니다. 이는 유형 안정성을 확인하는 데 유용합니다.

f(x)는 임의의 호출 표현식일 수 있습니다. 유형이 일치하면 f(x)의 결과를 반환하고, 다른 유형이 발견되면 Error Result를 반환합니다.

선택적으로, 허용된유형은 테스트를 완화하여 f(x)의 유형이 허용된유형을 제외한 추론된 유형과 일치하거나, 반환 유형이 허용된유형의 하위 유형일 때 통과하도록 합니다. 이는 Union{Nothing, T} 또는 Union{Missing, T}와 같은 작은 유니온을 반환하는 함수의 유형 안정성을 테스트할 때 유용합니다.

julia> f(a) = a > 1 ? 1 : 1.0
f (generic function with 1 method)

julia> typeof(f(2))
Int64

julia> @code_warntype f(2)
MethodInstance for f(::Int64)
  from f(a) @ Main none:1
Arguments
  #self#::Core.Const(f)
  a::Int64
Body::UNION{FLOAT64, INT64}
1 ─ %1 = (a > 1)::Bool
└──      goto #3 if not %1
2 ─      return 1
3 ─      return 1.0

julia> @inferred f(2)
ERROR: return type Int64 does not match inferred return type Union{Float64, Int64}
[...]

julia> @inferred max(1, 2)
2

julia> g(a) = a < 10 ? missing : 1.0
g (generic function with 1 method)

julia> @inferred g(20)
ERROR: return type Float64 does not match inferred return type Union{Missing, Float64}
[...]

julia> @inferred Missing g(20)
1.0

julia> h(a) = a < 10 ? missing : f(a)
h (generic function with 1 method)

julia> @inferred Missing h(20)
ERROR: return type Int64 does not match inferred return type Union{Missing, Float64, Int64}
[...]
source
Test.@test_deprecatedMacro
@test_deprecated [pattern] expression

--depwarn=yes일 때, expression이 사용 중단 경고를 발생시키는지 테스트하고 expression의 값을 반환합니다. 로그 메시지 문자열은 기본적으로 r"deprecated"i에 대해 일치합니다.

--depwarn=no일 때, 단순히 expression을 실행한 결과를 반환합니다. --depwarn=error일 때, ErrorException이 발생하는지 확인합니다.

예제

# julia 0.7에서 사용 중단됨
@test_deprecated num2hex(1)

# 반환된 값은 @test와 연결하여 테스트할 수 있습니다:
@test (@test_deprecated num2hex(1)) == "0000000000000001"
source
Test.@test_warnMacro
@test_warn msg expr

expr를 평가할 때 stderr 출력이 msg 문자열을 포함하거나 msg 정규 표현식과 일치하는지 테스트합니다. msg가 부울 함수인 경우, msg(output)true를 반환하는지 테스트합니다. msg가 튜플 또는 배열인 경우, 오류 출력이 msg의 각 항목을 포함/일치하는지 확인합니다. expr을 평가한 결과를 반환합니다.

오류 출력의 부재를 확인하려면 @test_nowarn도 참조하세요.

참고: @warn으로 생성된 경고는 이 매크로로 테스트할 수 없습니다. 대신 @test_logs를 사용하세요.

source
Test.@test_nowarnMacro
@test_nowarn expr

expr를 평가했을 때 빈 stderr 출력(경고나 다른 메시지 없음) 결과가 나오는지 테스트합니다. expr을 평가한 결과를 반환합니다.

참고: @warn에 의해 생성된 경고의 부재는 이 매크로로 테스트할 수 없습니다. 대신 @test_logs를 사용하세요.

source

Broken Tests

테스트가 지속적으로 실패하면 @test_broken 매크로를 사용하도록 변경할 수 있습니다. 이렇게 하면 테스트가 계속 실패할 경우 Broken으로 표시되며, 테스트가 성공할 경우 사용자에게 Error로 알림을 보냅니다.

Test.@test_brokenMacro
@test_broken ex
@test_broken f(args...) key=val ...

현재 일관되게 실패하는 테스트를 나타냅니다. 표현식 exfalse로 평가되거나 예외를 발생시키는지 테스트합니다. 만약 그렇다면 Broken Result를 반환하고, 표현식이 true로 평가되면 Error Result를 반환합니다. 이는 @test ex broken=true와 동등합니다.

@test_broken f(args...) key=val... 형식은 @test 매크로와 동일하게 작동합니다.

예제

julia> @test_broken 1 == 2
Test Broken
  Expression: 1 == 2

julia> @test_broken 1 == 2 atol=0.1
Test Broken
  Expression: ==(1, 2, atol = 0.1)
source

@test_skip는 평가 없이 테스트를 건너뛰는 데에도 사용할 수 있지만, 테스트 세트 보고서에서 건너뛴 테스트를 포함합니다. 테스트는 실행되지 않지만 Broken Result를 제공합니다.

Test.@test_skipMacro
@test_skip ex
@test_skip f(args...) key=val ...

실행되지 않아야 하지만 테스트 요약 보고서에는 Broken으로 포함되어야 하는 테스트를 표시합니다. 이는 간헐적으로 실패하는 테스트나 아직 구현되지 않은 기능의 테스트에 유용할 수 있습니다. 이는 @test ex skip=true와 동일합니다.

@test_skip f(args...) key=val... 형식은 @test 매크로와 동일하게 작동합니다.

예시

julia> @test_skip 1 == 2
Test Broken
  Skipped: 1 == 2

julia> @test_skip 1 == 2 atol=0.1
Test Broken
  Skipped: ==(1, 2, atol = 0.1)
source

Test result types

Test.ResultType
Test.Result

모든 테스트는 결과 객체를 생성합니다. 이 객체는 테스트가 테스트 세트의 일부인지 여부에 따라 저장될 수도 있고 저장되지 않을 수도 있습니다.

source
Test.PassType
Test.Pass <: Test.Result

테스트 조건이 참이었습니다. 즉, 표현식이 참으로 평가되었거나 올바른 예외가 발생했습니다.

source
Test.FailType
Test.Fail <: Test.Result

테스트 조건이 거짓이었습니다. 즉, 표현식이 거짓으로 평가되었거나 올바른 예외가 발생하지 않았습니다.

source
Test.ErrorType
Test.Error <: Test.Result

테스트 조건을 예외로 인해 평가할 수 없거나 Bool 이외의 다른 것으로 평가되었습니다. @test_broken의 경우, 예상치 못한 Pass Result가 발생했음을 나타내는 데 사용됩니다.

source
Test.BrokenType
Test.Broken <: Test.Result

테스트 조건은 깨진 테스트의 예상(실패한) 결과이거나, @test_skip으로 명시적으로 건너뛰었습니다.

source

Creating Custom AbstractTestSet Types

패키지는 recordfinish 메서드를 구현하여 자체 AbstractTestSet 하위 유형을 생성할 수 있습니다. 하위 유형은 설명 문자열을 인수로 받는 단일 인수 생성자를 가져야 하며, 모든 옵션은 키워드 인수로 전달되어야 합니다.

Test.recordFunction
record(ts::AbstractTestSet, res::Result)

테스트 세트에 결과를 기록합니다. 이 함수는 포함된 @test 매크로가 완료될 때마다 @testset 인프라에 의해 호출되며, 테스트 결과(이는 Error일 수 있음)를 받습니다. 테스트 블록 내에서 @test 컨텍스트 외부에서 예외가 발생하면 Error와 함께 호출됩니다.

source
Test.finishFunction
finish(ts::AbstractTestSet)

주어진 테스트 세트에 필요한 최종 처리를 수행합니다. 이는 테스트 블록이 실행된 후 @testset 인프라에 의해 호출됩니다.

사용자 정의 AbstractTestSet 하위 유형은 테스트 결과 트리에 자신을 추가하기 위해 부모에게 record를 호출해야 합니다(부모가 있는 경우). 이는 다음과 같이 구현될 수 있습니다:

if get_testset_depth() != 0
    # 이 테스트 세트를 부모 테스트 세트에 연결합니다
    parent_ts = get_testset()
    record(parent_ts, self)
    return self
end
source

Test는 실행되는 동안 중첩된 테스트 세트의 스택을 유지하는 책임을 지지만, 결과 누적은 AbstractTestSet 하위 유형의 책임입니다. 이 스택은 get_testsetget_testset_depth 메서드를 사용하여 접근할 수 있습니다. 이러한 함수는 내보내지 않음을 유의하십시오.

Test.get_testsetFunction
get_testset()

작업의 로컬 저장소에서 활성 테스트 세트를 가져옵니다. 활성 테스트 세트가 없으면 기본 테스트 세트를 사용합니다.

source
Test.get_testset_depthFunction
get_testset_depth()

기본 테스트 세트를 제외한 활성 테스트 세트의 수를 반환합니다.

source

Test는 또한 중첩된 @testset 호출이 부모와 동일한 AbstractTestSet 하위 유형을 사용하도록 보장합니다. 명시적으로 설정하지 않는 한 그렇습니다. 테스트 세트의 속성은 전파되지 않습니다. 옵션 상속 동작은 Test가 제공하는 스택 인프라를 사용하여 패키지에서 구현할 수 있습니다.

기본 AbstractTestSet 하위 유형을 정의하는 것은 다음과 같을 수 있습니다:

import Test: Test, record, finish
using Test: AbstractTestSet, Result, Pass, Fail, Error
using Test: get_testset_depth, get_testset
struct CustomTestSet <: Test.AbstractTestSet
    description::AbstractString
    foo::Int
    results::Vector
    # constructor takes a description string and options keyword arguments
    CustomTestSet(desc; foo=1) = new(desc, foo, [])
end

record(ts::CustomTestSet, child::AbstractTestSet) = push!(ts.results, child)
record(ts::CustomTestSet, res::Result) = push!(ts.results, res)
function finish(ts::CustomTestSet)
    # just record if we're not the top-level parent
    if get_testset_depth() > 0
        record(get_testset(), ts)
        return ts
    end

    # so the results are printed if we are at the top level
    Test.print_test_results(ts)
    return ts
end

그리고 그 테스트 세트를 사용하는 모습은 다음과 같습니다:

@testset CustomTestSet foo=4 "custom testset inner 2" begin
    # this testset should inherit the type, but not the argument.
    @testset "custom testset inner" begin
        @test true
    end
end

사용자 정의 테스트 세트를 사용하고 기록된 결과가 외부 기본 테스트 세트의 일부로 인쇄되도록 하려면 Test.get_test_counts도 정의해야 합니다. 이는 다음과 같이 보일 수 있습니다:

using Test: AbstractTestSet, Pass, Fail, Error, Broken, get_test_counts, TestCounts, format_duration

function Test.get_test_counts(ts::CustomTestSet)
    passes, fails, errors, broken = 0, 0, 0, 0
    # cumulative results
    c_passes, c_fails, c_errors, c_broken = 0, 0, 0, 0

    for t in ts.results
        # count up results
        isa(t, Pass)   && (passes += 1)
        isa(t, Fail)   && (fails  += 1)
        isa(t, Error)  && (errors += 1)
        isa(t, Broken) && (broken += 1)
        # handle children
        if isa(t, AbstractTestSet)
            tc = get_test_counts(t)::TestCounts
            c_passes += tc.passes + tc.cumulative_passes
            c_fails  += tc.fails + tc.cumulative_fails
            c_errors += tc.errors + tc.cumulative_errors
            c_broken += tc.broken + tc.cumulative_broken
        end
    end
    # get a duration, if we have one
    duration = format_duration(ts)
    return TestCounts(true, passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration)
end
Test.TestCountsType
TestCounts

테스트 세트의 결과를 재귀적으로 수집하여 표시하기 위한 상태를 보유합니다.

필드:

  • customized: get_test_counts 함수가 이 카운트 객체에 대한 AbstractTestSet에 맞게 사용자 정의되었는지 여부. 사용자 정의 메서드가 정의된 경우, 항상 생성자에 true를 전달하십시오.
  • passes: 통과한 @test 호출의 수.
  • fails: 실패한 @test 호출의 수.
  • errors: 오류가 발생한 @test 호출의 수.
  • broken: 고장난 @test 호출의 수.
  • passes: 통과한 @test 호출의 누적 수.
  • fails: 실패한 @test 호출의 누적 수.
  • errors: 오류가 발생한 @test 호출의 누적 수.
  • broken: 고장난 @test 호출의 누적 수.
  • duration: 해당 AbstractTestSet이 실행된 총 지속 시간, 형식화된 String으로.
source
Test.get_test_countsFunction

" gettestcounts(::AbstractTestSet) -> TestCounts

테스트 세트에서 각 유형의 테스트 결과 수를 직접 계산하고 자식 테스트 세트에서 총계를 계산하는 재귀 함수입니다.

사용자 정의 AbstractTestSet은 이 함수를 구현하여 DefaultTestSet과 함께 총계를 계산하고 표시해야 합니다.

사용자 정의 TestSet에 대해 이 기능이 구현되지 않은 경우, 출력은 실패에 대해 x를 보고하고 지속 시간에 대해 ?s로 대체됩니다.

source
Test.format_durationFunction
format_duration(::AbstractTestSet)

테스트 세트가 실행된 기간을 인쇄하기 위한 형식화된 문자열을 반환합니다.

정의되지 않은 경우 "?s"로 대체됩니다.

source
Test.print_test_resultsFunction
print_test_results(ts::AbstractTestSet, depth_pad=0)

AbstractTestSet의 결과를 형식화된 테이블로 출력합니다.

depth_pad는 모든 출력 앞에 추가해야 할 패딩의 양을 나타냅니다.

Test.finish 내부에서 호출되며, finish된 테스트 세트가 가장 상위 테스트 세트인 경우에 해당합니다.

source

Test utilities

Test.GenericArrayType

GenericArrayAbstractArray 인터페이스에 프로그래밍된 일반 배열 API를 테스트하는 데 사용할 수 있으며, 이를 통해 함수가 표준 Array 유형 외의 배열 유형에서도 작동할 수 있는지 확인할 수 있습니다.

source
Test.GenericDictType

GenericDictAbstractDict 인터페이스에 프로그래밍된 일반 dict API를 테스트하는 데 사용할 수 있으며, 이를 통해 함수가 표준 Dict 유형 외의 연관 유형과 함께 작동할 수 있는지 확인할 수 있습니다.

source
Test.GenericOrderType

GenericOrder는 일반 정렬 유형에 대한 지원을 테스트하기 위해 API를 테스트하는 데 사용할 수 있습니다.

source
Test.GenericSetType

GenericSetAbstractSet 인터페이스에 프로그래밍된 일반 집합 API를 테스트하는 데 사용할 수 있으며, 이를 통해 함수가 표준 SetBitSet 유형 외의 집합 유형에서도 작동할 수 있는지 확인할 수 있습니다.

source
Test.GenericStringType

GenericStringAbstractString 인터페이스에 프로그래밍된 일반 문자열 API를 테스트하는 데 사용할 수 있으며, 이를 통해 함수가 표준 String 유형 외의 문자열 유형과 함께 작동할 수 있는지 확인할 수 있습니다.

source
Test.detect_ambiguitiesFunction
detect_ambiguities(mod1, mod2...; recursive=false,
                                  ambiguous_bottom=false,
                                  allowed_undefineds=nothing)

지정된 모듈에서 정의된 모호한 메서드의 (Method,Method) 쌍의 벡터를 반환합니다. 모든 하위 모듈에서 테스트하려면 recursive=true를 사용하십시오.

ambiguous_bottomUnion{} 타입 매개변수로만 유발된 모호성을 포함할지 여부를 제어합니다. 대부분의 경우 이 값을 false로 설정하는 것이 좋습니다. Base.isambiguous를 참조하십시오.

allowed_undefineds에 대한 설명은 Test.detect_unbound_args를 참조하십시오.

Julia 1.8

allowed_undefineds는 최소한 Julia 1.8이 필요합니다.

source
Test.detect_unbound_argsFunction
detect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing)

유형 매개변수가 바인딩되지 않았을 수 있는 Method의 벡터를 반환합니다. 모든 하위 모듈에서 테스트하려면 recursive=true를 사용하십시오.

기본적으로 정의되지 않은 기호는 경고를 발생시킵니다. 이 경고는 경고를 건너뛸 수 있는 GlobalRef의 컬렉션을 제공하여 억제할 수 있습니다. 예를 들어,

allowed_undefineds = Set([GlobalRef(Base, :active_repl),
                          GlobalRef(Base, :active_repl_backend)])

Base.active_replBase.active_repl_backend에 대한 경고를 억제합니다.

Julia 1.8

allowed_undefineds는 최소한 Julia 1.8이 필요합니다.

source

Workflow for Testing Packages

이전 섹션에서 사용 가능한 도구를 사용하여 패키지를 생성하고 테스트를 추가하는 잠재적인 워크플로우는 다음과 같습니다.

Generating an Example Package

이 워크플로우에서는 Example이라는 패키지를 생성할 것입니다:

pkg> generate Example
shell> cd Example
shell> mkdir test
pkg> activate .

Creating Sample Functions

테스트 패키지를 위한 가장 중요한 요구 사항은 테스트할 기능이 있는 것입니다. 이를 위해, 우리는 테스트할 수 있는 몇 가지 간단한 함수를 Example에 추가할 것입니다. 다음을 src/Example.jl에 추가하세요:

module Example

function greet()
    "Hello world!"
end

function simple_add(a, b)
    a + b
end

function type_multiply(a::Float64, b::Float64)
    a * b
end

export greet, simple_add, type_multiply

end

Creating a Test Environment

Example 패키지의 루트에서 test 디렉토리로 이동한 후, 그곳에서 새로운 환경을 활성화하고 Test 패키지를 환경에 추가합니다:

shell> cd test
pkg> activate .
(test) pkg> add Test

Testing Our Package

이제 Example에 테스트를 추가할 준비가 되었습니다. 실행할 테스트 세트를 포함하는 runtests.jl라는 파일을 test 디렉토리 내에 만드는 것이 표준 관행입니다. test 디렉토리 내에 해당 파일을 생성하고 다음 코드를 추가하세요:

using Example
using Test

@testset "Example tests" begin

    @testset "Math tests" begin
        include("math_tests.jl")
    end

    @testset "Greeting tests" begin
        include("greeting_tests.jl")
    end
end

우리는 math_tests.jlgreeting_tests.jl라는 두 개의 포함된 파일을 생성하고, 그 안에 몇 가지 테스트를 추가해야 합니다.

참고: test 환경의 Project.tomlExample을 추가할 필요가 없다는 점에 주목하세요. 이는 줄리아의 테스트 시스템의 장점으로, 여러분은 read about more here를 사용할 수 있습니다.

Writing Tests for math_tests.jl

Test.jl에 대한 우리의 지식을 바탕으로, math_tests.jl에 추가할 수 있는 몇 가지 예제 테스트는 다음과 같습니다:

@testset "Testset 1" begin
    @test 2 == simple_add(1, 1)
    @test 3.5 == simple_add(1, 2.5)
        @test_throws MethodError simple_add(1, "A")
        @test_throws MethodError simple_add(1, 2, 3)
end

@testset "Testset 2" begin
    @test 1.0 == type_multiply(1.0, 1.0)
        @test isa(type_multiply(2.0, 2.0), Float64)
    @test_throws MethodError type_multiply(1, 2.5)
end

Writing Tests for greeting_tests.jl

Test.jl에 대한 우리의 지식을 바탕으로, greeting_tests.jl에 추가할 수 있는 몇 가지 예제 테스트는 다음과 같습니다:

@testset "Testset 3" begin
    @test "Hello world!" == greet()
    @test_throws MethodError greet("Antonia")
end

Testing Our Package

이제 test에 테스트와 runtests.jl 스크립트를 추가했으므로, Example 패키지 환경의 루트로 돌아가서 Example 환경을 다시 활성화하여 Example 패키지를 테스트할 수 있습니다:

shell> cd ..
pkg> activate .

거기서 우리는 다음과 같이 테스트 스위트를 실행할 수 있습니다:

(Example) pkg> test
     Testing Example
      Status `/tmp/jl_Yngpvy/Project.toml`
  [fa318bd2] Example v0.1.0 `/home/src/Projects/tmp/errata/Example`
  [8dfed614] Test `@stdlib/Test`
      Status `/tmp/jl_Yngpvy/Manifest.toml`
  [fa318bd2] Example v0.1.0 `/home/src/Projects/tmp/errata/Example`
  [2a0f44e3] Base64 `@stdlib/Base64`
  [b77e0a4c] InteractiveUtils `@stdlib/InteractiveUtils`
  [56ddb016] Logging `@stdlib/Logging`
  [d6f4376e] Markdown `@stdlib/Markdown`
  [9a3f8284] Random `@stdlib/Random`
  [ea8e919c] SHA `@stdlib/SHA`
  [9e88b42a] Serialization `@stdlib/Serialization`
  [8dfed614] Test `@stdlib/Test`
     Testing Running tests...
Test Summary: | Pass  Total
Example tests |    9      9
     Testing Example tests passed

모든 것이 올바르게 진행되었다면, 위와 유사한 출력을 볼 수 있어야 합니다. Test.jl을 사용하면 패키지에 대해 더 복잡한 테스트를 추가할 수 있지만, 이는 이상적으로 개발자들이 자신이 만든 패키지의 테스트를 시작하는 방법을 안내하는 방향으로 나아가야 합니다.

Code Coverage

테스트 중 코드 커버리지 추적은 pkg> test --coverage 플래그를 사용하여 활성화할 수 있습니다 (또는 더 낮은 수준에서 --code-coverage 줄리아 인수를 사용하여). 이는 julia-runtest GitHub 액션에서 기본적으로 활성화되어 있습니다.

To evaluate coverage either manually inspect the .cov files that are generated beside the source files locally, or in CI use the julia-processcoverage GitHub action.

Julia 1.11

Julia 1.11부터는 패키지 사전 컴파일 단계에서 커버리지가 수집되지 않습니다.