Unit Testing

Testing Base Julia

Julia befindet sich in einer schnellen Entwicklung und verfügt über eine umfangreiche Test-Suite, um die Funktionalität auf mehreren Plattformen zu überprüfen. Wenn Sie Julia aus dem Quellcode erstellen, können Sie diese Test-Suite mit make test ausführen. Bei einer Binärinstallation können Sie die Test-Suite mit Base.runtests() ausführen.

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

Führen Sie die in tests aufgeführten Julia-Einheitstests aus, die entweder eine Zeichenfolge oder ein Array von Zeichenfolgen sein können, unter Verwendung von ncores Prozessoren. Wenn exit_on_error auf false gesetzt ist, werden bei einem fehlgeschlagenen Test alle verbleibenden Tests in anderen Dateien weiterhin ausgeführt; andernfalls werden sie verworfen, wenn exit_on_error == true. Wenn revise auf true gesetzt ist, wird das Revise-Paket verwendet, um alle Änderungen an Base oder an den Standardbibliotheken vor der Ausführung der Tests zu laden. Wenn ein Seed über das Schlüsselwort-Argument bereitgestellt wird, wird er verwendet, um den globalen RNG im Kontext, in dem die Tests ausgeführt werden, zu initialisieren; andernfalls wird der Seed zufällig gewählt.

source

Basic Unit Tests

Das Test-Modul bietet einfache Unit-Testing-Funktionalität. Unit-Testing ist eine Möglichkeit, um zu überprüfen, ob Ihr Code korrekt ist, indem Sie sicherstellen, dass die Ergebnisse Ihren Erwartungen entsprechen. Es kann hilfreich sein, um sicherzustellen, dass Ihr Code nach Änderungen weiterhin funktioniert, und kann beim Entwickeln verwendet werden, um die Verhaltensweisen zu spezifizieren, die Ihr Code beim Abschluss haben sollte. Sie möchten möglicherweise auch die Dokumentation für adding tests to your Julia Package ansehen.

Einfache Unit-Tests können mit den @test und @test_throws Makros durchgeführt werden:

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

Testen Sie, ob der Ausdruck ex zu true evaluiert. Wenn er innerhalb eines @testset ausgeführt wird, geben Sie ein Pass Result zurück, wenn dies der Fall ist, ein Fail Result, wenn es false ist, und ein Error Result, wenn es nicht evaluiert werden konnte. Wenn es außerhalb eines @testset ausgeführt wird, werfen Sie stattdessen eine Ausnahme, anstatt Fail oder Error zurückzugeben.

Beispiele

julia> @test true
Test Bestanden

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

Die Form @test f(args...) key=val... ist äquivalent zu @test f(args..., key=val...), was nützlich sein kann, wenn der Ausdruck ein Aufruf mit Infix-Syntax ist, wie z. B. ungefähre Vergleiche:

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

Dies ist äquivalent zu dem unansehnlicheren Test @test ≈(π, 3.14, atol=0.01). Es ist ein Fehler, mehr als einen Ausdruck anzugeben, es sei denn, der erste ist ein Aufrufausdruck und der Rest sind Zuweisungen (k=v).

Sie können jeden Schlüssel für die key=val Argumente verwenden, außer für broken und skip, die im Kontext von @test spezielle Bedeutungen haben:

  • broken=cond zeigt einen Test an, der bestehen sollte, aber derzeit konsequent fehlschlägt, wenn cond==true. Tests, dass der Ausdruck ex zu false evaluiert oder eine Ausnahme verursacht. Gibt ein Broken Result zurück, wenn dies der Fall ist, oder ein Error Result, wenn der Ausdruck zu true evaluiert. Der reguläre @test ex wird evaluiert, wenn cond==false.
  • skip=cond kennzeichnet einen Test, der nicht ausgeführt werden sollte, aber in der Testzusammenfassungsberichterstattung als Broken enthalten sein sollte, wenn cond==true. Dies kann nützlich sein für Tests, die intermittierend fehlschlagen, oder Tests von noch nicht implementierten Funktionen. Der reguläre @test ex wird evaluiert, wenn cond==false.

Beispiele

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

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

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

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

Die Schlüsselwortargumente broken und skip erfordern mindestens Julia 1.7.

source
Test.@test_throwsMacro
@test_throws exception expr

Testet, dass der Ausdruck expr exception auslöst. Die Ausnahme kann entweder einen Typ, einen String, einen regulären Ausdruck oder eine Liste von Strings, die in der angezeigten Fehlermeldung vorkommen, eine Übereinstimmungsfunktion oder einen Wert angeben (der auf Gleichheit durch den Vergleich von Feldern getestet wird). Beachten Sie, dass @test_throws keine abschließende Schlüsselwortform unterstützt.

