Julia Language
Test unitario
Ricerca…
Sintassi
- @test [expr]
- @test_throws [Exception] [expr]
- @testset "[nome]" inizia; [test]; fine
- Pkg.test ([pacchetto])
Osservazioni
La documentazione della libreria standard per Base.Test
copre materiale aggiuntivo oltre a quello mostrato in questi esempi.
Test di un pacchetto
Per eseguire i test unitari per un pacchetto, utilizzare la funzione Pkg.test
. Per un pacchetto denominato MyPackage
, il comando sarebbe
julia> Pkg.test("MyPackage")
Un risultato atteso sarebbe simile a
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
anche se ovviamente, non ci si può aspettare che corrisponda esattamente a quanto sopra, poiché i diversi pacchetti usano quadri diversi.
Questo comando esegue il file test/runtests.jl
del pacchetto in un ambiente pulito.
Si può testare tutti i pacchetti installati contemporaneamente con
julia> Pkg.test()
ma questo di solito richiede molto tempo.
Scrivere un semplice test
I test unitari sono dichiarati nel file test/runtests.jl
in un pacchetto. In genere, questo file ha inizio
using MyModule
using Base.Test
L'unità di base di test è la macro @test
. Questa macro è come una sorta di asserzione. Qualsiasi espressione booleana può essere testata nella macro @test
:
@test 1 + 1 == 2
@test iseven(10)
@test 9 < 10 || 10 < 9
Possiamo provare la macro @test
nella REPL:
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
La macro di test può essere utilizzata praticamente ovunque, ad esempio nei loop o nelle funzioni:
# 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)
Scrivere un set di prova
Nella versione v0.5, i set di test sono incorporati nel modulo Base.Test
libreria standard e non devi fare nulla di speciale (oltre a using Base.Test
) per utilizzarli.
I set di test non fanno parte della libreria Base.Test
di Julia v0.4. Invece, è necessario REQUIRE
il modulo BaseTestNext
e aggiungere using BaseTestNext
al file. Per supportare entrambe le versioni 0.4 e 0.5, è possibile utilizzare
if VERSION ≥ v"0.5.0-dev+7720"
using Base.Test
else
using BaseTestNext
const Test = BaseTestNext
end
È utile raggruppare @test
s correlati in un set di test. Oltre a un'organizzazione di test più chiara, i set di test offrono risultati migliori e maggiore personalizzazione.
Per definire un set di test, è sufficiente racchiudere qualsiasi numero di @test
s con un blocco @testset
:
@testset "+" begin
@test 1 + 1 == 2
@test 2 + 2 == 4
end
@testset "*" begin
@test 1 * 1 == 1
@test 2 * 2 == 4
end
L'esecuzione di questi set di test stampa il seguente output:
Test Summary: | Pass Total
+ | 2 2
Test Summary: | Pass Total
* | 2 2
Anche se un set di test contiene un test non funzionante, l'intero set di test verrà eseguito fino al completamento e i guasti verranno registrati e riportati:
@testset "-" begin
@test 1 - 1 == 0
@test 2 - 2 == 1
@test 3 - () == 3
@test 4 - 4 == 0
end
Esecuzione di questo risultato del test 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.
...
I set di test possono essere annidati, consentendo un'organizzazione arbitrariamente profonda
@testset "Int" begin
@testset "+" begin
@test 1 + 1 == 2
@test 2 + 2 == 4
end
@testset "-" begin
@test 1 - 1 == 0
end
end
Se i test superano, questo mostrerà solo i risultati per il set di test più esterno:
Test Summary: | Pass Total
Int | 3 3
Ma se i test falliscono, viene riportato un drill-down nell'esatta serie di test e test che ha causato l'errore.
La macro @testset
può essere utilizzata con un ciclo for
per creare molti set di test contemporaneamente:
@testset for i in 1:5
@test 2i == i + i
@test i^2 == i * i
@test i ÷ i == 1
end
quali rapporti
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
Una struttura comune è quella di avere test o gruppi di test esterni. All'interno di questi set di test esterni, il test interno stabilisce il comportamento di test. Ad esempio, supponiamo di aver creato un tipo UniversalSet
con un'istanza singleton che contiene tutto. Prima ancora di implementare il tipo, possiamo utilizzare i principi di sviluppo basati sui test e implementare i test:
@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
Possiamo quindi iniziare a implementare le nostre funzionalità fino a quando non supererà i nostri test. Il primo passo è definire il tipo:
immutable UniversalSet <: Base.AbstractSet end
Solo due dei nostri test passano adesso. Possiamo implementare in
:
immutable UniversalSet <: Base.AbstractSet end
Base.in(x, ::UniversalSet) = true
Ciò rende anche alcuni dei nostri test di sottoinsieme. Tuttavia, il issubset
( ⊆
) non funziona per UniversalSet
, perché il fallback tenta di eseguire iterazioni su elementi, cosa che non possiamo fare. Possiamo semplicemente definire una specializzazione che rende issubset
tornare true
per ogni insieme:
immutable UniversalSet <: Base.AbstractSet end
Base.in(x, ::UniversalSet) = true
Base.issubset(x::Base.AbstractSet, ::UniversalSet) = true
E ora passano tutti i nostri test!
Test delle eccezioni
Le eccezioni incontrate durante l'esecuzione di un test non supereranno il test e, se il test non si trova in un set di test, terminerà il test engine. Di solito, questa è una buona cosa, perché nella maggior parte delle situazioni le eccezioni non sono il risultato desiderato. Ma a volte, si vuole testare in modo specifico che venga sollevata una certa eccezione. La macro @test_throws
facilita questo.
julia> @test_throws BoundsError [1, 2, 3][4]
Test Passed
Expression: ([1,2,3])[4]
Thrown: BoundsError
Se viene generata l'eccezione sbagliata, @test_throws
fallirà ancora:
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
e se non viene lanciata alcuna eccezione, @test_throws
fallirà anche:
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
Testing Equality approssimativo a virgola mobile
Qual è l'accordo con quanto segue?
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
L'errore è causato dal fatto che nessuno di 0.1
, 0.2
e 0.3
è rappresentato nel computer esattamente come quei valori - 1//10
, 2//10
e 3//10
. Invece, sono approssimati da valori molto vicini. Ma come visto nel fallimento del test sopra, quando si aggiungono due approssimazioni insieme, il risultato può essere un'approssimazione leggermente peggiore di quanto sia possibile. C'è molto di più in questo argomento che non può essere coperto qui.
Ma non siamo sfortunati! Per verificare che la combinazione di arrotondamento a un numero in virgola mobile e aritmetica in virgola mobile sia approssimativamente corretta, anche se non esatta, possiamo usare la funzione isapprox
(che corrisponde all'operatore ≈
). Quindi possiamo riscrivere il nostro test come
julia> @test 0.1 + 0.2 ≈ 0.3
Test Passed
Expression: 0.1 + 0.2 ≈ 0.3
Evaluated: 0.30000000000000004 isapprox 0.3
Ovviamente, se il nostro codice fosse completamente sbagliato, il test continuerà a rilevare che:
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
La funzione isapprox
utilizza l'euristica in base alla dimensione dei numeri e alla precisione del tipo a virgola mobile per determinare la quantità di errore da tollerare. Non è appropriato per tutte le situazioni, ma funziona in gran parte, e fa un sacco di sforzi per implementare la propria versione di isapprox
.