Unit Testing

Testing Base Julia

Julia está en un desarrollo rápido y tiene un extenso conjunto de pruebas para verificar la funcionalidad en múltiples plataformas. Si construyes Julia desde el código fuente, puedes ejecutar este conjunto de pruebas con make test. En una instalación binaria, puedes ejecutar el conjunto de pruebas usando Base.runtests().

Base.runtestsFunction
Base.runtests(tests=["all"]; ncores=ceil(Int, Sys.CPU_THREADS / 2),
              exit_on_error=false, revise=false, [seed])

Ejecuta las pruebas unitarias de Julia listadas en tests, que pueden ser una cadena o un arreglo de cadenas, utilizando ncores procesadores. Si exit_on_error es false, cuando una prueba falla, todas las pruebas restantes en otros archivos aún se ejecutarán; de lo contrario, se descartan, cuando exit_on_error == true. Si revise es true, se utiliza el paquete Revise para cargar cualquier modificación a Base o a las bibliotecas estándar antes de ejecutar las pruebas. Si se proporciona una semilla a través del argumento de palabra clave, se utiliza para inicializar el generador de números aleatorios global en el contexto donde se ejecutan las pruebas; de lo contrario, la semilla se elige aleatoriamente.

source

Basic Unit Tests

El módulo Test proporciona una funcionalidad simple de pruebas unitarias. Las pruebas unitarias son una forma de ver si tu código es correcto al verificar que los resultados son los que esperas. Puede ser útil para asegurarte de que tu código siga funcionando después de realizar cambios, y se puede utilizar durante el desarrollo como una forma de especificar los comportamientos que tu código debería tener cuando esté completo. También puede que desees consultar la documentación de adding tests to your Julia Package.

Las pruebas unitarias simples se pueden realizar con los macros @test y @test_throws:

Test.@testMacro
@test ex
@test f(args...) key=val ...
@test ex broken=true
@test ex skip=true

Prueba que la expresión ex evalúe a true. Si se ejecuta dentro de un @testset, devuelve un Pass Result si lo hace, un Fail Result si es false, y un Error Result si no se pudo evaluar. Si se ejecuta fuera de un @testset, lanza una excepción en lugar de devolver Fail o Error.

Ejemplos

julia> @test true
Test Passed

julia> @test [1, 2] + [2, 1] == [3, 3]
Test Passed

La forma @test f(args...) key=val... es equivalente a escribir @test f(args..., key=val...), lo cual puede ser útil cuando la expresión es una llamada que utiliza sintaxis infija, como comparaciones aproximadas:

julia> @test π ≈ 3.14 atol=0.01
Test Passed

Esto es equivalente a la prueba más fea @test ≈(π, 3.14, atol=0.01). Es un error proporcionar más de una expresión a menos que la primera sea una expresión de llamada y el resto sean asignaciones (k=v).

Puedes usar cualquier clave para los argumentos key=val, excepto para broken y skip, que tienen significados especiales en el contexto de @test:

  • broken=cond indica una prueba que debería pasar pero que actualmente falla consistentemente cuando cond==true. Pruebas que la expresión ex evalúe a false o cause una excepción. Devuelve un Broken Result si lo hace, o un Error Result si la expresión evalúa a true. La prueba regular @test ex se evalúa cuando cond==false.
  • skip=cond marca una prueba que no debería ejecutarse pero que debería incluirse en el informe de resumen de pruebas como Broken, cuando cond==true. Esto puede ser útil para pruebas que fallan intermitentemente, o pruebas de funcionalidades que aún no se han implementado. La prueba regular @test ex se evalúa cuando cond==false.

Ejemplos

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

Los argumentos de palabra clave broken y skip requieren al menos Julia 1.7.

source
Test.@test_throwsMacro
@test_throws exception expr

Prueba que la expresión expr lanza exception. La excepción puede especificar ya sea un tipo, una cadena, una expresión regular, o una lista de cadenas que ocurren en el mensaje de error mostrado, una función de coincidencia, o un valor (que será probado por igualdad comparando campos). Ten en cuenta que @test_throws no soporta una forma de palabra clave al final.