Julia 1.8

Die Möglichkeit, etwas anderes als einen Typ oder einen Wert als exception anzugeben, erfordert Julia v1.8 oder höher.

Beispiele

julia> @test_throws BoundsError [1, 2, 3][4]
Test bestanden
      Ausgelöst: BoundsError

julia> @test_throws DimensionMismatch [1, 2, 3] + [1, 2]
Test bestanden
      Ausgelöst: DimensionMismatch

julia> @test_throws "Versuchen Sie sqrt(Complex" sqrt(-1)
Test bestanden
     Nachricht: "DomainError mit -1.0:\nsqrt wurde mit einem negativen reellen Argument aufgerufen, gibt jedoch nur ein komplexes Ergebnis zurück, wenn es mit einem komplexen Argument aufgerufen wird. Versuchen Sie sqrt(Complex(x))."

Im letzten Beispiel hätte anstelle der Übereinstimmung mit einem einzelnen String alternativ auch Folgendes verwendet werden können:

  • ["Versuchen", "Complex"] (eine Liste von Strings)
  • r"Versuchen sqrt\([Cc]omplex" (ein regulärer Ausdruck)
  • str -> occursin("complex", str) (eine Übereinstimmungsfunktion)
source

Zum Beispiel, nehmen wir an, wir möchten überprüfen, ob unsere neue Funktion foo(x) wie erwartet funktioniert:

julia> using Test

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

Wenn die Bedingung wahr ist, wird ein Pass zurückgegeben:

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

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

Wenn die Bedingung falsch ist, wird ein Fail zurückgegeben und eine Ausnahme ausgelöst:

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

ERROR: There was an error during testing

Wenn die Bedingung nicht ausgewertet werden konnte, weil eine Ausnahme ausgelöst wurde, was in diesem Fall geschieht, weil length für Symbole nicht definiert ist, wird ein Error-Objekt zurückgegeben und eine Ausnahme ausgelöst:

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

Wenn wir erwarten, dass die Auswertung eines Ausdrucks eine Ausnahme auslösen sollte, können wir @test_throws verwenden, um zu überprüfen, dass dies geschieht:

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

Working with Test Sets

Typischerweise wird eine große Anzahl von Tests verwendet, um sicherzustellen, dass Funktionen über eine Reihe von Eingaben korrekt arbeiten. Im Falle eines fehlgeschlagenen Tests ist das Standardverhalten, sofort eine Ausnahme auszulösen. Es ist jedoch normalerweise vorzuziehen, zuerst die restlichen Tests auszuführen, um ein besseres Bild davon zu bekommen, wie viele Fehler im getesteten Code vorhanden sind.

Note

Der @testset erstellt einen eigenen lokalen Geltungsbereich, wenn die Tests darin ausgeführt werden.

Das @testset-Makro kann verwendet werden, um Tests in Sätze zu gruppieren. Alle Tests in einem Testsatz werden ausgeführt, und am Ende des Testsatzes wird eine Zusammenfassung gedruckt. Wenn einer der Tests fehlgeschlagen ist oder aufgrund eines Fehlers nicht ausgewertet werden konnte, wird der Testsatz dann eine TestSetException auslösen.

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

Mit begin/end oder Funktionsaufruf

Wenn @testset verwendet wird, mit begin/end oder einem einzelnen Funktionsaufruf, startet das Makro ein neues Testset, in dem der gegebene Ausdruck ausgewertet wird.

Wenn kein benutzerdefinierter Testset-Typ angegeben ist, wird standardmäßig ein DefaultTestSet erstellt. DefaultTestSet zeichnet alle Ergebnisse auf und wirft, falls es Fails oder Errors gibt, am Ende des obersten (nicht geschachtelten) Testsets eine Ausnahme, zusammen mit einer Zusammenfassung der Testergebnisse.

Jeder benutzerdefinierte Testset-Typ (Untertyp von AbstractTestSet) kann angegeben werden und wird auch für alle geschachtelten @testset-Aufrufe verwendet. Die angegebenen Optionen werden nur auf das Testset angewendet, in dem sie angegeben sind. Der Standard-Testset-Typ akzeptiert drei boolesche Optionen:

  • verbose: wenn true, wird die Ergebnisszusammenfassung der geschachtelten Testsets angezeigt, selbst wenn sie alle bestehen (der Standardwert ist false).
  • showtiming: wenn true, wird die Dauer jedes angezeigten Testsets angezeigt (der Standardwert ist true).
  • failfast: wenn true, führt jeder Testfehler oder Fehler dazu, dass das Testset und alle untergeordneten Testsets sofort zurückgegeben werden (der Standardwert ist false). Dies kann auch global über die Umgebungsvariable JULIA_TEST_FAILFAST festgelegt werden.
