Julia Language
Testen van een eenheid
Zoeken…
Syntaxis
- @test [expr]
- @test_throws [Uitzondering] [expr]
- @testset "[naam]" begint; [Proeven]; einde
- Pkg.test ([package])
Opmerkingen
De standaardbibliotheekdocumentatie voor Base.Test
omvat aanvullend materiaal dat verder gaat dan in deze voorbeelden wordt getoond.
Een pakket testen
Gebruik de functie Pkg.test
om de eenheidstests voor een pakket uit te Pkg.test
. Voor een pakket met de naam MyPackage
zou het commando zijn
julia> Pkg.test("MyPackage")
Een verwachte output zou vergelijkbaar zijn met
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
hoewel het uiteraard niet te verwachten is dat het exact overeenkomt met het bovenstaande, omdat verschillende pakketten verschillende frameworks gebruiken.
Met deze opdracht wordt het bestand test/runtests.jl
het pakket in een schone omgeving uitgevoerd.
Men kan alle geïnstalleerde pakketten tegelijk testen met
julia> Pkg.test()
maar dit duurt meestal erg lang.
Een eenvoudige test schrijven
Eenheidstests worden in het pakket test/runtests.jl
. Meestal begint dit bestand
using MyModule
using Base.Test
De basiseenheid van testen is de macro @test
. Deze macro is als een soort bewering. Elke booleaanse uitdrukking kan worden getest in de @test
macro:
@test 1 + 1 == 2
@test iseven(10)
@test 9 < 10 || 10 < 9
We kunnen de macro @test
in de REPL uitproberen:
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
De testmacro kan vrijwel overal worden gebruikt, zoals in lussen of functies:
# 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)
Een testset schrijven
In versie v0.5 zijn Base.Test
ingebouwd in de standaardbibliotheek Base.Test
module en hoeft u niets speciaals te doen (behalve using Base.Test
) om ze te gebruiken.
Testsets maken geen deel uit van de Base.Test
bibliotheek van Julia v0.4. In plaats daarvan moet je REQUIRE
de BaseTestNext
module en voeg using BaseTestNext
om het bestand. Om zowel versie 0.4 als 0.5 te ondersteunen, zou je kunnen gebruiken
if VERSION ≥ v"0.5.0-dev+7720"
using Base.Test
else
using BaseTestNext
const Test = BaseTestNext
end
Het is handig om gerelateerde @test
samen te groeperen in een testset. Naast een duidelijkere testorganisatie bieden testsets een betere output en meer aanpasbaarheid.
Een testset definiëren wikkel elk aantal @test
s met @testset
block:
@testset "+" begin
@test 1 + 1 == 2
@test 2 + 2 == 4
end
@testset "*" begin
@test 1 * 1 == 1
@test 2 * 2 == 4
end
Als u deze testsets uitvoert, wordt de volgende uitvoer afgedrukt:
Test Summary: | Pass Total
+ | 2 2
Test Summary: | Pass Total
* | 2 2
Zelfs als een testset een falende test bevat, wordt de volledige testset uitgevoerd en worden de fouten geregistreerd en gerapporteerd:
@testset "-" begin
@test 1 - 1 == 0
@test 2 - 2 == 1
@test 3 - () == 3
@test 4 - 4 == 0
end
Het uitvoeren van deze testset resulteert in
-: 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.
...
Testsets kunnen worden genest, waardoor een willekeurige diepe organisatie mogelijk is
@testset "Int" begin
@testset "+" begin
@test 1 + 1 == 2
@test 2 + 2 == 4
end
@testset "-" begin
@test 1 - 1 == 0
end
end
Als de tests slagen, worden alleen de resultaten voor de buitenste testset weergegeven:
Test Summary: | Pass Total
Int | 3 3
Maar als de tests mislukken, wordt een analyse van de exacte testset en de test die de fout veroorzaakt gerapporteerd.
De macro @testset
kan worden gebruikt met een for
lus om meerdere @testset
tegelijk te maken:
@testset for i in 1:5
@test 2i == i + i
@test i^2 == i * i
@test i ÷ i == 1
end
welke rapporten
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
Een veel voorkomende structuur is om testonderdelen of types van buitenste testsets te hebben. Binnen deze buitentestsets, testgedrag binnentestsets. Stel bijvoorbeeld dat we een type UniversalSet
hebben gemaakt met een enkele instantie die alles bevat. Voordat we het type zelfs implementeren, kunnen we testgestuurde ontwikkelingsprincipes gebruiken en de tests implementeren:
@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
We kunnen dan beginnen met het implementeren van onze functionaliteit totdat deze onze tests doorstaat. De eerste stap is om het type te definiëren:
immutable UniversalSet <: Base.AbstractSet end
Slechts twee van onze tests slagen nu. We kunnen implementeren in
:
immutable UniversalSet <: Base.AbstractSet end
Base.in(x, ::UniversalSet) = true
Dit maakt ook enkele van onze subsets testen geslaagd. De issubset
( ⊆
) fallback werkt niet voor UniversalSet
, omdat de fallback probeert elementen te herhalen, wat we niet kunnen doen. We kunnen eenvoudig een specialisatie definiëren die issubset
return true
voor elke set:
immutable UniversalSet <: Base.AbstractSet end
Base.in(x, ::UniversalSet) = true
Base.issubset(x::Base.AbstractSet, ::UniversalSet) = true
En nu zijn al onze tests geslaagd!
Uitzonderingen testen
Uitzonderingen die worden aangetroffen tijdens het uitvoeren van een test zullen de test niet doorstaan, en als de test zich niet in een testset bevindt, beëindigt u de testmotor. Meestal is dit een goede zaak, omdat in de meeste situaties uitzonderingen niet het gewenste resultaat zijn. Maar soms wil men specifiek testen dat een bepaalde uitzondering wordt opgeworpen. De macro @test_throws
maakt dit mogelijk.
julia> @test_throws BoundsError [1, 2, 3][4]
Test Passed
Expression: ([1,2,3])[4]
Thrown: BoundsError
Als de verkeerde uitzondering wordt gegenereerd, @test_throws
nog steeds:
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
en als er geen uitzondering wordt gegenereerd, @test_throws
ook:
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
Drijvend punt testen Gelijkheid bij benadering
Wat is het probleem met het volgende?
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
De fout wordt veroorzaakt door het feit dat geen van de waarden 0.1
, 0.2
en 0.3
in de computer worden weergegeven als exact die waarden - 1//10
, 2//10
en 3//10
. In plaats daarvan worden ze benaderd door waarden die heel dichtbij zijn. Maar zoals te zien in de bovenstaande testfout, kan het resultaat bij het optellen van twee benaderingen een iets slechtere benadering zijn dan mogelijk is. Er is veel meer aan dit onderwerp dat hier niet kan worden behandeld.
Maar we hebben geen pech! Om te testen dat de combinatie van afronden naar een drijvend puntnummer en drijvend punt rekenkundig ongeveer correct is, zelfs als ze niet exact zijn, kunnen we de isapprox
functie gebruiken (die overeenkomt met operator ≈
). Dus we kunnen onze test herschrijven als
julia> @test 0.1 + 0.2 ≈ 0.3
Test Passed
Expression: 0.1 + 0.2 ≈ 0.3
Evaluated: 0.30000000000000004 isapprox 0.3
Natuurlijk, als onze code helemaal fout was, zal de test dat nog steeds vangen:
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
De isapprox
functie gebruikt heuristieken op basis van de grootte van de getallen en de precisie van het drijvende-komma-type om de hoeveelheid te tolereren fouten te bepalen. Het is niet geschikt voor alle situaties, maar het werkt in de meeste en bespaart veel moeite met het implementeren van een eigen versie van isapprox
.