Julia 1.8

La capacidad de especificar cualquier cosa que no sea un tipo o un valor como exception requiere Julia v1.8 o posterior.

Ejemplos

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

En el último ejemplo, en lugar de coincidir con una sola cadena, podría haberse realizado alternativamente con:

  • ["Try", "Complex"] (una lista de cadenas)
  • r"Try sqrt\([Cc]omplex" (una expresión regular)
  • str -> occursin("complex", str) (una función de coincidencia)
source

Por ejemplo, supongamos que queremos verificar que nuestra nueva función foo(x) funciona como se espera:

julia> using Test

julia> foo(x) = length(x)^2
foo (generic function with 1 method)

Si la condición es verdadera, se devuelve un Pass:

julia> @test foo("bar") == 9
Test Passed

julia> @test foo("fizz") >= 10
Test Passed

Si la condición es falsa, entonces se devuelve un Fail y se lanza una excepción:

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 condición no se pudo evaluar porque se lanzó una excepción, lo que ocurre en este caso porque length no está definido para símbolos, se devuelve un objeto Error y se lanza una excepción:

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 esperamos que evaluar una expresión debería lanzar una excepción, entonces podemos usar @test_throws para verificar que esto ocurra:

julia> @test_throws MethodError foo(:cat)
Test Passed
      Thrown: MethodError

Working with Test Sets

Típicamente, se utiliza un gran número de pruebas para asegurarse de que las funciones funcionen correctamente en una variedad de entradas. En caso de que una prueba falle, el comportamiento predeterminado es lanzar una excepción de inmediato. Sin embargo, normalmente es preferible ejecutar el resto de las pruebas primero para obtener una mejor idea de cuántos errores hay en el código que se está probando.

Note

El @testset creará un ámbito local propio al ejecutar las pruebas en él.

El macro @testset se puede utilizar para agrupar pruebas en conjuntos. Todas las pruebas en un conjunto de pruebas se ejecutarán, y al final del conjunto de pruebas se imprimirá un resumen. Si alguna de las pruebas falla, o no se puede evaluar debido a un error, el conjunto de pruebas lanzará una TestSetException.

Test.@testsetMacro
@testset [CustomTestSet] [options...] ["descripción"] begin test_ex end
@testset [CustomTestSet] [options...] ["descripción $v"] for v in itr test_ex end
@testset [CustomTestSet] [options...] ["descripción $v, $w"] for v in itrv, w in itrw test_ex end
@testset [CustomTestSet] [options...] ["descripción"] test_func()
@testset let v = v, w = w; test_ex; end

Con begin/end o llamada a función

Cuando se utiliza @testset, con begin/end o una sola llamada a función, la macro inicia un nuevo conjunto de pruebas en el que evaluar la expresión dada.

Si no se proporciona un tipo de conjunto de pruebas personalizado, se crea por defecto un DefaultTestSet. DefaultTestSet registra todos los resultados y, si hay algún Fail o Error, lanza una excepción al final del conjunto de pruebas de nivel superior (no anidado), junto con un resumen de los resultados de las pruebas.

Se puede proporcionar cualquier tipo de conjunto de pruebas personalizado (subtipo de AbstractTestSet) y también se utilizará para cualquier invocación anidada de @testset. Las opciones dadas solo se aplican al conjunto de pruebas donde se proporcionan. El tipo de conjunto de pruebas por defecto acepta tres opciones booleanas:

  • verbose: si es true, se muestra el resumen de resultados de los conjuntos de pruebas anidados incluso cuando todos pasan (el valor por defecto es false).
  • showtiming: si es true, se muestra la duración de cada conjunto de pruebas mostrado (el valor por defecto es true).
  • failfast: si es true, cualquier fallo de prueba o error hará que el conjunto de pruebas y cualquier conjunto de pruebas hijo devuelvan inmediatamente (el valor por defecto es false). Esto también se puede establecer globalmente a través de la variable de entorno JULIA_TEST_FAILFAST.
Julia 1.8

@testset test_func() requiere al menos Julia 1.8.

