Julia Language
Examen de la unidad
Buscar..
Sintaxis
- @test [expr]
- @test_throws [Exception] [expr]
- @testset "[nombre]" comienza; [pruebas]; fin
- Pkg.test ([paquete])
Observaciones
La documentación estándar de la biblioteca para Base.Test
cubre material adicional más allá de lo que se muestra en estos ejemplos.
Probando un paquete
Para ejecutar las pruebas unitarias de un paquete, use la función Pkg.test
. Para un paquete llamado MyPackage
, el comando sería
julia> Pkg.test("MyPackage")
Una salida esperada sería similar a
INFO: Computing test dependencies for MyPackage...
INFO: Installing BaseTestNext v0.2.2
INFO: Testing MyPackage
Test Summary: | Pass Total
Data | 66 66
Test Summary: | Pass Total
Monetary | 107 107
Test Summary: | Pass Total
Basket | 47 47
Test Summary: | Pass Total
Mixed | 13 13
Test Summary: | Pass Total
Data Access | 35 35
INFO: MyPackage tests passed
INFO: Removing BaseTestNext v0.2.2
aunque obviamente, no se puede esperar que coincida exactamente con lo anterior, ya que los paquetes diferentes utilizan marcos diferentes.
Este comando ejecuta el archivo test/runtests.jl
del paquete en un entorno limpio.
Uno puede probar todos los paquetes instalados a la vez con
julia> Pkg.test()
pero esto usualmente toma mucho tiempo.
Escribir una prueba simple
Las pruebas unitarias se declaran en el archivo test/runtests.jl
en un paquete. Por lo general, este archivo comienza.
using MyModule
using Base.Test
La unidad básica de prueba es la macro @test
. Esta macro es como una aseveración de clases. Cualquier expresión booleana se puede probar en la macro @test
:
@test 1 + 1 == 2
@test iseven(10)
@test 9 < 10 || 10 < 9
Podemos probar la macro @test
en el REPL:
julia> using Base.Test
julia> @test 1 + 1 == 2
Test Passed
Expression: 1 + 1 == 2
Evaluated: 2 == 2
julia> @test 1 + 1 == 3
Test Failed
Expression: 1 + 1 == 3
Evaluated: 2 == 3
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test(::Base.Test.Returned, ::Expr) at ./test.jl:281
La macro de prueba se puede utilizar en casi cualquier lugar, como en bucles o funciones:
# For positive integers, a number's square is at least as large as the number
for i in 1:10
@test i^2 ≥ i
end
# Test that no two of a, b, or c share a prime factor
function check_pairwise_coprime(a, b, c)
@test gcd(a, b) == 1
@test gcd(a, c) == 1
@test gcd(b, c) == 1
end
check_pairwise_coprime(10, 23, 119)
Escribir un conjunto de prueba
En la versión v0.5, los conjuntos de pruebas están integrados en el módulo estándar de Base.Test
biblioteca, y no tiene que hacer nada especial (además de using Base.Test
) para usarlos.
Los conjuntos de pruebas no forman parte de la biblioteca Base.Test
de Julia v0.4. En su lugar, debe REQUIRE
el módulo BaseTestNext
y agregar using BaseTestNext
a su archivo. Para soportar tanto la versión 0.4 como la 0.5, podrías usar
if VERSION ≥ v"0.5.0-dev+7720"
using Base.Test
else
using BaseTestNext
const Test = BaseTestNext
end
Es útil agrupar @test
relacionados en un conjunto de prueba. Además de una organización de pruebas más clara, los conjuntos de pruebas ofrecen mejores resultados y más personalización.
Para definir un conjunto de pruebas, simplemente envuelva cualquier número de @test
s con un bloque @testset
:
@testset "+" begin
@test 1 + 1 == 2
@test 2 + 2 == 4
end
@testset "*" begin
@test 1 * 1 == 1
@test 2 * 2 == 4
end
La ejecución de estos conjuntos de pruebas imprime el siguiente resultado:
Test Summary: | Pass Total
+ | 2 2
Test Summary: | Pass Total
* | 2 2
Incluso si un conjunto de pruebas contiene una prueba que falla, todo el conjunto de pruebas se ejecutará hasta su finalización, y las fallas se registrarán e informarán:
@testset "-" begin
@test 1 - 1 == 0
@test 2 - 2 == 1
@test 3 - () == 3
@test 4 - 4 == 0
end
Ejecutando los resultados de este conjunto de pruebas en
-: Test Failed
Expression: 2 - 2 == 1
Evaluated: 0 == 1
in record(::Base.Test.DefaultTestSet, ::Base.Test.Fail) at ./test.jl:428
...
-: Error During Test
Test threw an exception of type MethodError
Expression: 3 - () == 3
MethodError: no method matching -(::Int64, ::Tuple{})
...
Test Summary: | Pass Fail Error Total
- | 2 1 1 4
ERROR: Some tests did not pass: 2 passed, 1 failed, 1 errored, 0 broken.
...
Los conjuntos de pruebas se pueden anidar, lo que permite una organización arbitrariamente profunda
@testset "Int" begin
@testset "+" begin
@test 1 + 1 == 2
@test 2 + 2 == 4
end
@testset "-" begin
@test 1 - 1 == 0
end
end
Si las pruebas pasan, esto solo mostrará los resultados del conjunto de pruebas más externo:
Test Summary: | Pass Total
Int | 3 3
Pero si las pruebas fallan, se informa un desglose en el conjunto de pruebas exacto y la prueba que causa el fallo.
La macro @testset
se puede usar con un bucle for
para crear muchos conjuntos de pruebas a la vez:
@testset for i in 1:5
@test 2i == i + i
@test i^2 == i * i
@test i ÷ i == 1
end
que informa
Test Summary: | Pass Total
i = 1 | 3 3
Test Summary: | Pass Total
i = 2 | 3 3
Test Summary: | Pass Total
i = 3 | 3 3
Test Summary: | Pass Total
i = 4 | 3 3
Test Summary: | Pass Total
i = 5 | 3 3
Una estructura común es tener conjuntos de prueba externos componentes o tipos de prueba. Dentro de estos conjuntos de pruebas externas, los conjuntos de pruebas internas prueban el comportamiento. Por ejemplo, supongamos que creamos un tipo UniversalSet
con una instancia de singleton que contiene todo. Antes de que implementemos el tipo, podemos usar principios de desarrollo controlados por pruebas e implementar las pruebas:
@testset "UniversalSet" begin
U = UniversalSet.instance
@testset "egal/equal" begin
@test U === U
@test U == U
end
@testset "in" begin
@test 1 in U
@test "Hello World" in U
@test Int in U
@test U in U
end
@testset "subset" begin
@test Set() ⊆ U
@test Set(["Hello World"]) ⊆ U
@test Set(1:10) ⊆ U
@test Set([:a, 2.0, "w", Set()]) ⊆ U
@test U ⊆ U
end
end
Entonces podemos comenzar a implementar nuestra funcionalidad hasta que pase nuestras pruebas. El primer paso es definir el tipo:
immutable UniversalSet <: Base.AbstractSet end
Sólo dos de nuestras pruebas pasan ahora. Podemos implementar in
:
immutable UniversalSet <: Base.AbstractSet end
Base.in(x, ::UniversalSet) = true
Esto también hace que algunas de nuestras pruebas de subconjunto pasen. Sin embargo, el issubset
( ⊆
) no funciona para UniversalSet
, porque el respaldo trata de iterar sobre elementos, lo que no podemos hacer. Simplemente podemos definir una especialización que hace que issubset
devuelva true
para cualquier conjunto:
immutable UniversalSet <: Base.AbstractSet end
Base.in(x, ::UniversalSet) = true
Base.issubset(x::Base.AbstractSet, ::UniversalSet) = true
Y ahora, todas nuestras pruebas pasan!
Pruebas de excepciones
Las excepciones encontradas durante la ejecución de una prueba fallarán en la prueba, y si la prueba no está en un conjunto de pruebas, finalice la prueba del motor. Por lo general, esto es bueno, porque en la mayoría de las situaciones las excepciones no son el resultado deseado. Pero a veces, uno quiere probar específicamente que se produce una cierta excepción. La macro @test_throws
facilita esto.
julia> @test_throws BoundsError [1, 2, 3][4]
Test Passed
Expression: ([1,2,3])[4]
Thrown: BoundsError
Si se lanza la excepción incorrecta, @test_throws
seguirá fallando:
julia> @test_throws TypeError [1, 2, 3][4]
Test Failed
Expression: ([1,2,3])[4]
Expected: TypeError
Thrown: BoundsError
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test_throws(::Base.Test.Threw, ::Expr, ::Type{T}) at ./test.jl:329
y si no se produce ninguna excepción, @test_throws
también fallará:
julia> @test_throws BoundsError [1, 2, 3, 4][4]
Test Failed
Expression: ([1,2,3,4])[4]
Expected: BoundsError
No exception thrown
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test_throws(::Base.Test.Returned, ::Expr, ::Type{T}) at ./test.jl:329
Prueba de Punto Flotante Aproximada Igualdad
¿Cuál es el trato con lo siguiente?
julia> @test 0.1 + 0.2 == 0.3
Test Failed
Expression: 0.1 + 0.2 == 0.3
Evaluated: 0.30000000000000004 == 0.3
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test(::Base.Test.Returned, ::Expr) at ./test.jl:281
El error se debe al hecho de que ninguno de 0.1
, 0.2
y 0.3
está representado en la computadora exactamente como esos valores: 1//10
, 2//10
y 3//10
. En cambio, se aproximan por valores que están muy cerca. Pero como se vio en el fallo de la prueba anterior, al sumar dos aproximaciones, el resultado puede ser una aproximación un poco peor de lo que es posible. Hay mucho más sobre este tema que no se puede cubrir aquí.
¡Pero no estamos sin suerte! Para probar que la combinación de redondeo a un número de punto flotante y la aritmética de punto flotante es aproximadamente correcta, aunque no exacta, podemos utilizar el isapprox
función (que corresponde al operador ≈
). Así que podemos reescribir nuestra prueba como
julia> @test 0.1 + 0.2 ≈ 0.3
Test Passed
Expression: 0.1 + 0.2 ≈ 0.3
Evaluated: 0.30000000000000004 isapprox 0.3
Por supuesto, si nuestro código estaba completamente equivocado, la prueba todavía detectará eso:
julia> @test 0.1 + 0.2 ≈ 0.4
Test Failed
Expression: 0.1 + 0.2 ≈ 0.4
Evaluated: 0.30000000000000004 isapprox 0.4
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test(::Base.Test.Returned, ::Expr) at ./test.jl:281
La función isapprox
utiliza heurísticas basadas en el tamaño de los números y la precisión del tipo de punto flotante para determinar la cantidad de error a tolerar. No es apropiado para todas las situaciones, pero funciona en la mayoría, y ahorra mucho esfuerzo al implementar la propia versión de isapprox
.