Julia 1.8

@testset test_func() erfordert mindestens Julia 1.8.

Julia 1.9

failfast erfordert mindestens Julia 1.9.

Der Beschreibungsstring akzeptiert Interpolation von den Schleifenindizes. Wenn keine Beschreibung angegeben ist, wird eine basierend auf den Variablen erstellt. Wenn ein Funktionsaufruf angegeben ist, wird dessen Name verwendet. Explizite Beschreibungsstrings überschreiben dieses Verhalten.

Standardmäßig gibt das @testset-Makro das Testset-Objekt selbst zurück, obwohl dieses Verhalten in anderen Testset-Typen angepasst werden kann. Wenn eine for-Schleife verwendet wird, sammelt und gibt das Makro eine Liste der Rückgabewerte der finish-Methode zurück, die standardmäßig eine Liste der in jeder Iteration verwendeten Testset-Objekte zurückgibt.

Vor der Ausführung des Körpers eines @testset erfolgt ein impliziter Aufruf von Random.seed!(seed), wobei seed der aktuelle Seed des globalen RNG ist. Darüber hinaus wird nach der Ausführung des Körpers der Zustand des globalen RNG auf den Zustand vor dem @testset zurückgesetzt. Dies soll die Reproduzierbarkeit im Falle eines Fehlers erleichtern und nahtlose Umstellungen von @testsets unabhängig von ihren Nebenwirkungen auf den Zustand des globalen RNG ermöglichen.

Beispiele

julia> @testset "trigonometric identities" begin
           θ = 2/3*π
           @test sin(-θ) ≈ -sin(θ)
           @test cos(-θ) ≈ cos(θ)
           @test sin(2θ) ≈ 2*sin(θ)*cos(θ)
           @test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2
       end;
Test Summary:            | Pass  Total  Time
trigonometric identities |    4      4  0.2s

@testset for

Wenn @testset for verwendet wird, startet das Makro einen neuen Test für jede Iteration der bereitgestellten Schleife. Die Semantik jedes Testsets ist ansonsten identisch mit der des begin/end-Falls (als ob es für jede Schleifeniteration verwendet wird).

@testset let

Wenn @testset let verwendet wird, startet das Makro ein transparentes Testset, wobei das gegebene Objekt als Kontextobjekt zu jedem fehlerhaften Test hinzugefügt wird, der darin enthalten ist. Dies ist nützlich, wenn eine Reihe verwandter Tests an einem größeren Objekt durchgeführt wird und es wünschenswert ist, dieses größere Objekt anzuzeigen, wenn einer der einzelnen Tests fehlschlägt. Transparente Testsets führen nicht zu zusätzlichen Ebenen der Verschachtelung in der Testset-Hierarchie und werden direkt an das übergeordnete Testset weitergegeben (mit dem Kontextobjekt, das an alle fehlerhaften Tests angehängt wird).

Julia 1.9

@testset let erfordert mindestens Julia 1.9.

Julia 1.10

Mehrere let-Zuweisungen werden seit Julia 1.10 unterstützt.

Beispiele

julia> @testset let logi = log(im)
           @test imag(logi) == π/2
           @test !iszero(real(logi))
       end
Test Failed at none:3
  Expression: !(iszero(real(logi)))
     Context: logi = 0.0 + 1.5707963267948966im

ERROR: Es gab einen Fehler während des Tests

julia> @testset let logi = log(im), op = !iszero
           @test imag(logi) == π/2
           @test op(real(logi))
       end
Test Failed at none:3
  Expression: op(real(logi))
     Context: logi = 0.0 + 1.5707963267948966im
              op = !iszero

ERROR: Es gab einen Fehler während des Tests
source
Test.TestSetExceptionType
TestSetException

Wird ausgelöst, wenn ein Testset abgeschlossen ist und nicht alle Tests bestanden wurden.

source

Wir können unsere Tests für die foo(x)-Funktion in einem Testset unterbringen:

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

Test-Sets können auch geschachtelt werden:

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

Sowie Funktionen aufrufen:

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

Dies kann verwendet werden, um die Faktorisierung von Testsets zu ermöglichen, was es einfacher macht, einzelne Testsets auszuführen, indem stattdessen die zugehörigen Funktionen aufgerufen werden. Beachten Sie, dass im Fall von Funktionen das Testset den Namen der aufgerufenen Funktion erhält. Falls ein geschachteltes Testset keine Fehler aufweist, wie hier geschehen, wird es in der Zusammenfassung ausgeblendet, es sei denn, die Option verbose=true wird übergeben:

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