Julia 1.9

failfast requiere al menos Julia 1.9.

La cadena de descripción acepta interpolación de los índices del bucle. Si no se proporciona una descripción, se construye una basada en las variables. Si se proporciona una llamada a función, se utilizará su nombre. Las cadenas de descripción explícitas anulan este comportamiento.

Por defecto, la macro @testset devolverá el objeto del conjunto de pruebas en sí, aunque este comportamiento se puede personalizar en otros tipos de conjuntos de pruebas. Si se utiliza un bucle for, entonces la macro recopila y devuelve una lista de los valores de retorno del método finish, que por defecto devolverá una lista de los objetos de conjunto de pruebas utilizados en cada iteración.

Antes de la ejecución del cuerpo de un @testset, hay una llamada implícita a Random.seed!(seed) donde seed es la semilla actual del generador de números aleatorios global. Además, después de la ejecución del cuerpo, el estado del generador de números aleatorios global se restaura a lo que era antes del @testset. Esto está destinado a facilitar la reproducibilidad en caso de fallo y permitir reordenamientos sin problemas de los @testset independientemente de su efecto secundario en el estado del generador de números aleatorios global.

Ejemplos

julia> @testset "identidades trigonométricas" begin
           θ = 2/3*π
           @test sin(-θ) ≈ -sin(θ)
           @test cos(-θ) ≈ cos(θ)
           @test sin(2θ) ≈ 2*sin(θ)*cos(θ)
           @test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2
       end;
Resumen de Pruebas:      | Pasar  Total  Tiempo
identidades trigonométricas |    4      4  0.2s

@testset for

Cuando se utiliza @testset for, la macro inicia una nueva prueba para cada iteración del bucle proporcionado. La semántica de cada conjunto de pruebas es de otro modo idéntica a la del caso begin/end (como si se usara para cada iteración del bucle).

@testset let

Cuando se utiliza @testset let, la macro inicia un conjunto de pruebas transparente con el objeto dado añadido como un objeto de contexto a cualquier prueba fallida contenida en él. Esto es útil al realizar un conjunto de pruebas relacionadas sobre un objeto más grande y es deseable imprimir este objeto más grande cuando alguna de las pruebas individuales falla. Los conjuntos de pruebas transparentes no introducen niveles adicionales de anidamiento en la jerarquía del conjunto de pruebas y se pasan directamente al conjunto de pruebas padre (con el objeto de contexto añadido a cualquier prueba fallida).

Julia 1.9

@testset let requiere al menos Julia 1.9.

Julia 1.10

Se admiten múltiples asignaciones let desde Julia 1.10.

Ejemplos

julia> @testset let logi = log(im)
           @test imag(logi) == π/2
           @test !iszero(real(logi))
       end
Prueba Fallida en none:3
  Expresión: !(iszero(real(logi)))
     Contexto: logi = 0.0 + 1.5707963267948966im

ERROR: Hubo un error durante la prueba

julia> @testset let logi = log(im), op = !iszero
           @test imag(logi) == π/2
           @test op(real(logi))
       end
Prueba Fallida en none:3
  Expresión: op(real(logi))
     Contexto: logi = 0.0 + 1.5707963267948966im
              op = !iszero

ERROR: Hubo un error durante la prueba
source

Podemos poner nuestras pruebas para la función foo(x) en un conjunto de pruebas:

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

Los conjuntos de prueba también pueden estar anidados:

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

Así como llamar funciones:

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

Esto se puede usar para permitir la factorización de conjuntos de pruebas, facilitando la ejecución de conjuntos de pruebas individuales al ejecutar en su lugar las funciones asociadas. Tenga en cuenta que en el caso de funciones, al conjunto de pruebas se le dará el nombre de la función llamada. En el caso de que un conjunto de pruebas anidado no tenga fallos, como ocurrió aquí, se ocultará en el resumen, a menos que se pase la opción verbose=true:

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 tenemos un fallo en la prueba, solo se mostrarán los detalles de los conjuntos de pruebas fallidos:

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

Uno puede usar el @test_logs macro para probar declaraciones de registro, o usar un TestLogger.

