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.runtestsFunction
Base.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.

source

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.@testMacro
@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 lorsque cond==true. Tests que l'expression ex évalue à false ou provoque une exception. Renvoie un Broken Result si c'est le cas, ou un Error Result si l'expression évalue à true. Le @test ex régulier est évalué lorsque cond==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 comme Broken, lorsque cond==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é lorsque cond==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
Julia 1.7

Les arguments de mot-clé broken et skip nécessitent au moins Julia 1.7.

source
Test.@test_throwsMacro
@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.

Julia 1.8

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)
source

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é.

Note

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.@testsetMacro
@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 Fails ou des Errors, 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 : si true, le résumé des résultats des testsets imbriqués est affiché même lorsqu'ils passent tous (la valeur par défaut est false).
  • showtiming : si true, la durée de chaque testset affiché est montrée (la valeur par défaut est true).
  • failfast : si true, tout échec de test ou erreur fera que le testset et tous les testsets enfants retourneront immédiatement (la valeur par défaut est false). Cela peut également être défini globalement via la variable d'environnement JULIA_TEST_FAILFAST.
Julia 1.8

@testset test_func() nécessite au moins Julia 1.8.

Julia 1.9

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)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).

Julia 1.9

@testset let nécessite au moins Julia 1.9.

Julia 1.10

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
source
Test.TestSetExceptionType
TestSetException

Lancé lorsqu'un ensemble de tests se termine et que tous les tests n'ont pas réussi.

source

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_logsMacro
@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 Symbols peuvent être utilisés pour les niveaux de journal standard, et les Regexs 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.

source
Test.TestLoggerType
TestLogger(; 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
source
Test.LogRecordType
LogRecord

Stocke les résultats d'un seul événement de journalisation. Champs :

  • level : le LogLevel du message de journalisation
  • message : le contenu textuel du message de journalisation
  • _module : le module de l'événement de journalisation
  • group : 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 journalisation
  • file : le fichier contenant l'événement de journalisation
  • line : la ligne dans le fichier de l'événement de journalisation
  • kwargs : tous les arguments de mot-clé passés à l'événement de journalisation
source

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.@inferredMacro
@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}
[...]
source
Test.@test_deprecatedMacro
@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"
source
Test.@test_warnMacro
@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.

source
Test.@test_nowarnMacro
@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.

source

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_brokenMacro
@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)
source

@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_skipMacro
@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)
source

Test result types

Test.ResultType
Test.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.

source
Test.PassType
Test.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.

source
Test.FailType
Test.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.

source
Test.ErrorType
Test.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.

source
Test.BrokenType
Test.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.

source

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.recordFunction
record(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.

source
Test.finishFunction
finish(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
source

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_testsetFunction
get_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.

source
Test.get_testset_depthFunction
get_testset_depth()

Retourne le nombre de jeux de tests actifs, sans inclure le jeu de tests par défaut.

source

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.TestCountsType
TestCounts

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 fonction get_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 toujours true 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 de String formatée.
source
Test.get_test_countsFunction

" 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.

source
Test.format_durationFunction
format_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".

source
Test.print_test_resultsFunction
print_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.

source

Test utilities

Test.GenericArrayType

Le 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.

source
Test.GenericDictType

Le 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.

source
Test.GenericOrderType

Le GenericOrder peut être utilisé pour tester les API pour leur prise en charge des types ordonnés génériques.

source
Test.GenericSetType

Le 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.

source
Test.GenericStringType

Le 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.

source
Test.detect_ambiguitiesFunction
detect_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.

Julia 1.8

allowed_undefineds nécessite au moins Julia 1.8.

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

Renvoie un vecteur de Methods 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 GlobalRefs 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.

Julia 1.8

allowed_undefineds nécessite au moins Julia 1.8.

source

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 fichier Project.toml de l'environnement test. 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.

Julia 1.11

Depuis Julia 1.11, la couverture n'est pas collectée pendant la phase de précompilation des paquets.