Wenn wir einen Testfehler haben, werden nur die Details für die fehlgeschlagenen Testsets angezeigt:

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

Man kann das @test_logs Makro verwenden, um Protokollanweisungen zu testen, oder ein TestLogger verwenden.

Test.@test_logsMacro
@test_logs [log_patterns...] [keywords] Ausdruck

Sammeln Sie eine Liste von Protokolleinträgen, die durch Ausdruck generiert werden, indem Sie collect_test_logs verwenden, überprüfen Sie, ob sie mit der Sequenz log_patterns übereinstimmen, und geben Sie den Wert von Ausdruck zurück. Die keywords bieten eine einfache Filterung der Protokolleinträge: das min_level-Keyword steuert das minimale Protokollniveau, das für den Test gesammelt wird, das match_mode-Keyword definiert, wie das Matching durchgeführt wird (das Standard-:all überprüft, ob alle Protokolle und Muster paarweise übereinstimmen; verwenden Sie :any, um zu überprüfen, ob das Muster irgendwo in der Sequenz mindestens einmal übereinstimmt.)

Das nützlichste Protokollmuster ist ein einfaches Tupel der Form (level,message). Eine andere Anzahl von Tupel-Elementen kann verwendet werden, um andere Protokollmetadaten abzugleichen, die den Argumenten entsprechen, die an AbstractLogger über die Funktion handle_message übergeben werden: (level,message,module,group,id,file,line). Elemente, die vorhanden sind, werden standardmäßig paarweise mit den Feldern des Protokolleintrags unter Verwendung von == abgeglichen, wobei die Sonderfälle, dass Symbols für die Standardprotokollniveaus verwendet werden können, und Regexs im Muster die String- oder Symbolfelder mit occursin abgleichen.

Beispiele

Betrachten Sie eine Funktion, die eine Warnung protokolliert und mehrere Debug-Nachrichten:

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

Wir können die Info-Nachricht testen mit

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

Wenn wir auch die Debug-Nachrichten testen möchten, müssen diese mit dem min_level-Keyword aktiviert werden:

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

Wenn Sie testen möchten, dass bestimmte Nachrichten generiert werden, während der Rest ignoriert wird, können Sie das Keyword match_mode=:any setzen:

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

Das Makro kann mit @test verkettet werden, um auch den zurückgegebenen Wert zu testen:

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

Wenn Sie auf das Fehlen von Warnungen testen möchten, können Sie die Angabe von Protokollmustern weglassen und das min_level entsprechend setzen:

# testen, dass der Ausdruck keine Nachrichten protokolliert, wenn das Logger-Niveau warn ist:
@test_logs min_level=Logging.Warn @info("Some information") # besteht
@test_logs min_level=Logging.Warn @warn("Some information") # schlägt fehl

