单元测试

  1. DocTestSetup = :(using Test)

测试 Julia Base 库

Julia 处于快速开发中,有着可以扩展的测试套件,用来跨平台测试功能。 如果你是通过源代码构建的 Julia ,你可以通过 make test 来运行这个测试套件。 如果是通过二进制包安装的,你可以通过 Base.runtests() 来运行这个测试套件。

  1. Base.runtests

基本的单元测试

The Test module provides simple unit testing functionality. Unit testing is a way to see if your code is correct by checking that the results are what you expect. It can be helpful to ensure your code still works after you make changes, and can be used when developing as a way of specifying the behaviors your code should have when complete.

简单的单元测试可以通过 @test@test_throws 宏来完成:

  1. Test.@test
  2. Test.@test_throws

例如,假设我们想要测试新的函数 foo(x) 是否按照期望的方式工作:

```jldoctest testfoo julia> using Test

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

  1. If the condition is true, a `Pass` is returned:
  2. ```jldoctest testfoo
  3. julia> @test foo("bar") == 9
  4. Test Passed
  5. Expression: foo("bar") == 9
  6. Evaluated: 9 == 9
  7. julia> @test foo("fizz") >= 10
  8. Test Passed
  9. Expression: foo("fizz") >= 10
  10. Evaluated: 16 >= 10

如果条件为假,则返回 Fail 并抛出异常。

```jldoctest testfoo julia> @test foo(“f”) == 20 Test Failed at none:1 Expression: foo(“f”) == 20 Evaluated: 1 == 20 ERROR: There was an error during testing

  1. If the condition could not be evaluated because an exception was thrown, which occurs in this
  2. case because `length` is not defined for symbols, an `Error` object is returned and an exception
  3. is thrown:
  4. ```julia-repl
  5. julia> @test foo(:cat) == 1
  6. Error During Test
  7. Test threw an exception of type MethodError
  8. Expression: foo(:cat) == 1
  9. MethodError: no method matching length(::Symbol)
  10. Closest candidates are:
  11. length(::SimpleVector) at essentials.jl:256
  12. length(::Base.MethodList) at reflection.jl:521
  13. length(::MethodTable) at reflection.jl:597
  14. ...
  15. Stacktrace:
  16. [...]
  17. ERROR: There was an error during testing

If we expect that evaluating an expression should throw an exception, then we can use @test_throws to check that this occurs:

```jldoctest testfoo julia> @test_throws MethodError foo(:cat) Test Passed Expression: foo(:cat) Thrown: MethodError

  1. ## Working with Test Sets
  2. Typically a large number of tests are used to make sure functions work correctly over a range
  3. of inputs. In the event a test fails, the default behavior is to throw an exception immediately.
  4. However, it is normally preferable to run the rest of the tests first to get a better picture
  5. of how many errors there are in the code being tested.
  6. !!! note
  7. The `@testset` will create a local scope of its own when running the tests in it.
  8. The `@testset` macro can be used to group tests into *sets*. All the tests in a test set will
  9. be run, and at the end of the test set a summary will be printed. If any of the tests failed,
  10. or could not be evaluated due to an error, the test set will then throw a `TestSetException`.
  11. ```@docs
  12. Test.@testset
  13. Test.TestSetException

We can put our tests for the foo(x) function in a test set:

```jldoctest testfoo julia> @testset “Foo Tests” begin @test foo(“a”) == 1 @test foo(“ab”) == 4 @test foo(“abc”) == 9 end; Test Summary: | Pass Total Foo Tests | 3 3

  1. 测试集可以嵌套:
  2. ```jldoctest testfoo
  3. julia> @testset "Foo Tests" begin
  4. @testset "Animals" begin
  5. @test foo("cat") == 9
  6. @test foo("dog") == foo("cat")
  7. end
  8. @testset "Arrays $i" for i in 1:3
  9. @test foo(zeros(i)) == i^2
  10. @test foo(fill(1.0, i)) == i^2
  11. end
  12. end;
  13. Test Summary: | Pass Total
  14. Foo Tests | 8 8

In the event that a nested test set has no failures, as happened here, it will be hidden in the summary, unless the verbose=true option is passed:

