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.runtests
— FunctionBase.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.
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.@test
— Macro@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 cuandocond==true
. Pruebas que la expresiónex
evalúe afalse
o cause una excepción. Devuelve unBroken
Result
si lo hace, o unError
Result
si la expresión evalúa atrue
. La prueba regular@test ex
se evalúa cuandocond==false
.skip=cond
marca una prueba que no debería ejecutarse pero que debería incluirse en el informe de resumen de pruebas comoBroken
, cuandocond==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 cuandocond==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
Los argumentos de palabra clave broken
y skip
requieren al menos Julia 1.7.
Test.@test_throws
— Macro@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.
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)
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.
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.@testset
— Macro@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 estrue
, se muestra el resumen de resultados de los conjuntos de pruebas anidados incluso cuando todos pasan (el valor por defecto esfalse
).showtiming
: si estrue
, se muestra la duración de cada conjunto de pruebas mostrado (el valor por defecto estrue
).failfast
: si estrue
, cualquier fallo de prueba o error hará que el conjunto de pruebas y cualquier conjunto de pruebas hijo devuelvan inmediatamente (el valor por defecto esfalse
). Esto también se puede establecer globalmente a través de la variable de entornoJULIA_TEST_FAILFAST
.
@testset test_func()
requiere al menos Julia 1.8.
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).
@testset let
requiere al menos Julia 1.9.
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
Test.TestSetException
— TypeTestSetException
Se lanza cuando un conjunto de pruebas finaliza y no todas las pruebas pasaron.
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_logs
— Macro@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 Symbol
s para los niveles de log estándar, y Regex
s 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
. ```
Test.TestLogger
— TypeTestLogger(; 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
Test.LogRecord
— TypeLogRecord
Almacena los resultados de un solo evento de registro. Campos:
level
: elLogLevel
del mensaje de registromessage
: el contenido textual del mensaje de registro_module
: el módulo del evento de registrogroup
: el grupo de registro (por defecto, el nombre del archivo que contiene el evento de registro)id
: el ID del evento de registrofile
: el archivo que contiene el evento de registroline
: la línea dentro del archivo del evento de registrokwargs
: cualquier argumento de palabra clave pasado al evento de registro
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.@inferred
— Macro@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}
[...]
Test.@test_deprecated
— Macro@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"
Test.@test_warn
— Macro@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.
Test.@test_nowarn
— Macro@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.
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_broken
— Macro@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)
@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_skip
— Macro@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)
Test result types
Test.Result
— TypeTest.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.
Test.Pass
— TypeTest.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.
Test.Fail
— TypeTest.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.
Test.Error
— TypeTest.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.
Test.Broken
— TypeTest.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
.
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.record
— Functionrecord(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
.
Test.finish
— Functionfinish(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
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_testset
— Functionget_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.
Test.get_testset_depth
— Functionget_testset_depth()
Devuelve el número de conjuntos de pruebas activos, sin incluir el conjunto de pruebas predeterminado.
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.TestCounts
— TypeTestCounts
Mantiene el estado para recopilar recursivamente los resultados de un conjunto de pruebas con fines de visualización.
Campos:
customized
: Si la funciónget_test_counts
fue personalizada para elAbstractTestSet
para el cual este objeto de conteo es. Si se definió un método personalizado, siempre pasetrue
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 elAbstractTestSet
en cuestión se ejecutó, como unaString
formateada.
Test.get_test_counts
— Function" 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.
Test.format_duration
— Functionformat_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"
.
Test.print_test_results
— Functionprint_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 finish
ed es el testset más alto.
Test utilities
Test.GenericArray
— TypeEl 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
.
Test.GenericDict
— TypeEl 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
.
Test.GenericOrder
— TypeEl GenericOrder
se puede utilizar para probar las API en su soporte para tipos ordenados genéricos.
Test.GenericSet
— TypeEl 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
.
Test.GenericString
— TypeEl 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
.
Test.detect_ambiguities
— Functiondetect_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
.
allowed_undefineds
requiere al menos Julia 1.8.
Test.detect_unbound_args
— Functiondetect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing)
Devuelve un vector de Method
s 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 GlobalRef
s 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
.
allowed_undefineds
requiere al menos Julia 1.8.
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 elProject.toml
del entornotest
. 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.
Desde Julia 1.11, la cobertura no se recopila durante la fase de precompilación del paquete.