Test.@test_logsMacro
@test_logs [patrones_de_log...] [palabras_clave] expresión

Recoge una lista de registros de log generados por expresión usando collect_test_logs, verifica que coincidan con la secuencia patrones_de_log, y devuelve el valor de expresión. Las palabras_clave proporcionan un filtrado simple de los registros de log: la palabra clave min_level controla el nivel mínimo de log que se recogerá para la prueba, la palabra clave match_mode define cómo se realizará la coincidencia (el valor predeterminado :all verifica que todos los logs y patrones coincidan de manera par a par; usa :any para verificar que el patrón coincida al menos una vez en algún lugar de la secuencia).

El patrón de log más útil es una tupla simple de la forma (nivel,mensaje). Se puede usar un número diferente de elementos de tupla para coincidir con otros metadatos de log, correspondientes a los argumentos que se pasan a AbstractLogger a través de la función handle_message: (nivel,mensaje,módulo,grupo,id,archivo,línea). Los elementos que están presentes se emparejarán con los campos del registro de log usando == por defecto, con los casos especiales de que se pueden usar Symbols para los niveles de log estándar, y Regexs en el patrón coincidirán con campos de cadena o Symbol usando occursin.

Ejemplos

Considera una función que registra una advertencia y varios mensajes de depuración:

function foo(n)
    @info "Haciendo foo con n=$n"
    for i=1:n
        @debug "Iteración $i"
    end
    42
end

Podemos probar el mensaje de información usando

@test_logs (:info,"Haciendo foo con n=2") foo(2)

Si también quisiéramos probar los mensajes de depuración, estos deben habilitarse con la palabra clave min_level:

using Logging
@test_logs (:info,"Haciendo foo con n=2") (:debug,"Iteración 1") (:debug,"Iteración 2") min_level=Logging.Debug foo(2)

Si deseas probar que se generan algunos mensajes particulares mientras ignoras el resto, puedes establecer la palabra clave match_mode=:any:

using Logging
@test_logs (:info,) (:debug,"Iteración 42") min_level=Logging.Debug match_mode=:any foo(100)

El macro puede encadenarse con @test para también probar el valor devuelto:

@test (@test_logs (:info,"Haciendo foo con n=2") foo(2)) == 42

Si deseas probar la ausencia de advertencias, puedes omitir especificar patrones de log y establecer el min_level en consecuencia:

# prueba que la expresión no registra mensajes cuando el nivel del logger es warn:
@test_logs min_level=Logging.Warn @info("Alguna información") # pasa
@test_logs min_level=Logging.Warn @warn("Alguna información") # falla

