Julia Language
Unit Testing
Suche…
Syntax
- @test [Ausdruck]
- @test_throws [Ausnahme] [Ausdruck]
- @testset "[Name]" begin; [Tests]; Ende
- Pkg.test ([Paket])
Bemerkungen
Die Standard-Bibliotheksdokumentation für Base.Test
umfasst zusätzliches Material, das über das in diesen Beispielen gezeigte hinausgeht.
Paket testen
Verwenden Sie zum Pkg.test
der Pkg.test
für ein Paket die Funktion Pkg.test
. Für ein Paket mit dem Namen MyPackage
der Befehl
julia> Pkg.test("MyPackage")
Eine erwartete Ausgabe wäre ähnlich
INFO: Computing test dependencies for MyPackage...
INFO: Installing BaseTestNext v0.2.2
INFO: Testing MyPackage
Test Summary: | Pass Total
Data | 66 66
Test Summary: | Pass Total
Monetary | 107 107
Test Summary: | Pass Total
Basket | 47 47
Test Summary: | Pass Total
Mixed | 13 13
Test Summary: | Pass Total
Data Access | 35 35
INFO: MyPackage tests passed
INFO: Removing BaseTestNext v0.2.2
Natürlich kann man nicht erwarten, dass es genau mit den oben genannten übereinstimmt, da verschiedene Pakete unterschiedliche Frameworks verwenden.
Dieser Befehl führt die Datei test/runtests.jl
des Pakets in einer sauberen Umgebung aus.
Mit können Sie alle installierten Pakete gleichzeitig testen
julia> Pkg.test()
Dies dauert jedoch normalerweise sehr lange.
Einen einfachen Test schreiben
Unit-Tests werden in der Datei test/runtests.jl
in einem Paket deklariert. Normalerweise beginnt diese Datei
using MyModule
using Base.Test
Die grundlegende @test
ist das @test
Makro. Dieses Makro ist eine Art Behauptung. Jeder boolesche Ausdruck kann im @test
Makro getestet werden:
@test 1 + 1 == 2
@test iseven(10)
@test 9 < 10 || 10 < 9
Wir können das @test
Makro in der REPL ausprobieren:
julia> using Base.Test
julia> @test 1 + 1 == 2
Test Passed
Expression: 1 + 1 == 2
Evaluated: 2 == 2
julia> @test 1 + 1 == 3
Test Failed
Expression: 1 + 1 == 3
Evaluated: 2 == 3
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test(::Base.Test.Returned, ::Expr) at ./test.jl:281
Das Testmakro kann fast überall verwendet werden, beispielsweise in Schleifen oder Funktionen:
# For positive integers, a number's square is at least as large as the number
for i in 1:10
@test i^2 ≥ i
end
# Test that no two of a, b, or c share a prime factor
function check_pairwise_coprime(a, b, c)
@test gcd(a, b) == 1
@test gcd(a, c) == 1
@test gcd(b, c) == 1
end
check_pairwise_coprime(10, 23, 119)
Test-Set schreiben
In Version v0.5 sind Testsätze in die Standardbibliothek Base.Test
, und Sie müssen keine besonderen using Base.Test
(außer using Base.Test
), um sie verwenden zu können.
Test - Sets sind nicht Teil der von Julia v0.4 Base.Test
Bibliothek. Stattdessen müssen Sie REQUIRE
das BaseTestNext
Modul, und fügen Sie using BaseTestNext
zu Ihrer Datei. Um beide Versionen 0.4 und 0.5 zu unterstützen, können Sie verwenden
if VERSION ≥ v"0.5.0-dev+7720"
using Base.Test
else
using BaseTestNext
const Test = BaseTestNext
end
Es ist hilfreich, verwandte @test
Tests in einem Test-Set zusammenzufassen. Zusätzlich zu einer übersichtlicheren Testorganisation bieten Testsets eine bessere Ausgabe und mehr Anpassbarkeit.
Um ein @test
zu definieren, wickeln Sie einfach eine beliebige Anzahl von @test
mit einem @testset
Block ein:
@testset "+" begin
@test 1 + 1 == 2
@test 2 + 2 == 4
end
@testset "*" begin
@test 1 * 1 == 1
@test 2 * 2 == 4
end
Beim Ausführen dieser Testsätze wird die folgende Ausgabe gedruckt:
Test Summary: | Pass Total
+ | 2 2
Test Summary: | Pass Total
* | 2 2
Selbst wenn ein Testsatz einen fehlerhaften Test enthält, wird der gesamte Testsatz vollständig ausgeführt und die Fehler werden aufgezeichnet und gemeldet:
@testset "-" begin
@test 1 - 1 == 0
@test 2 - 2 == 1
@test 3 - () == 3
@test 4 - 4 == 0
end
Das Ausführen dieses Testsatzes führt zu
-: Test Failed
Expression: 2 - 2 == 1
Evaluated: 0 == 1
in record(::Base.Test.DefaultTestSet, ::Base.Test.Fail) at ./test.jl:428
...
-: Error During Test
Test threw an exception of type MethodError
Expression: 3 - () == 3
MethodError: no method matching -(::Int64, ::Tuple{})
...
Test Summary: | Pass Fail Error Total
- | 2 1 1 4
ERROR: Some tests did not pass: 2 passed, 1 failed, 1 errored, 0 broken.
...
Testsätze können geschachtelt werden, um eine beliebig tiefe Organisation zu ermöglichen
@testset "Int" begin
@testset "+" begin
@test 1 + 1 == 2
@test 2 + 2 == 4
end
@testset "-" begin
@test 1 - 1 == 0
end
end
Wenn die Tests erfolgreich sind, werden nur die Ergebnisse für den äußersten Testsatz angezeigt:
Test Summary: | Pass Total
Int | 3 3
Wenn die Tests fehlschlagen, wird ein Drilldown in den genauen Testsatz und den Test, der den Fehler verursacht, gemeldet.
Das @testset
Makro kann mit einer for
Schleife verwendet werden , um viele Testsätze gleichzeitig zu erstellen:
@testset for i in 1:5
@test 2i == i + i
@test i^2 == i * i
@test i ÷ i == 1
end
welche berichtet
Test Summary: | Pass Total
i = 1 | 3 3
Test Summary: | Pass Total
i = 2 | 3 3
Test Summary: | Pass Total
i = 3 | 3 3
Test Summary: | Pass Total
i = 4 | 3 3
Test Summary: | Pass Total
i = 5 | 3 3
Eine übliche Struktur besteht darin, äußere Testsätze Komponenten oder Typen prüfen zu lassen. Innerhalb dieser äußeren Testsätze wird durch das innere Testverhalten das Testverhalten festgelegt. Angenommen, wir haben einen Typ UniversalSet
mit einer Einzelinstanz erstellt, die alles enthält. Bevor wir den Typ überhaupt implementieren, können wir testgetriebene Entwicklungsprinzipien anwenden und die Tests implementieren:
@testset "UniversalSet" begin
U = UniversalSet.instance
@testset "egal/equal" begin
@test U === U
@test U == U
end
@testset "in" begin
@test 1 in U
@test "Hello World" in U
@test Int in U
@test U in U
end
@testset "subset" begin
@test Set() ⊆ U
@test Set(["Hello World"]) ⊆ U
@test Set(1:10) ⊆ U
@test Set([:a, 2.0, "w", Set()]) ⊆ U
@test U ⊆ U
end
end
Wir können dann mit der Implementierung unserer Funktionalität beginnen, bis unsere Tests bestanden sind. Der erste Schritt ist die Definition des Typs:
immutable UniversalSet <: Base.AbstractSet end
Nur zwei unserer Tests bestehen im Moment. Wir können implementieren in
:
immutable UniversalSet <: Base.AbstractSet end
Base.in(x, ::UniversalSet) = true
Damit sind auch einige unserer Teilmengenprüfungen erfolgreich. Das issubset
( ⊆
) -Fallback funktioniert jedoch nicht für UniversalSet
, da das Fallback versucht, Elemente zu durchlaufen, was wir nicht tun können. Wir können einfach eine Spezialisierung definieren, die issubset
, dass issubset
für jede Menge true
:
immutable UniversalSet <: Base.AbstractSet end
Base.in(x, ::UniversalSet) = true
Base.issubset(x::Base.AbstractSet, ::UniversalSet) = true
Und jetzt bestehen alle unsere Tests!
Ausnahmen testen
Ausnahmen, die während eines Tests aufgetreten sind, schlagen fehl, und wenn der Test nicht in einem Testsatz enthalten ist, beenden Sie den Testmotor. Dies ist normalerweise eine gute Sache, da in den meisten Fällen Ausnahmen nicht das gewünschte Ergebnis sind. Aber manchmal möchte man speziell testen, dass eine bestimmte Ausnahme ausgelöst wird. Das Makro @test_throws
erleichtert dies.
julia> @test_throws BoundsError [1, 2, 3][4]
Test Passed
Expression: ([1,2,3])[4]
Thrown: BoundsError
Wenn die falsche Ausnahme ausgelöst wird, @test_throws
immer noch fehl:
julia> @test_throws TypeError [1, 2, 3][4]
Test Failed
Expression: ([1,2,3])[4]
Expected: TypeError
Thrown: BoundsError
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test_throws(::Base.Test.Threw, ::Expr, ::Type{T}) at ./test.jl:329
und wenn keine Ausnahme ausgelöst wird, @test_throws
ebenfalls fehl:
julia> @test_throws BoundsError [1, 2, 3, 4][4]
Test Failed
Expression: ([1,2,3,4])[4]
Expected: BoundsError
No exception thrown
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test_throws(::Base.Test.Returned, ::Expr, ::Type{T}) at ./test.jl:329
Prüfung des Fließpunkts Ungefährer Gleichwert
Was ist der Deal mit dem Folgenden?
julia> @test 0.1 + 0.2 == 0.3
Test Failed
Expression: 0.1 + 0.2 == 0.3
Evaluated: 0.30000000000000004 == 0.3
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test(::Base.Test.Returned, ::Expr) at ./test.jl:281
Der Fehler wird durch die Tatsache verursacht, dass keine der Werte 0.1
, 0.2
und 0.3
im Computer als genau die Werte dargestellt werden - 1//10
, 2//10
und 3//10
. Sie werden stattdessen durch sehr nahe Werte angeglichen. Wie aus dem obigen Testfehler hervorgeht, kann das Ergebnis beim Hinzufügen von zwei Näherungen eine etwas schlechtere Näherung sein, als dies möglich ist. Es gibt noch viel mehr zu diesem Thema , das hier nicht behandelt werden kann.
Aber wir haben kein Glück! Um zu testen, ob die Kombination aus Rundung auf eine Fließkommazahl und Fließkomma-Arithmetik annähernd korrekt ist, auch wenn sie nicht genau ist, können wir die Funktion isapprox
(die dem Operator ≈
) verwenden. So können wir unseren Test als neu schreiben
julia> @test 0.1 + 0.2 ≈ 0.3
Test Passed
Expression: 0.1 + 0.2 ≈ 0.3
Evaluated: 0.30000000000000004 isapprox 0.3
Wenn unser Code völlig falsch war, wird der Test das natürlich trotzdem feststellen:
julia> @test 0.1 + 0.2 ≈ 0.4
Test Failed
Expression: 0.1 + 0.2 ≈ 0.4
Evaluated: 0.30000000000000004 isapprox 0.4
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test(::Base.Test.Returned, ::Expr) at ./test.jl:281
Die isapprox
Funktion verwendet Heuristiken basierend auf der Größe der Zahlen und der Genauigkeit des Gleitkommatyps, um die zu tolerierende Fehlermenge zu bestimmen. Es ist nicht für alle Situationen geeignet, funktioniert aber in den meisten isapprox
und erspart viel Aufwand bei der Implementierung der eigenen Version von isapprox
.