```jldoctest testfoo 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 Foo Tests | 8 8 Animals | 2 2 Arrays 1 | 2 2 Arrays 2 | 2 2 Arrays 3 | 2 2

  1. If we do have a test failure, only the details for the failed test sets will be shown:
  2. ```julia-repl
  3. julia> @testset "Foo Tests" begin
  4. @testset "Animals" begin
  5. @testset "Felines" begin
  6. @test foo("cat") == 9
  7. end
  8. @testset "Canines" begin
  9. @test foo("dog") == 9
  10. end
  11. end
  12. @testset "Arrays" begin
  13. @test foo(zeros(2)) == 4
  14. @test foo(fill(1.0, 4)) == 15
  15. end
  16. end
  17. Arrays: Test Failed
  18. Expression: foo(fill(1.0, 4)) == 15
  19. Evaluated: 16 == 15
  20. [...]
  21. Test Summary: | Pass Fail Total
  22. Foo Tests | 3 1 4
  23. Animals | 2 2
  24. Arrays | 1 1 2
  25. ERROR: Some tests did not pass: 3 passed, 1 failed, 0 errored, 0 broken.

Other Test Macros

As calculations on floating-point values can be imprecise, you can perform approximate equality checks using either @test a ≈ b (where , typed via tab completion of \approx, is the isapprox function) or use isapprox directly.

  1. julia> @test 1 0.999999999
  2. Test Passed
  3. Expression: 1 0.999999999
  4. Evaluated: 1 0.999999999
  5. julia> @test 1 0.999999
  6. Test Failed at none:1
  7. Expression: 1 0.999999
  8. Evaluated: 1 0.999999
  9. ERROR: There was an error during testing

You can specify relative and absolute tolerances by setting the rtol and atol keyword arguments of isapprox, respectively, after the comparison:

  1. julia> @test 1 0.999999 rtol=1e-5
  2. Test Passed
  3. Expression: ≈(1, 0.999999, rtol = 1.0e-5)
  4. Evaluated: ≈(1, 0.999999; rtol = 1.0e-5)

Note that this is not a specific feature of the but rather a general feature of the @test macro: @test a <op> b key=val is transformed by the macro into @test op(a, b, key=val). It is, however, particularly useful for tests.

  1. Test.@inferred
  2. Test.@test_logs
  3. Test.@test_deprecated
  4. Test.@test_warn
  5. Test.@test_nowarn

Broken Tests

If a test fails consistently it can be changed to use the @test_broken macro. This will denote the test as Broken if the test continues to fail and alerts the user via an Error if the test succeeds.

  1. Test.@test_broken

@test_skip is also available to skip a test without evaluation, but counting the skipped test in the test set reporting. The test will not run but gives a Broken Result.

  1. Test.@test_skip

Creating Custom AbstractTestSet Types

Packages can create their own AbstractTestSet subtypes by implementing the record and finish methods. The subtype should have a one-argument constructor taking a description string, with any options passed in as keyword arguments.

  1. Test.record
  2. Test.finish

Test takes responsibility for maintaining a stack of nested testsets as they are executed, but any result accumulation is the responsibility of the AbstractTestSet subtype. You can access this stack with the get_testset and get_testset_depth methods. Note that these functions are not exported.

  1. Test.get_testset
  2. Test.get_testset_depth

Test also makes sure that nested @testset invocations use the same AbstractTestSet subtype as their parent unless it is set explicitly. It does not propagate any properties of the testset. Option inheritance behavior can be implemented by packages using the stack infrastructure that Test provides.

Defining a basic AbstractTestSet subtype might look like:

  1. import Test: Test, record, finish
  2. using Test: AbstractTestSet, Result, Pass, Fail, Error
  3. using Test: get_testset_depth, get_testset
  4. struct CustomTestSet <: Test.AbstractTestSet
  5. description::AbstractString
  6. foo::Int
  7. results::Vector
  8. # constructor takes a description string and options keyword arguments
  9. CustomTestSet(desc; foo=1) = new(desc, foo, [])
  10. end
  11. record(ts::CustomTestSet, child::AbstractTestSet) = push!(ts.results, child)
  12. record(ts::CustomTestSet, res::Result) = push!(ts.results, res)
  13. function finish(ts::CustomTestSet)
  14. # just record if we're not the top-level parent
  15. if get_testset_depth() > 0
  16. record(get_testset(), ts)
  17. end
  18. ts
  19. end

And using that testset looks like:

  1. @testset CustomTestSet foo=4 "custom testset inner 2" begin
  2. # this testset should inherit the type, but not the argument.
  3. @testset "custom testset inner" begin
  4. @test true
  5. end
  6. end

Test utilities

  1. Test.GenericArray
  2. Test.GenericDict
  3. Test.GenericOrder
  4. Test.GenericSet
  5. Test.GenericString
  6. Test.detect_ambiguities
  7. Test.detect_unbound_args
  1. DocTestSetup = nothing