Si deseas probar la ausencia de advertencias (o mensajes de error) en stderr que no son generados por @warn, consulta @test_nowarn. ```

source
Test.TestLoggerType
TestLogger(; min_level=Info, catch_exceptions=false)

Crea un TestLogger que captura los mensajes registrados en su campo logs::Vector{LogRecord}.

Establece min_level para controlar el LogLevel, catch_exceptions para determinar si las excepciones lanzadas como parte de la generación de eventos de registro deben ser capturadas, y respect_maxlog para decidir si se debe seguir la convención de registrar mensajes con maxlog=n para algún entero n un máximo de n veces.

Ver también: LogRecord.

Ejemplos

julia> using Test, Logging

julia> f() = @info "Hola" number=5;

julia> test_logger = TestLogger();

julia> with_logger(test_logger) do
           f()
           @info "¡Adiós!"
       end

julia> @test test_logger.logs[1].message == "Hola"
Test Passed

julia> @test test_logger.logs[1].kwargs[:number] == 5
Test Passed

julia> @test test_logger.logs[2].message == "¡Adiós!"
Test Passed
source
Test.LogRecordType
LogRecord

Almacena los resultados de un solo evento de registro. Campos:

  • level: el LogLevel del mensaje de registro
  • message: el contenido textual del mensaje de registro
  • _module: el módulo del evento de registro
  • group: el grupo de registro (por defecto, el nombre del archivo que contiene el evento de registro)
  • id: el ID del evento de registro
  • file: el archivo que contiene el evento de registro
  • line: la línea dentro del archivo del evento de registro
  • kwargs: cualquier argumento de palabra clave pasado al evento de registro
source

Other Test Macros

Dado que los cálculos con valores de punto flotante pueden ser imprecisos, puedes realizar verificaciones de igualdad aproximada utilizando @test a ≈ b (donde , escrito a través de la autocompletación de \approx, es la función isapprox) o usar 4d61726b646f776e2e436f64652822222c20226973617070726f782229_40726566 directamente.

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

Puedes especificar tolerancias relativas y absolutas configurando los argumentos de palabra clave rtol y atol de isapprox, respectivamente, después de la comparación :

julia> @test 1 ≈ 0.999999  rtol=1e-5
Test Passed

Tenga en cuenta que esta no es una característica específica del , sino más bien una característica general de la macro @test: @test a <op> b key=val es transformado por la macro en @test op(a, b, key=val). Sin embargo, es particularmente útil para pruebas .

Test.@inferredMacro
@inferred [AllowedType] f(x)

Prueba que la expresión de llamada f(x) devuelve un valor del mismo tipo inferido por el compilador. Es útil para verificar la estabilidad de tipos.

f(x) puede ser cualquier expresión de llamada. Devuelve el resultado de f(x) si los tipos coinciden, y un Error Result si encuentra tipos diferentes.

Opcionalmente, AllowedType relaja la prueba, haciéndola pasar cuando el tipo de f(x) coincide con el tipo inferido módulo AllowedType, o cuando el tipo de retorno es un subtipo de AllowedType. Esto es útil al probar la estabilidad de tipos de funciones que devuelven una pequeña unión como Union{Nothing, T} o 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: el tipo de retorno Int64 no coincide con el tipo de retorno inferido 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: el tipo de retorno Float64 no coincide con el tipo de retorno inferido 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: el tipo de retorno Int64 no coincide con el tipo de retorno inferido Union{Missing, Float64, Int64}
[...]
source
Test.@test_deprecatedMacro
@test_deprecated [patrón] expresión

Cuando --depwarn=yes, prueba que expresión emite una advertencia de deprecación y devuelve el valor de expresión. La cadena del mensaje de registro se comparará con patrón, que por defecto es r"deprecated"i.

Cuando --depwarn=no, simplemente devuelve el resultado de ejecutar expresión. Cuando --depwarn=error, verifica que se lance un ErrorException.

Ejemplos

# Deprecado en julia 0.7
@test_deprecated num2hex(1)

# El valor devuelto se puede probar encadenando con @test:
@test (@test_deprecated num2hex(1)) == "0000000000000001"
source
Test.@test_warnMacro
@test_warn msg expr

Prueba si la evaluación de expr resulta en una salida de stderr que contenga la cadena msg o coincida con la expresión regular msg. Si msg es una función booleana, prueba si msg(output) devuelve true. Si msg es una tupla o un arreglo, verifica que la salida de error contenga/coincida con cada elemento en msg. Devuelve el resultado de evaluar expr.

Consulta también @test_nowarn para verificar la ausencia de salida de error.

Nota: Las advertencias generadas por @warn no se pueden probar con esta macro. Usa @test_logs en su lugar.

source
Test.@test_nowarnMacro
@test_nowarn expr

Prueba si la evaluación de expr resulta en una salida vacía de stderr (sin advertencias ni otros mensajes). Devuelve el resultado de evaluar expr.

Nota: La ausencia de advertencias generadas por @warn no se puede probar con este macro. Usa @test_logs en su lugar.

source

Broken Tests

Si una prueba falla de manera consistente, se puede cambiar para usar el macro @test_broken. Esto denotará la prueba como Rota si la prueba sigue fallando y alertará al usuario a través de un Error si la prueba tiene éxito.

Test.@test_brokenMacro
@test_broken ex
@test_broken f(args...) key=val ...

Indica una prueba que debería pasar pero que actualmente falla de manera consistente. Prueba que la expresión ex evalúa a false o causa una excepción. Devuelve un Result Broken si lo hace, o un Result Error si la expresión evalúa a true. Esto es equivalente a @test ex broken=true.

La forma @test_broken f(args...) key=val... funciona como para la macro @test.

Ejemplos

julia> @test_broken 1 == 2
Prueba Rota
  Expresión: 1 == 2

julia> @test_broken 1 == 2 atol=0.1
Prueba Rota
  Expresión: ==(1, 2, atol = 0.1)
source

@test_skip también está disponible para omitir una prueba sin evaluación, pero contando la prueba omitida en el informe del conjunto de pruebas. La prueba no se ejecutará pero dará un Resultado Roto.

Test.@test_skipMacro
@test_skip ex
@test_skip f(args...) key=val ...

Marca una prueba que no debe ejecutarse pero que debe incluirse en el informe de resumen de pruebas como Rota. Esto puede ser útil para pruebas que fallan intermitentemente, o pruebas de funcionalidades que aún no se han implementado. Esto es equivalente a @test ex skip=true.

La forma @test_skip f(args...) key=val... funciona como para el macro @test.

Ejemplos

julia> @test_skip 1 == 2
Prueba Rota
  Omitida: 1 == 2

julia> @test_skip 1 == 2 atol=0.1
Prueba Rota
  Omitida: ==(1, 2, atol = 0.1)
source

Test result types

Test.ResultType
Test.Result

Todos los tests producen un objeto de resultado. Este objeto puede o no ser almacenado, dependiendo de si el test es parte de un conjunto de tests.

source
Test.PassType
Test.Pass <: Test.Result

La condición de prueba fue verdadera, es decir, la expresión se evaluó como verdadera o se lanzó la excepción correcta.

source
Test.FailType
Test.Fail <: Test.Result

La condición de prueba fue falsa, es decir, la expresión se evaluó como falsa o no se lanzó la excepción correcta.

source
Test.ErrorType
Test.Error <: Test.Result

La condición de prueba no pudo ser evaluada debido a una excepción, o se evaluó a algo diferente de un Bool. En el caso de @test_broken, se utiliza para indicar que ocurrió un Result Pass inesperado.

source
Test.BrokenType
Test.Broken <: Test.Result

La condición de prueba es el resultado esperado (fallido) de una prueba rota, o fue explícitamente omitida con @test_skip.

source

Creating Custom AbstractTestSet Types

Los paquetes pueden crear sus propios subtipos de AbstractTestSet implementando los métodos record y finish. El subtipo debe tener un constructor de un argumento que tome una cadena de descripción, con cualquier opción pasada como argumentos de palabra clave.

Test.recordFunction
record(ts::AbstractTestSet, res::Result)

Registra un resultado en un conjunto de pruebas. Esta función es llamada por la infraestructura @testset cada vez que se completa un macro @test contenido, y se le da el resultado de la prueba (que podría ser un Error). Esto también se llamará con un Error si se lanza una excepción dentro del bloque de prueba pero fuera de un contexto @test.

source
Test.finishFunction
finish(ts::AbstractTestSet)

Realiza cualquier procesamiento final necesario para el conjunto de pruebas dado. Esto es llamado por la infraestructura @testset después de que se ejecuta un bloque de prueba.

Los subtipos personalizados de AbstractTestSet deben llamar a record en su padre (si lo hay) para agregarse al árbol de resultados de pruebas. Esto podría implementarse como:

if get_testset_depth() != 0
    # Adjuntar este conjunto de pruebas al conjunto de pruebas padre
    parent_ts = get_testset()
    record(parent_ts, self)
    return self
end
source

Test asume la responsabilidad de mantener una pila de conjuntos de pruebas anidados a medida que se ejecutan, pero cualquier acumulación de resultados es responsabilidad del subtipo AbstractTestSet. Puedes acceder a esta pila con los métodos get_testset y get_testset_depth. Ten en cuenta que estas funciones no están exportadas.

Test.get_testsetFunction
get_testset()

Recupera el conjunto de pruebas activo del almacenamiento local de la tarea. Si no hay un conjunto de pruebas activo, utiliza el conjunto de pruebas predeterminado de respaldo.

source
Test.get_testset_depthFunction
get_testset_depth()

Devuelve el número de conjuntos de pruebas activos, sin incluir el conjunto de pruebas predeterminado.

source

Test también se asegura de que las invocaciones anidadas de @testset utilicen el mismo subtipo de AbstractTestSet que su padre, a menos que se establezca explícitamente. No propaga ninguna propiedad del testset. El comportamiento de herencia de opciones se puede implementar mediante paquetes utilizando la infraestructura de pila que proporciona Test.

Definir un subtipo básico de AbstractTestSet podría verse así:

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

Y usando ese conjunto de pruebas se ve así:

@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

Para utilizar un conjunto de pruebas personalizado y tener los resultados grabados impresos como parte de cualquier conjunto de pruebas predeterminado externo, también define Test.get_test_counts. Esto podría verse así:

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

Mantiene el estado para recopilar recursivamente los resultados de un conjunto de pruebas con fines de visualización.

Campos:

  • customized: Si la función get_test_counts fue personalizada para el AbstractTestSet para el cual este objeto de conteo es. Si se definió un método personalizado, siempre pase true al constructor.
  • passes: El número de invocaciones @test que pasaron.
  • fails: El número de invocaciones @test que fallaron.
  • errors: El número de invocaciones @test que tuvieron errores.
  • broken: El número de invocaciones @test que están rotas.
  • passes: El número acumulativo de invocaciones @test que pasaron.
  • fails: El número acumulativo de invocaciones @test que fallaron.
  • errors: El número acumulativo de invocaciones @test que tuvieron errores.
  • broken: El número acumulativo de invocaciones @test que están rotas.
  • duration: La duración total durante la cual el AbstractTestSet en cuestión se ejecutó, como una String formateada.
source
Test.get_test_countsFunction

" gettestcounts(::AbstractTestSet) -> TestCounts

Función recursiva que cuenta el número de resultados de prueba de cada tipo directamente en el conjunto de pruebas, y totaliza a través de los conjuntos de pruebas hijos.

Los AbstractTestSet personalizados deben implementar esta función para que sus totales sean contados y mostrados con DefaultTestSet también.

Si esto no se implementa para un TestSet personalizado, la impresión vuelve a reportar x para fallos y ?s para la duración.

source
Test.format_durationFunction
format_duration(::AbstractTestSet)

Devuelve una cadena formateada para imprimir la duración por la que se ejecutó el conjunto de pruebas.

Si no está definido, vuelve a "?s".

source
Test.print_test_resultsFunction
print_test_results(ts::AbstractTestSet, depth_pad=0)

Imprime los resultados de un AbstractTestSet como una tabla formateada.

depth_pad se refiere a cuánto relleno se debe agregar frente a toda la salida.

Llamado dentro de Test.finish, si el testset finished es el testset más alto.

source

Test utilities

Test.GenericArrayType

El GenericArray se puede utilizar para probar las API de arreglos genéricos que programan a la interfaz AbstractArray, con el fin de asegurar que las funciones puedan trabajar con tipos de arreglos además del tipo de arreglo estándar Array.

source
Test.GenericDictType

El GenericDict se puede utilizar para probar las API de diccionarios genéricos que programan a la interfaz AbstractDict, con el fin de asegurar que las funciones puedan trabajar con tipos asociativos además del tipo estándar Dict.

source
Test.GenericOrderType

El GenericOrder se puede utilizar para probar las API en su soporte para tipos ordenados genéricos.

source
Test.GenericSetType

El GenericSet se puede utilizar para probar las API de conjuntos genéricos que programan para la interfaz AbstractSet, con el fin de garantizar que las funciones puedan trabajar con tipos de conjuntos además de los tipos estándar Set y BitSet.

source
Test.GenericStringType

El GenericString se puede utilizar para probar las API de cadenas genéricas que programan para la interfaz AbstractString, con el fin de garantizar que las funciones puedan trabajar con tipos de cadena además del tipo estándar String.

source
Test.detect_ambiguitiesFunction
detect_ambiguities(mod1, mod2...; recursive=false,
                                  ambiguous_bottom=false,
                                  allowed_undefineds=nothing)

Devuelve un vector de pares (Method,Method) de métodos ambiguos definidos en los módulos especificados. Usa recursive=true para probar en todos los submódulos.

ambiguous_bottom controla si se incluyen las ambigüedades provocadas solo por parámetros de tipo Union{}; en la mayoría de los casos probablemente querrás establecer esto en false. Consulta Base.isambiguous.

Consulta Test.detect_unbound_args para una explicación de allowed_undefineds.

Julia 1.8

allowed_undefineds requiere al menos Julia 1.8.

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

Devuelve un vector de Methods que pueden tener parámetros de tipo no vinculados. Usa recursive=true para probar en todos los submódulos.

Por defecto, cualquier símbolo no definido desencadena una advertencia. Esta advertencia se puede suprimir proporcionando una colección de GlobalRefs para las cuales se puede omitir la advertencia. Por ejemplo, configurando

allowed_undefineds = Set([GlobalRef(Base, :active_repl),
                          GlobalRef(Base, :active_repl_backend)])

suprimiría las advertencias sobre Base.active_repl y Base.active_repl_backend.

Julia 1.8

allowed_undefineds requiere al menos Julia 1.8.

source

Workflow for Testing Packages

Usando las herramientas disponibles en las secciones anteriores, aquí hay un flujo de trabajo potencial para crear un paquete y agregarle pruebas.

Generating an Example Package

Para este flujo de trabajo, crearemos un paquete llamado Example:

pkg> generate Example
shell> cd Example
shell> mkdir test
pkg> activate .

Creating Sample Functions

El requisito número uno para probar un paquete es tener funcionalidad para probar. Para eso, agregaremos algunas funciones simples a Example que podamos probar. Agrega lo siguiente a 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

Desde la raíz del paquete Example, navega al directorio test, activa un nuevo entorno allí y añade el paquete Test al entorno:

shell> cd test
pkg> activate .
(test) pkg> add Test

Testing Our Package

Ahora, estamos listos para agregar pruebas a Example. Es una práctica estándar crear un archivo dentro del directorio test llamado runtests.jl que contenga los conjuntos de pruebas que queremos ejecutar. Adelante, crea ese archivo dentro del directorio test y agrega el siguiente código:

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

Necesitaremos crear esos dos archivos incluidos, math_tests.jl y greeting_tests.jl, y agregar algunas pruebas a ellos.

Nota: Observa cómo no tuvimos que especificar agregar Example en el Project.toml del entorno test. Este es un beneficio del sistema de pruebas de Julia que podrías read about more here.

Writing Tests for math_tests.jl

Usando nuestro conocimiento de Test.jl, aquí hay algunas pruebas de ejemplo que podríamos agregar a 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

Usando nuestro conocimiento de Test.jl, aquí hay algunos ejemplos de pruebas que podríamos agregar a greeting_tests.jl:

@testset "Testset 3" begin
    @test "Hello world!" == greet()
    @test_throws MethodError greet("Antonia")
end

Testing Our Package

Ahora que hemos agregado nuestras pruebas y nuestro script runtests.jl en test, podemos probar nuestro paquete Example volviendo a la raíz del entorno del paquete Example y reactivando el entorno Example:

shell> cd ..
pkg> activate .

A partir de ahí, finalmente podemos ejecutar nuestra suite de pruebas de la siguiente manera:

(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

Y si todo salió correctamente, deberías ver una salida similar a la anterior. Usando Test.jl, se pueden agregar pruebas más complicadas para los paquetes, pero esto debería, idealmente, orientar a los desarrolladores en la dirección de cómo comenzar a probar sus propios paquetes creados.

Code Coverage

El seguimiento de la cobertura de código durante las pruebas se puede habilitar utilizando la bandera pkg> test --coverage (o a un nivel más bajo utilizando el argumento de julia --code-coverage). Esto está activado por defecto en la acción de 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

Desde Julia 1.11, la cobertura no se recopila durante la fase de precompilación del paquete.