Unit Testing
Testing Base Julia
Julia est en cours de développement rapide et dispose d'une vaste suite de tests pour vérifier la fonctionnalité sur plusieurs plateformes. Si vous construisez Julia à partir du code source, vous pouvez exécuter cette suite de tests avec make test
. Dans une installation binaire, vous pouvez exécuter la suite de tests en utilisant Base.runtests()
.
Base.runtests
— FunctionBase.runtests(tests=["all"]; ncores=ceil(Int, Sys.CPU_THREADS / 2),
exit_on_error=false, revise=false, [seed])
Exécutez les tests unitaires Julia listés dans tests
, qui peuvent être soit une chaîne de caractères, soit un tableau de chaînes de caractères, en utilisant ncores
processeurs. Si exit_on_error
est false
, lorsqu'un test échoue, tous les tests restants dans d'autres fichiers seront tout de même exécutés ; sinon, ils sont abandonnés lorsque exit_on_error == true
. Si revise
est true
, le package Revise
est utilisé pour charger toute modification apportée à Base
ou aux bibliothèques standard avant d'exécuter les tests. Si une graine est fournie via l'argument clé, elle est utilisée pour initialiser le RNG global dans le contexte où les tests sont exécutés ; sinon, la graine est choisie aléatoirement.
Basic Unit Tests
Le module Test
fournit une fonctionnalité simple de tests unitaires. Les tests unitaires sont un moyen de vérifier si votre code est correct en vérifiant que les résultats correspondent à vos attentes. Cela peut être utile pour s'assurer que votre code fonctionne toujours après avoir apporté des modifications, et peut être utilisé lors du développement comme un moyen de spécifier les comportements que votre code devrait avoir une fois terminé. Vous voudrez peut-être également consulter la documentation pour adding tests to your Julia Package.
Des tests unitaires simples peuvent être effectués avec les macros @test
et @test_throws
:
Test.@test
— Macro@test ex
@test f(args...) key=val ...
@test ex broken=true
@test ex skip=true
Testez que l'expression ex
évalue à true
. Si exécuté à l'intérieur d'un @testset
, renvoyez un Pass
Result
si c'est le cas, un Fail
Result
s'il est false
, et un Error
Result
s'il ne peut pas être évalué. Si exécuté en dehors d'un @testset
, lancez une exception au lieu de renvoyer Fail
ou Error
.
Exemples
julia> @test true
Test Passed
julia> @test [1, 2] + [2, 1] == [3, 3]
Test Passed
La forme @test f(args...) key=val...
est équivalente à écrire @test f(args..., key=val...)
ce qui peut être utile lorsque l'expression est un appel utilisant une syntaxe infixe telle que des comparaisons approximatives :
julia> @test π ≈ 3.14 atol=0.01
Test Passed
Ceci est équivalent au test moins élégant @test ≈(π, 3.14, atol=0.01)
. Il est interdit de fournir plus d'une expression à moins que la première soit une expression d'appel et que le reste soit des affectations (k=v
).
Vous pouvez utiliser n'importe quelle clé pour les arguments key=val
, sauf pour broken
et skip
, qui ont des significations spéciales dans le contexte de @test
:
broken=cond
indique un test qui devrait réussir mais échoue actuellement de manière cohérente lorsquecond==true
. Tests que l'expressionex
évalue àfalse
ou provoque une exception. Renvoie unBroken
Result
si c'est le cas, ou unError
Result
si l'expression évalue àtrue
. Le@test ex
régulier est évalué lorsquecond==false
.skip=cond
marque un test qui ne devrait pas être exécuté mais qui devrait être inclus dans le rapport de résumé des tests commeBroken
, lorsquecond==true
. Cela peut être utile pour des tests qui échouent de manière intermittente, ou des tests de fonctionnalités pas encore implémentées. Le@test ex
régulier est évalué lorsquecond==false
.
Exemples
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
Les arguments de mot-clé broken
et skip
nécessitent au moins Julia 1.7.
Test.@test_throws
— Macro@test_throws exception expr
Teste que l'expression expr
lance exception
. L'exception peut spécifier soit un type, une chaîne, une expression régulière, ou une liste de chaînes apparaissant dans le message d'erreur affiché, une fonction de correspondance, ou une valeur (qui sera testée pour l'égalité en comparant les champs). Notez que @test_throws
ne prend pas en charge une forme de mot-clé terminal.
La capacité de spécifier autre chose qu'un type ou une valeur comme exception
nécessite Julia v1.8 ou ultérieure.
Exemples
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))."
Dans l'exemple final, au lieu de faire correspondre une seule chaîne, cela aurait pu également être effectué avec :
["Try", "Complex"]
(une liste de chaînes)r"Try sqrt\([Cc]omplex"
(une expression régulière)str -> occursin("complex", str)
(une fonction de correspondance)
Par exemple, supposons que nous voulons vérifier que notre nouvelle fonction foo(x)
fonctionne comme prévu :
julia> using Test
julia> foo(x) = length(x)^2
foo (generic function with 1 method)
Si la condition est vraie, un Pass
est retourné :
julia> @test foo("bar") == 9
Test Passed
julia> @test foo("fizz") >= 10
Test Passed
Si la condition est fausse, alors un Échec
est renvoyé et une exception est levée :
julia> @test foo("f") == 20
Test Failed at none:1
Expression: foo("f") == 20
Evaluated: 1 == 20
ERROR: There was an error during testing
Si la condition ne pouvait pas être évaluée en raison d'une exception levée, ce qui se produit dans ce cas parce que length
n'est pas défini pour les symboles, un objet Error
est renvoyé et une exception est levée :
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
Si nous nous attendons à ce qu'évaluer une expression doit lancer une exception, alors nous pouvons utiliser @test_throws
pour vérifier que cela se produit :
julia> @test_throws MethodError foo(:cat)
Test Passed
Thrown: MethodError
Working with Test Sets
Typiquement, un grand nombre de tests sont utilisés pour s'assurer que les fonctions fonctionnent correctement sur une gamme d'entrées. Dans le cas où un test échoue, le comportement par défaut est de lancer une exception immédiatement. Cependant, il est généralement préférable d'exécuter le reste des tests d'abord pour avoir une meilleure idée du nombre d'erreurs dans le code testé.
Le @testset
créera un scope local à lui-même lors de l'exécution des tests qu'il contient.
Le macro @testset
peut être utilisé pour regrouper des tests en ensembles. Tous les tests d'un ensemble de tests seront exécutés, et à la fin de l'ensemble de tests, un résumé sera imprimé. Si l'un des tests a échoué, ou n'a pas pu être évalué en raison d'une erreur, l'ensemble de tests lancera alors une TestSetException
.
Test.@testset
— Macro@testset [CustomTestSet] [options...] ["description"] begin test_ex end
@testset [CustomTestSet] [options...] ["description $v"] for v in itr test_ex end
@testset [CustomTestSet] [options...] ["description $v, $w"] for v in itrv, w in itrw test_ex end
@testset [CustomTestSet] [options...] ["description"] test_func()
@testset let v = v, w = w; test_ex; end
Avec begin/end ou appel de fonction
Lorsque @testset est utilisé, avec begin/end ou un seul appel de fonction, la macro commence un nouveau test set dans lequel évaluer l'expression donnée.
Si aucun type de testset personnalisé n'est donné, il crée par défaut un DefaultTestSet
. DefaultTestSet
enregistre tous les résultats et, s'il y a des Fail
s ou des Error
s, lance une exception à la fin du test set de niveau supérieur (non imbriqué), accompagnée d'un résumé des résultats des tests.
Tout type de testset personnalisé (sous-type de AbstractTestSet
) peut être donné et sera également utilisé pour toute invocation imbriquée de @testset
. Les options données ne s'appliquent qu'au test set où elles sont fournies. Le type de test set par défaut accepte trois options booléennes :
verbose
: sitrue
, le résumé des résultats des testsets imbriqués est affiché même lorsqu'ils passent tous (la valeur par défaut estfalse
).showtiming
: sitrue
, la durée de chaque testset affiché est montrée (la valeur par défaut esttrue
).failfast
: sitrue
, tout échec de test ou erreur fera que le testset et tous les testsets enfants retourneront immédiatement (la valeur par défaut estfalse
). Cela peut également être défini globalement via la variable d'environnementJULIA_TEST_FAILFAST
.
@testset test_func()
nécessite au moins Julia 1.8.
failfast
nécessite au moins Julia 1.9.
La chaîne de description accepte l'interpolation des indices de boucle. Si aucune description n'est fournie, une est construite en fonction des variables. Si un appel de fonction est fourni, son nom sera utilisé. Les chaînes de description explicites remplacent ce comportement.
Par défaut, la macro @testset
renverra l'objet testset lui-même, bien que ce comportement puisse être personnalisé dans d'autres types de testset. Si une boucle for
est utilisée, alors la macro collecte et renvoie une liste des valeurs de retour de la méthode finish
, qui par défaut renverra une liste des objets testset utilisés à chaque itération.
Avant l'exécution du corps d'un @testset
, il y a un appel implicite à Random.seed!(seed)
où seed
est la graine actuelle du RNG global. De plus, après l'exécution du corps, l'état du RNG global est restauré à ce qu'il était avant le @testset
. Cela vise à faciliter la reproductibilité en cas d'échec et à permettre des réarrangements transparents des @testset
indépendamment de leur effet secondaire sur l'état du RNG global.
Exemples
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
Lorsque @testset for
est utilisé, la macro commence un nouveau test pour chaque itération de la boucle fournie. La sémantique de chaque test set est autrement identique à celle du cas begin/end
(comme si utilisé pour chaque itération de boucle).
@testset let
Lorsque @testset let
est utilisé, la macro commence un test set transparent avec l'objet donné ajouté comme objet de contexte à tout test échouant contenu dans celui-ci. Cela est utile lors de l'exécution d'un ensemble de tests liés sur un objet plus grand et il est souhaitable d'imprimer cet objet plus grand lorsque l'un des tests individuels échoue. Les test sets transparents n'introduisent pas de niveaux supplémentaires d'imbrication dans la hiérarchie des test sets et sont transmis directement au test set parent (avec l'objet de contexte ajouté à tout test échouant).
@testset let
nécessite au moins Julia 1.9.
Plusieurs affectations let
sont prises en charge depuis Julia 1.10.
Exemples
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: Il y a eu une erreur pendant le test
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: Il y a eu une erreur pendant le test
Test.TestSetException
— TypeTestSetException
Lancé lorsqu'un ensemble de tests se termine et que tous les tests n'ont pas réussi.
Nous pouvons mettre nos tests pour la fonction foo(x)
dans un ensemble de tests :
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
Les ensembles de tests peuvent également être imbriqués :
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
Ainsi que des fonctions d'appel :
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
Cela peut être utilisé pour permettre la factorisation des ensembles de tests, facilitant ainsi l'exécution d'ensembles de tests individuels en exécutant plutôt les fonctions associées. Notez que dans le cas des fonctions, l'ensemble de tests portera le nom de la fonction appelée. Dans le cas où un ensemble de tests imbriqué n'a pas d'échecs, comme c'est le cas ici, il sera masqué dans le résumé, à moins que l'option verbose=true
ne soit passée :
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
Si nous avons un échec de test, seuls les détails des ensembles de tests échoués seront affichés :
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
On peut utiliser la macro @test_logs
pour tester les déclarations de journal, ou utiliser un TestLogger
.
Test.@test_logs
— Macro@test_logs [log_patterns...] [keywords] expression
Collectez une liste d'enregistrements de journal générés par expression
en utilisant collect_test_logs
, vérifiez qu'ils correspondent à la séquence log_patterns
, et renvoyez la valeur de expression
. Les keywords
fournissent un filtrage simple des enregistrements de journal : le mot-clé min_level
contrôle le niveau de journal minimum qui sera collecté pour le test, le mot-clé match_mode
définit comment la correspondance sera effectuée (le :all
par défaut vérifie que tous les journaux et motifs correspondent par paires ; utilisez :any
pour vérifier que le motif correspond au moins une fois quelque part dans la séquence.)
Le motif de journal le plus utile est un simple tuple de la forme (level,message)
. Un nombre différent d'éléments de tuple peut être utilisé pour correspondre à d'autres métadonnées de journal, correspondant aux arguments passés à AbstractLogger
via la fonction handle_message
: (level,message,module,group,id,file,line)
. Les éléments qui sont présents seront appariés par paires avec les champs d'enregistrement de journal en utilisant ==
par défaut, avec les cas spéciaux que les Symbol
s peuvent être utilisés pour les niveaux de journal standard, et les Regex
s dans le motif correspondront aux champs de chaîne ou de symbole en utilisant occursin
.
Exemples
Considérez une fonction qui enregistre un avertissement et plusieurs messages de débogage :
function foo(n)
@info "Doing foo with n=$n"
for i=1:n
@debug "Iteration $i"
end
42
end
Nous pouvons tester le message d'information en utilisant
@test_logs (:info,"Doing foo with n=2") foo(2)
Si nous voulons également tester les messages de débogage, ceux-ci doivent être activés avec le mot-clé min_level
:
using Logging
@test_logs (:info,"Doing foo with n=2") (:debug,"Iteration 1") (:debug,"Iteration 2") min_level=Logging.Debug foo(2)
Si vous souhaitez tester que certains messages particuliers sont générés tout en ignorant le reste, vous pouvez définir le mot-clé match_mode=:any
:
using Logging
@test_logs (:info,) (:debug,"Iteration 42") min_level=Logging.Debug match_mode=:any foo(100)
Le macro peut être enchaîné avec @test
pour tester également la valeur renvoyée :
@test (@test_logs (:info,"Doing foo with n=2") foo(2)) == 42
Si vous souhaitez tester l'absence d'avertissements, vous pouvez omettre la spécification des motifs de journal et définir le min_level
en conséquence :
# test que l'expression ne journalise aucun message lorsque le niveau du journal est averti :
@test_logs min_level=Logging.Warn @info("Some information") # passe
@test_logs min_level=Logging.Warn @warn("Some information") # échoue
Si vous souhaitez tester l'absence d'avertissements (ou de messages d'erreur) dans stderr
qui ne sont pas générés par @warn
, consultez @test_nowarn
.
Test.TestLogger
— TypeTestLogger(; min_level=Info, catch_exceptions=false)
Créez un TestLogger
qui capture les messages enregistrés dans son champ logs::Vector{LogRecord}
.
Définissez min_level
pour contrôler le LogLevel
, catch_exceptions
pour déterminer si les exceptions levées lors de la génération d'événements de journalisation doivent être capturées, et respect_maxlog
pour savoir s'il faut suivre la convention de journaliser des messages avec maxlog=n
pour un certain entier n
au maximum n
fois.
Voir aussi : LogRecord
.
Exemples
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
Test.LogRecord
— TypeLogRecord
Stocke les résultats d'un seul événement de journalisation. Champs :
level
: leLogLevel
du message de journalisationmessage
: le contenu textuel du message de journalisation_module
: le module de l'événement de journalisationgroup
: le groupe de journalisation (par défaut, le nom du fichier contenant l'événement de journalisation)id
: l'ID de l'événement de journalisationfile
: le fichier contenant l'événement de journalisationline
: la ligne dans le fichier de l'événement de journalisationkwargs
: tous les arguments de mot-clé passés à l'événement de journalisation
Other Test Macros
Comme les calculs sur des valeurs à virgule flottante peuvent être imprécis, vous pouvez effectuer des vérifications d'égalité approximative en utilisant soit @test a ≈ b
(où ≈
, tapé via la complétion par tabulation de \approx
, est la fonction isapprox
) ou utiliser 4d61726b646f776e2e436f64652822222c20226973617070726f782229_40726566
directement.
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
Vous pouvez spécifier des tolérances relatives et absolues en définissant les arguments de mot-clé rtol
et atol
de isapprox
, respectivement, après la comparaison ≈
:
julia> @test 1 ≈ 0.999999 rtol=1e-5
Test Passed
Notez que ce n'est pas une fonctionnalité spécifique de ≈
, mais plutôt une fonctionnalité générale de la macro @test
: @test a <op> b key=val
est transformé par la macro en @test op(a, b, key=val)
. Cela dit, c'est particulièrement utile pour les tests ≈
.
Test.@inferred
— Macro@inferred [AllowedType] f(x)
Teste que l'expression d'appel f(x)
renvoie une valeur du même type que celui inféré par le compilateur. Il est utile de vérifier la stabilité des types.
f(x)
peut être n'importe quelle expression d'appel. Renvoie le résultat de f(x)
si les types correspondent, et un Error
Result
s'il trouve des types différents.
En option, AllowedType
assouplit le test, en le faisant réussir lorsque soit le type de f(x)
correspond au type inféré modulo AllowedType
, soit lorsque le type de retour est un sous-type de AllowedType
. Cela est utile lors des tests de stabilité des types de fonctions renvoyant une petite union telle que Union{Nothing, T}
ou 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}
[...]
Test.@test_deprecated
— Macro@test_deprecated [pattern] expression
Lorsque --depwarn=yes
, testez que expression
émet un avertissement de dépréciation et renvoyez la valeur de expression
. La chaîne de message de journal sera comparée à pattern
, qui par défaut est r"deprecated"i
.
Lorsque --depwarn=no
, renvoyez simplement le résultat de l'exécution de expression
. Lorsque --depwarn=error
, vérifiez qu'une ErrorException est levée.
Exemples
# Déprécié dans julia 0.7
@test_deprecated num2hex(1)
# La valeur retournée peut être testée en chaînant avec @test :
@test (@test_deprecated num2hex(1)) == "0000000000000001"
Test.@test_warn
— Macro@test_warn msg expr
Testez si l'évaluation de expr
produit une sortie stderr
qui contient la chaîne msg
ou correspond à l'expression régulière msg
. Si msg
est une fonction booléenne, teste si msg(output)
renvoie true
. Si msg
est un tuple ou un tableau, vérifie que la sortie d'erreur contient/correspond à chaque élément de msg
. Renvoie le résultat de l'évaluation de expr
.
Voir aussi @test_nowarn
pour vérifier l'absence de sortie d'erreur.
Remarque : Les avertissements générés par @warn
ne peuvent pas être testés avec cette macro. Utilisez @test_logs
à la place.
Test.@test_nowarn
— Macro@test_nowarn expr
Testez si l'évaluation de expr
produit une sortie stderr
vide (pas d'avertissements ni d'autres messages). Renvoie le résultat de l'évaluation de expr
.
Remarque : L'absence d'avertissements générés par @warn
ne peut pas être testée avec cette macro. Utilisez @test_logs
à la place.
Broken Tests
Si un test échoue de manière constante, il peut être modifié pour utiliser le macro @test_broken
. Cela désignera le test comme Broken
si le test continue d'échouer et alertera l'utilisateur via une Error
si le test réussit.
Test.@test_broken
— Macro@test_broken ex
@test_broken f(args...) key=val ...
Indique un test qui devrait réussir mais échoue actuellement de manière constante. Teste si l'expression ex
évalue à false
ou provoque une exception. Renvoie un Result
Broken
si c'est le cas, ou un Result
Error
si l'expression évalue à true
. Cela équivaut à @test ex broken=true
.
La forme @test_broken f(args...) key=val...
fonctionne comme pour la macro @test
.
Exemples
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)
@test_skip
est également disponible pour ignorer un test sans évaluation, mais en comptant le test ignoré dans le rapport du jeu de tests. Le test ne s'exécutera pas mais donnera un Résultat
Cassé
.
Test.@test_skip
— Macro@test_skip ex
@test_skip f(args...) key=val ...
Marque un test qui ne doit pas être exécuté mais qui doit être inclus dans le rapport de résumé des tests comme Broken
. Cela peut être utile pour des tests qui échouent de manière intermittente, ou des tests de fonctionnalités pas encore implémentées. Cela équivaut à @test ex skip=true
.
La forme @test_skip f(args...) key=val...
fonctionne comme pour la macro @test
.
Exemples
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)
Test result types
Test.Result
— TypeTest.Result
Tous les tests produisent un objet de résultat. Cet objet peut ou non être stocké, en fonction de si le test fait partie d'un ensemble de tests.
Test.Pass
— TypeTest.Pass <: Test.Result
La condition de test était vraie, c'est-à-dire que l'expression a été évaluée à vraie ou que l'exception correcte a été levée.
Test.Fail
— TypeTest.Fail <: Test.Result
La condition de test était fausse, c'est-à-dire que l'expression a été évaluée à faux ou que l'exception correcte n'a pas été levée.
Test.Error
— TypeTest.Error <: Test.Result
La condition de test n'a pas pu être évaluée en raison d'une exception, ou elle a été évaluée à quelque chose d'autre qu'un Bool
. Dans le cas de @test_broken
, il est utilisé pour indiquer qu'un Result
Pass
inattendu s'est produit.
Test.Broken
— TypeTest.Broken <: Test.Result
La condition de test est le résultat attendu (échoué) d'un test cassé, ou a été explicitement ignorée avec @test_skip
.
Creating Custom AbstractTestSet
Types
Les packages peuvent créer leurs propres sous-types AbstractTestSet
en implémentant les méthodes record
et finish
. Le sous-type doit avoir un constructeur à un argument prenant une chaîne de description, avec toutes les options passées en tant qu'arguments nommés.
Test.record
— Functionrecord(ts::AbstractTestSet, res::Result)
Enregistre un résultat dans un ensemble de tests. Cette fonction est appelée par l'infrastructure @testset
chaque fois qu'un macro @test
contenu se termine, et reçoit le résultat du test (qui pourrait être une Error
). Cela sera également appelé avec une Error
si une exception est levée à l'intérieur du bloc de test mais en dehors d'un contexte @test
.
Test.finish
— Functionfinish(ts::AbstractTestSet)
Effectuez tout traitement final nécessaire pour le testset donné. Cela est appelé par l'infrastructure @testset
après l'exécution d'un bloc de test.
Les sous-types AbstractTestSet
personnalisés doivent appeler record
sur leur parent (s'il y en a un) pour s'ajouter à l'arbre des résultats de test. Cela pourrait être implémenté comme suit :
if get_testset_depth() != 0
# Attacher ce test set au test set parent
parent_ts = get_testset()
record(parent_ts, self)
return self
end
Test
prend la responsabilité de maintenir une pile de jeux de tests imbriqués au fur et à mesure de leur exécution, mais toute accumulation de résultats est de la responsabilité du sous-type AbstractTestSet
. Vous pouvez accéder à cette pile avec les méthodes get_testset
et get_testset_depth
. Notez que ces fonctions ne sont pas exportées.
Test.get_testset
— Functionget_testset()
Récupère l'ensemble de test actif à partir du stockage local de la tâche. Si aucun ensemble de test n'est actif, utilise l'ensemble de test par défaut de secours.
Test.get_testset_depth
— Functionget_testset_depth()
Retourne le nombre de jeux de tests actifs, sans inclure le jeu de tests par défaut.
Test
s'assure également que les invocations imbriquées de @testset
utilisent le même sous-type AbstractTestSet
que leur parent, sauf si cela est défini explicitement. Il ne propage aucune propriété du testset. Le comportement d'héritage des options peut être implémenté par des packages en utilisant l'infrastructure de pile que Test
fournit.
Définir un sous-type de base AbstractTestSet
pourrait ressembler à :
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
Et en utilisant cet ensemble de test, cela ressemble à :
@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
Pour utiliser un ensemble de tests personnalisé et avoir les résultats enregistrés imprimés comme partie de tout ensemble de tests par défaut extérieur, définissez également Test.get_test_counts
. Cela pourrait ressembler à ceci :
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.TestCounts
— TypeTestCounts
Contient l'état pour rassembler de manière récursive les résultats d'un ensemble de tests à des fins d'affichage.
Champs :
customized
: Indique si la fonctionget_test_counts
a été personnalisée pour l'AbstractTestSet
pour lequel cet objet de comptage est destiné. Si une méthode personnalisée a été définie, passez toujourstrue
au constructeur.passes
: Le nombre d'invocations@test
réussies.fails
: Le nombre d'invocations@test
échouées.errors
: Le nombre d'invocations@test
avec erreurs.broken
: Le nombre d'invocations@test
cassées.passes
: Le nombre cumulatif d'invocations@test
réussies.fails
: Le nombre cumulatif d'invocations@test
échouées.errors
: Le nombre cumulatif d'invocations@test
avec erreurs.broken
: Le nombre cumulatif d'invocations@test
cassées.duration
: La durée totale pendant laquelle l'AbstractTestSet
en question a été exécuté, sous forme deString
formatée.
Test.get_test_counts
— Function" gettestcounts(::AbstractTestSet) -> TestCounts
Fonction récursive qui compte le nombre de résultats de test de chaque type directement dans le testset, et totalise à travers les testsets enfants.
Les AbstractTestSet
personnalisés doivent implémenter cette fonction pour que leurs totaux soient comptés et affichés avec DefaultTestSet
également.
Si cela n'est pas implémenté pour un TestSet
personnalisé, l'impression revient à signaler x
pour les échecs et ?s
pour la durée.
Test.format_duration
— Functionformat_duration(::AbstractTestSet)
Retourne une chaîne formatée pour afficher la durée pendant laquelle le testset a été exécuté.
Si non défini, revient à "?s"
.
Test.print_test_results
— Functionprint_test_results(ts::AbstractTestSet, depth_pad=0)
Imprime les résultats d'un AbstractTestSet
sous forme de tableau formaté.
depth_pad
fait référence à la quantité de remplissage qui doit être ajoutée devant toute sortie.
Appelé à l'intérieur de Test.finish
, si le testset finish
é est le testset le plus haut.
Test utilities
Test.GenericArray
— TypeLe GenericArray
peut être utilisé pour tester les API de tableau génériques qui programment l'interface AbstractArray
, afin de s'assurer que les fonctions peuvent fonctionner avec des types de tableau autres que le type Array
standard.
Test.GenericDict
— TypeLe GenericDict
peut être utilisé pour tester les API de dictionnaire génériques qui programment l'interface AbstractDict
, afin de s'assurer que les fonctions peuvent fonctionner avec des types associatifs en plus du type Dict
standard.
Test.GenericOrder
— TypeLe GenericOrder
peut être utilisé pour tester les API pour leur prise en charge des types ordonnés génériques.
Test.GenericSet
— TypeLe GenericSet
peut être utilisé pour tester les API de ensembles génériques qui programment l'interface AbstractSet
, afin de s'assurer que les fonctions peuvent fonctionner avec des types d'ensembles autres que les types standard Set
et BitSet
.
Test.GenericString
— TypeLe GenericString
peut être utilisé pour tester les API de chaînes génériques qui programment l'interface AbstractString
, afin de s'assurer que les fonctions peuvent fonctionner avec des types de chaînes autres que le type standard String
.
Test.detect_ambiguities
— Functiondetect_ambiguities(mod1, mod2...; recursive=false,
ambiguous_bottom=false,
allowed_undefineds=nothing)
Renvoie un vecteur de paires (Method,Method)
de méthodes ambiguës définies dans les modules spécifiés. Utilisez recursive=true
pour tester dans tous les sous-modules.
ambiguous_bottom
contrôle si les ambiguïtés déclenchées uniquement par des paramètres de type Union{}
sont incluses ; dans la plupart des cas, vous voudrez probablement définir cela sur false
. Voir Base.isambiguous
.
Voir Test.detect_unbound_args
pour une explication de allowed_undefineds
.
allowed_undefineds
nécessite au moins Julia 1.8.
Test.detect_unbound_args
— Functiondetect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing)
Renvoie un vecteur de Method
s qui peuvent avoir des paramètres de type non liés. Utilisez recursive=true
pour tester dans tous les sous-modules.
Par défaut, tout symbole non défini déclenche un avertissement. Cet avertissement peut être supprimé en fournissant une collection de GlobalRef
s pour lesquels l'avertissement peut être ignoré. Par exemple, en définissant
allowed_undefineds = Set([GlobalRef(Base, :active_repl),
GlobalRef(Base, :active_repl_backend)])
vous supprimeriez les avertissements concernant Base.active_repl
et Base.active_repl_backend
.
allowed_undefineds
nécessite au moins Julia 1.8.
Workflow for Testing Packages
En utilisant les outils disponibles dans les sections précédentes, voici un flux de travail potentiel pour créer un package et y ajouter des tests.
Generating an Example Package
Pour ce flux de travail, nous allons créer un package appelé Example
:
pkg> generate Example
shell> cd Example
shell> mkdir test
pkg> activate .
Creating Sample Functions
La condition numéro un pour tester un package est d'avoir une fonctionnalité à tester. Pour cela, nous allons ajouter quelques fonctions simples à Example
que nous pouvons tester. Ajoutez ce qui suit à 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
Depuis la racine du package Example
, naviguez vers le répertoire test
, activez un nouvel environnement là-bas et ajoutez le package Test
à l'environnement :
shell> cd test
pkg> activate .
(test) pkg> add Test
Testing Our Package
Maintenant, nous sommes prêts à ajouter des tests à Example
. Il est d'usage de créer un fichier dans le répertoire test
appelé runtests.jl
qui contient les ensembles de tests que nous voulons exécuter. Allez-y et créez ce fichier dans le répertoire test
et ajoutez-y le code suivant :
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
Nous devrons créer ces deux fichiers inclus, math_tests.jl
et greeting_tests.jl
, et y ajouter quelques tests.
Remarque : Remarquez comment nous n'avons pas eu besoin de spécifier d'ajouter
Example
dans le fichierProject.toml
de l'environnementtest
. C'est un avantage du système de test de Julia que vous pourriez read about more here.
Writing Tests for math_tests.jl
En utilisant nos connaissances de Test.jl
, voici quelques tests d'exemple que nous pourrions ajouter à 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
En utilisant nos connaissances de Test.jl
, voici quelques tests d'exemple que nous pourrions ajouter à greeting_tests.jl
:
@testset "Testset 3" begin
@test "Hello world!" == greet()
@test_throws MethodError greet("Antonia")
end
Testing Our Package
Maintenant que nous avons ajouté nos tests et notre script runtests.jl
dans test
, nous pouvons tester notre package Example
en revenant à la racine de l'environnement du package Example
et en réactivant l'environnement Example
:
shell> cd ..
pkg> activate .
À partir de là, nous pouvons enfin exécuter notre suite de tests comme suit :
(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
Et si tout s'est bien passé, vous devriez voir une sortie similaire à celle ci-dessus. En utilisant Test.jl
, des tests plus compliqués peuvent être ajoutés pour les packages, mais cela devrait idéalement orienter les développeurs sur la façon de commencer à tester leurs propres packages créés.
Code Coverage
Le suivi de la couverture de code pendant les tests peut être activé en utilisant le drapeau pkg> test --coverage
(ou à un niveau inférieur en utilisant l'argument julia --code-coverage
). Cela est activé par défaut dans l'action GitHub julia-runtest.
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.
Depuis Julia 1.11, la couverture n'est pas collectée pendant la phase de précompilation des paquets.