Wenn Sie das Fehlen von Warnungen (oder Fehlermeldungen) in stderr testen möchten, die nicht durch @warn generiert werden, siehe @test_nowarn. ```

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

Erstellen Sie einen TestLogger, der protokollierte Nachrichten in seinem Feld logs::Vector{LogRecord} erfasst.

Setzen Sie min_level, um das LogLevel zu steuern, catch_exceptions, um festzulegen, ob Ausnahmen, die im Rahmen der Protokollereigniserzeugung ausgelöst werden, erfasst werden sollen, und respect_maxlog, um festzulegen, ob die Konvention befolgt werden soll, Nachrichten mit maxlog=n für eine ganze Zahl n höchstens n Mal zu protokollieren.

Siehe auch: LogRecord.

Beispiele

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

Speichert die Ergebnisse eines einzelnen Protokollereignisses. Felder:

  • level: das LogLevel der Protokollnachricht
  • message: der textliche Inhalt der Protokollnachricht
  • _module: das Modul des Protokollereignisses
  • group: die Protokollgruppe (standardmäßig der Name der Datei, die das Protokollereignis enthält)
  • id: die ID des Protokollereignisses
  • file: die Datei, die das Protokollereignis enthält
  • line: die Zeile innerhalb der Datei des Protokollereignisses
  • kwargs: alle Schlüsselwortargumente, die an das Protokollereignis übergeben wurden
source

Other Test Macros

Da Berechnungen mit Fließkommawerten ungenau sein können, können Sie ungefähre Gleichheitsprüfungen entweder mit @test a ≈ b durchführen (wobei , eingegeben über die Tab-Vervollständigung von \approx, die isapprox-Funktion ist) oder verwenden Sie 4d61726b646f776e2e436f64652822222c20226973617070726f782229_40726566 direkt.

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

Sie können relative und absolute Toleranzen festlegen, indem Sie die Schlüsselwortargumente rtol und atol von isapprox entsprechend nach dem -Vergleich einstellen:

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

Beachten Sie, dass dies kein spezifisches Merkmal von ist, sondern vielmehr ein allgemeines Merkmal des @test-Makros: @test a <op> b key=val wird vom Makro in @test op(a, b, key=val) umgewandelt. Es ist jedoch besonders nützlich für -Tests.

Test.@inferredMacro
@inferred [ErlaubterTyp] f(x)

Testet, ob der Aufrufausdruck f(x) einen Wert des gleichen Typs zurückgibt, der vom Compiler abgeleitet wurde. Es ist nützlich, um die Typstabilität zu überprüfen.

f(x) kann jeder Aufrufausdruck sein. Gibt das Ergebnis von f(x) zurück, wenn die Typen übereinstimmen, und ein Error Result, wenn unterschiedliche Typen gefunden werden.

Optional lockert ErlaubterTyp den Test, indem er besteht, wenn entweder der Typ von f(x) mit dem abgeleiteten Typ modulo ErlaubterTyp übereinstimmt oder wenn der Rückgabetyp ein Subtyp von ErlaubterTyp ist. Dies ist nützlich, wenn man die Typstabilität von Funktionen testet, die eine kleine Vereinigung wie Union{Nothing, T} oder Union{Missing, T} zurückgeben.

julia> f(a) = a > 1 ? 1 : 1.0
f (generische Funktion mit 1 Methode)

julia> typeof(f(2))
Int64

julia> @code_warntype f(2)
MethodInstance für f(::Int64)
  von f(a) @ Main none:1
Argumente
  #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)
FEHLER: Rückgabetyp Int64 stimmt nicht mit abgeleitetem Rückgabetyp Union{Float64, Int64} überein
[...]

julia> @inferred max(1, 2)
2

julia> g(a) = a < 10 ? missing : 1.0
g (generische Funktion mit 1 Methode)

julia> @inferred g(20)
FEHLER: Rückgabetyp Float64 stimmt nicht mit abgeleitetem Rückgabetyp Union{Missing, Float64} überein
[...]

julia> @inferred Missing g(20)
1.0

julia> h(a) = a < 10 ? missing : f(a)
h (generische Funktion mit 1 Methode)

julia> @inferred Missing h(20)
FEHLER: Rückgabetyp Int64 stimmt nicht mit abgeleitetem Rückgabetyp Union{Missing, Float64, Int64} überein
[...]
source
Test.@test_deprecatedMacro
@test_deprecated [pattern] expression

Wenn --depwarn=yes gesetzt ist, teste, dass expression eine Abwertungswarnung ausgibt und gib den Wert von expression zurück. Der Lognachricht-String wird mit pattern verglichen, das standardmäßig auf r"deprecated"i gesetzt ist.

Wenn --depwarn=no gesetzt ist, gib einfach das Ergebnis der Ausführung von expression zurück. Wenn --depwarn=error gesetzt ist, überprüfe, dass eine ErrorException ausgelöst wird.

Beispiele

# Abgewertet in julia 0.7
@test_deprecated num2hex(1)

# Der zurückgegebene Wert kann durch Verketten mit @test getestet werden:
@test (@test_deprecated num2hex(1)) == "0000000000000001"
source
Test.@test_warnMacro
@test_warn msg expr

Testen Sie, ob die Auswertung von expr zu einer stderr-Ausgabe führt, die den msg-String enthält oder mit dem msg-Regulären Ausdruck übereinstimmt. Wenn msg eine boolesche Funktion ist, wird getestet, ob msg(output) true zurückgibt. Wenn msg ein Tupel oder Array ist, wird überprüft, ob die Fehlermeldung jedes Element in msg enthält/übereinstimmt. Gibt das Ergebnis der Auswertung von expr zurück.

Siehe auch @test_nowarn, um das Fehlen von Fehlermeldungen zu überprüfen.

Hinweis: Warnungen, die von @warn erzeugt werden, können mit diesem Makro nicht getestet werden. Verwenden Sie stattdessen @test_logs.

source
Test.@test_nowarnMacro
@test_nowarn expr

Testen Sie, ob die Auswertung von expr zu leerem stderr Ausgabe (keine Warnungen oder andere Nachrichten) führt. Gibt das Ergebnis der Auswertung von expr zurück.

Hinweis: Das Fehlen von Warnungen, die von @warn erzeugt werden, kann mit diesem Makro nicht getestet werden. Verwenden Sie stattdessen @test_logs.

source

Broken Tests

Wenn ein Test konstant fehlschlägt, kann er so geändert werden, dass das @test_broken-Makro verwendet wird. Dies kennzeichnet den Test als Broken, wenn der Test weiterhin fehlschlägt, und warnt den Benutzer über einen Error, wenn der Test erfolgreich ist.

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

Kennzeichnet einen Test, der bestehen sollte, aber derzeit konstant fehlschlägt. Testet, ob der Ausdruck ex zu false evaluiert oder eine Ausnahme verursacht. Gibt ein Broken Result zurück, wenn dies der Fall ist, oder ein Error Result, wenn der Ausdruck zu true evaluiert. Dies entspricht @test ex broken=true.

Die Form @test_broken f(args...) key=val... funktioniert wie bei dem @test-Makro.

Beispiele

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 ist ebenfalls verfügbar, um einen Test ohne Auswertung zu überspringen, zählt jedoch den übersprungenen Test in der Testberichterstattung. Der Test wird nicht ausgeführt, gibt jedoch ein Broken Result zurück.

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

Markiert einen Test, der nicht ausgeführt werden soll, aber in der Testzusammenfassung als Broken aufgeführt werden sollte. Dies kann nützlich sein für Tests, die sporadisch fehlschlagen, oder Tests von noch nicht implementierten Funktionen. Dies entspricht @test ex skip=true.

Die Form @test_skip f(args...) key=val... funktioniert wie bei dem @test-Makro.

Beispiele

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

Alle Tests erzeugen ein Ergebnisobjekt. Dieses Objekt kann gespeichert werden oder auch nicht, abhängig davon, ob der Test Teil eines Testsets ist.

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

Die Testbedingung war wahr, d.h. der Ausdruck wurde als wahr ausgewertet oder die korrekte Ausnahme wurde ausgelöst.

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

Die Testbedingung war falsch, d.h. der Ausdruck wurde als falsch ausgewertet oder die korrekte Ausnahme wurde nicht ausgelöst.

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

Die Testbedingung konnte aufgrund einer Ausnahme nicht ausgewertet werden oder sie wurde auf etwas anderes als ein Bool ausgewertet. Im Fall von @test_broken wird es verwendet, um anzuzeigen, dass ein unerwartetes Pass Result aufgetreten ist.

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

Die Testbedingung ist das erwartete (fehlgeschlagene) Ergebnis eines fehlerhaften Tests oder wurde ausdrücklich mit @test_skip übersprungen.

source

Creating Custom AbstractTestSet Types

Pakete können ihre eigenen AbstractTestSet-Untertypen erstellen, indem sie die Methoden record und finish implementieren. Der Untertyp sollte einen Konstruktor mit einem Argument haben, der einen Beschreibungsstring entgegennimmt, wobei alle Optionen als Schlüsselwortargumente übergeben werden.

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

Ein Ergebnis zu einem Testset aufzeichnen. Diese Funktion wird von der @testset-Infrastruktur jedes Mal aufgerufen, wenn ein enthaltenes @test-Makro abgeschlossen ist, und erhält das Testergebnis (das ein Error sein könnte). Dies wird auch mit einem Error aufgerufen, wenn eine Ausnahme innerhalb des Testblocks, aber außerhalb eines @test-Kontexts ausgelöst wird.

source
Test.finishFunction
finish(ts::AbstractTestSet)

Führen Sie alle erforderlichen Abschlussverarbeitungen für das gegebene Testset durch. Dies wird von der @testset-Infrastruktur aufgerufen, nachdem ein Testblock ausgeführt wurde.

Benutzerdefinierte AbstractTestSet-Untertypen sollten record auf ihrem übergeordneten Element (sofern vorhanden) aufrufen, um sich dem Baum der Testergebnisse hinzuzufügen. Dies könnte wie folgt implementiert werden:

if get_testset_depth() != 0
    # Fügen Sie dieses Testset dem übergeordneten Testset hinzu
    parent_ts = get_testset()
    record(parent_ts, self)
    return self
end
source

Test übernimmt die Verantwortung für die Verwaltung eines Stapels von geschachtelten Testsets während ihrer Ausführung, aber die Ergebnisakkumulation liegt in der Verantwortung des AbstractTestSet-Subtyps. Sie können auf diesen Stapel mit den Methoden get_testset und get_testset_depth zugreifen. Beachten Sie, dass diese Funktionen nicht exportiert sind.

Test.get_testsetFunction
get_testset()

Rufen Sie das aktive Testset aus dem lokalen Speicher des Tasks ab. Wenn kein aktives Testset vorhanden ist, verwenden Sie das Standard-Testset als Fallback.

source
Test.get_testset_depthFunction
get_testset_depth()

Gibt die Anzahl der aktiven Testsets zurück, ohne das Standard-Testset einzuschließen.

source

Test stellt auch sicher, dass geschachtelte @testset-Aufrufe denselben AbstractTestSet-Untertyp wie ihr übergeordnetes Element verwenden, es sei denn, dies wird ausdrücklich festgelegt. Es propagiert keine Eigenschaften des Testsets. Das Verhalten der Optionenvererbung kann von Paketen mithilfe der Stapelinfrastruktur, die Test bereitstellt, implementiert werden.

Die Definition eines grundlegenden AbstractTestSet-Subtyps könnte folgendermaßen aussehen:

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

Und die Verwendung dieses Testsets sieht folgendermaßen aus:

@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

Um ein benutzerdefiniertes Testset zu verwenden und die aufgezeichneten Ergebnisse als Teil eines äußeren Standard-Testsets auszugeben, definieren Sie auch Test.get_test_counts. Das könnte so aussehen:

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

Hält den Zustand für das rekursive Sammeln der Ergebnisse eines Testsets zu Anzeigezwecken.

Felder:

  • customized: Ob die Funktion get_test_counts für das AbstractTestSet angepasst wurde, für das dieses Zählobjekt gedacht ist. Wenn eine benutzerdefinierte Methode definiert wurde, immer true an den Konstruktor übergeben.
  • passes: Die Anzahl der bestandenen @test-Aufrufe.
  • fails: Die Anzahl der fehlgeschlagenen @test-Aufrufe.
  • errors: Die Anzahl der fehlerhaften @test-Aufrufe.
  • broken: Die Anzahl der defekten @test-Aufrufe.
  • passes: Die kumulierte Anzahl der bestandenen @test-Aufrufe.
  • fails: Die kumulierte Anzahl der fehlgeschlagenen @test-Aufrufe.
  • errors: Die kumulierte Anzahl der fehlerhaften @test-Aufrufe.
  • broken: Die kumulierte Anzahl der defekten @test-Aufrufe.
  • duration: Die Gesamtdauer, die das betreffende AbstractTestSet lief, als formatierter String.
source
Test.get_test_countsFunction

" gettestcounts(::AbstractTestSet) -> TestCounts

Rekursive Funktion, die die Anzahl der Testergebnisse jedes Typs direkt im Testset zählt und die Gesamtzahlen über die untergeordneten Testsets summiert.

Benutzerdefinierte AbstractTestSet sollten diese Funktion implementieren, um ihre Gesamtzahlen zu zählen und zusammen mit DefaultTestSet anzuzeigen.

Wenn dies für ein benutzerdefiniertes TestSet nicht implementiert ist, fällt die Ausgabe auf die Berichterstattung von x für Fehler und ?s für die Dauer zurück.

source
Test.format_durationFunction
format_duration(::AbstractTestSet)

Gibt einen formatierten String zurück, um die Dauer anzuzeigen, für die das Testset ausgeführt wurde.

Wenn nicht definiert, fällt es auf "?s" zurück.

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

Druckt die Ergebnisse eines AbstractTestSet als formatierte Tabelle aus.

depth_pad bezieht sich darauf, wie viel Padding vor allen Ausgaben hinzugefügt werden soll.

Wird innerhalb von Test.finish aufgerufen, wenn das finishte Testset das oberste Testset ist.

source

Test utilities

Test.GenericArrayType

Das GenericArray kann verwendet werden, um generische Array-APIs zu testen, die auf das AbstractArray-Interface abzielen, um sicherzustellen, dass Funktionen auch mit Array-Typen neben dem Standard-Array-Typ arbeiten können.

source
Test.GenericDictType

Der GenericDict kann verwendet werden, um generische Dict-APIs zu testen, die auf das AbstractDict-Interface abzielen, um sicherzustellen, dass Funktionen auch mit assoziativen Typen neben dem Standardtyp Dict arbeiten können.

source
Test.GenericOrderType

Der GenericOrder kann verwendet werden, um APIs auf ihre Unterstützung für generische geordnete Typen zu testen.

source
Test.GenericSetType

Das GenericSet kann verwendet werden, um generische Set-APIs zu testen, die auf das AbstractSet-Interface abzielen, um sicherzustellen, dass Funktionen auch mit Set-Typen neben den Standardtypen Set und BitSet funktionieren.

source
Test.GenericStringType

Der GenericString kann verwendet werden, um generische String-APIs zu testen, die auf das AbstractString-Interface abzielen, um sicherzustellen, dass Funktionen mit String-Typen neben dem Standard-String-Typ arbeiten können.

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

Gibt ein Vektor von (Method,Method)-Paaren von mehrdeutigen Methoden zurück, die in den angegebenen Modulen definiert sind. Verwenden Sie recursive=true, um in allen Untermodulen zu testen.

ambiguous_bottom steuert, ob Mehrdeutigkeiten, die nur durch Union{}-Typparameter ausgelöst werden, einbezogen werden; in den meisten Fällen möchten Sie dies wahrscheinlich auf false setzen. Siehe Base.isambiguous.

Siehe Test.detect_unbound_args für eine Erklärung von allowed_undefineds.

Julia 1.8

allowed_undefineds erfordert mindestens Julia 1.8.

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

Gibt einen Vektor von Methods zurück, die möglicherweise ungebundene Typparameter haben. Verwenden Sie recursive=true, um in allen Untermodulen zu testen.

Standardmäßig lösen alle undefinierten Symbole eine Warnung aus. Diese Warnung kann unterdrückt werden, indem eine Sammlung von GlobalRefs bereitgestellt wird, für die die Warnung übersprungen werden kann. Zum Beispiel, wenn Sie

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

setzen, würden Warnungen über Base.active_repl und Base.active_repl_backend unterdrückt.

Julia 1.8

allowed_undefineds erfordert mindestens Julia 1.8.

source

Workflow for Testing Packages

Mit den uns in den vorherigen Abschnitten zur Verfügung stehenden Werkzeugen, hier ist ein möglicher Arbeitsablauf zur Erstellung eines Pakets und zum Hinzufügen von Tests dazu.

Generating an Example Package

Für diesen Workflow werden wir ein Paket namens Example erstellen:

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

Creating Sample Functions

Die wichtigste Voraussetzung für das Testen eines Pakets ist, dass es Funktionalität zum Testen gibt. Dazu werden wir einige einfache Funktionen zu Example hinzufügen, die wir testen können. Fügen Sie Folgendes zu src/Example.jl hinzu:

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

Navigieren Sie im Stammverzeichnis des Example-Pakets zum test-Verzeichnis, aktivieren Sie dort eine neue Umgebung und fügen Sie das Test-Paket zur Umgebung hinzu:

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

Testing Our Package

Jetzt sind wir bereit, Tests zu Example hinzuzufügen. Es ist gängige Praxis, eine Datei im test-Verzeichnis mit dem Namen runtests.jl zu erstellen, die die Testsets enthält, die wir ausführen möchten. Legen Sie diese Datei im test-Verzeichnis an und fügen Sie den folgenden Code hinzu:

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

Wir müssen die beiden enthaltenen Dateien math_tests.jl und greeting_tests.jl erstellen und einige Tests hinzufügen.

Hinweis: Beachten Sie, dass wir nicht angeben mussten, Example in die test-Umgebung von Project.toml aufzunehmen. Dies ist ein Vorteil des Testsystems von Julia, dass Sie read about more here hinzufügen konnten.

Writing Tests for math_tests.jl

Mit unserem Wissen über Test.jl sind hier einige Beispieltests, die wir zu math_tests.jl hinzufügen könnten:

@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

Mit unserem Wissen über Test.jl sind hier einige Beispieltests, die wir zu greeting_tests.jl hinzufügen könnten:

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

Testing Our Package

Jetzt, da wir unsere Tests und unser runtests.jl-Skript im test-Verzeichnis hinzugefügt haben, können wir unser Example-Paket testen, indem wir zum Stammverzeichnis der Example-Paketumgebung zurückkehren und die Example-Umgebung reaktivieren:

shell> cd ..
pkg> activate .

Von dort aus können wir schließlich unser Testpaket wie folgt ausführen:

(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

Und wenn alles korrekt verlief, sollten Sie eine ähnliche Ausgabe wie oben sehen. Mit Test.jl können kompliziertere Tests für Pakete hinzugefügt werden, aber dies sollte idealerweise die Entwickler in die Richtung weisen, wie sie mit dem Testen ihrer eigenen erstellten Pakete beginnen können.

Code Coverage

Die Verfolgung der Codeabdeckung während der Tests kann mit dem pkg> test --coverage-Flag aktiviert werden (oder auf einer niedrigeren Ebene mit dem --code-coverage Julia-Argument). Dies ist standardmäßig im julia-runtest GitHub-Action aktiviert.

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

Seit Julia 1.11 wird die Abdeckung während der Phase der Paketvorkompilierung nicht mehr erfasst.