老實說,我本身並不具有資訊工程相關背景,一開始也沒聽過什麼unit test。但自從踏入了生物資訊這個領域,隨著開發的東西增加,同時還有需要寫一些比較大型的專案,很多問題也跟著衍生出來。一直到了某次偶然的機會聽到了這個詞,還看到了別人怎麼在R語言的專案裡頭引入unit test這個東西,才開始理解這個步驟的意義。因此,為了寫好一個julia的專案,了解一下這個語言裡頭有哪些相應的工具可用也是必須的。
Test
在Julia底下直接就有個Test
的package可使用,它的使用方法也非常容易,直接呼叫它提供的macro
:
@test ex
@test f(args...) key=val ...
來看幾個實際的例子:
using Test
julia> x = "b"
"b"
julia> @test typeof(x) == String
Test Passed
julia> @test 5.0/3 ≈ 1.67 atol=0.01
Test Passed
另外,我們也可以用@test_throws
做一些例外
/錯誤
的測試,不過這些例外
/錯誤
基本上都是預先定義好的,有空來研究能不能增加新的錯誤定義:
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> foo(x) = length(x)^2
julia> @test_throws MethodError foo(:cat)
Test Passed
Thrown: MethodError
這樣一個一個測試終歸是麻煩且凌亂,有沒有辦法可以整理在一起、分門別類地做測試呢? 可以!用@testset
跟begin
區塊:
julia> @testset "Foo()測試" begin
@test foo("a") == 1
@test foo("ab") == 4
@test foo("abc") == 9
end
Test Summary: | Pass Total
Foo()測試 | 3 3
Test.DefaultTestSet("Foo()測試", Any[], 3, false)
julia> @testset "三角函數測試" begin
θ = 2/3 * π
@test sin(-θ) ≈ -sin(θ)
@test cos(-θ) ≈ cos(θ)
@test sin(2θ) ≈ 2*sin(θ)*cos(θ)
@test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2
end
Test Summary: | Pass Total
三角函數測試 | 4 4
Test.DefaultTestSet("三角函數測試", Any[], 4, false)
如果是寫一個Package的話,還可以充分利用巢狀結構來針對不同的module
做測試,或是對同一個module
做不同類型的測試:
julia> @testset "foo函數詳細測試" 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函數詳細測試 | 8 8
Test.DefaultTestSet("foo函數詳細測試", Any[DefaultTestSet("Animals", Any[], 2, false), DefaultTestSet("Arrays 1", Any[], 2, false), DefaultTestSet("Arrays 2", Any[], 2, false), DefaultTestSet("Arrays 3", Any[], 2, false)], 0, false)
除了這些之外,我們還可以用@infer
測試函數的返回值是否如預期
julia> f(a, b, c) = b > 1 ? 1 : 1.0
f (generic function with 1 method)
julia> typeof(f(1, 2, 3))
Int64
julia> @code_warntype f(1, 2, 3)
Body::Union{Float64, Int64}
1 1 ─ %1 = (Base.slt_int)(1, b)::Bool
└── goto #3 if not %1
2 ─ return 1
3 ─ return 1.0
julia> @inferred f(1,2,3)
ERROR: return type Int64 does not match inferred return type Union{Float64, Int64}
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] top-level scope at none:0
julia> @inferred max(1, 2)
2
另外根據官方文件說,他們還支援了@test_logs
,但我根據說明文件操作時julia卻拋出錯誤訊息,不知有沒有人遇過這種情況?
julia> function foo(n)
@info "Doing foo with n=$n"
for i=1:n
@debug "Iteration $i"
end
42
end
foo (generic function with 1 method)
julia> @test_logs (:info,"Doing foo with n=2") foo(2)
42
julia> @test_logs (:info,"Doing foo with n=2") (:debug,"Iteration 1") (:debug,"Iteration 2") min_level=Debug foo(2)
Error During Test at REPL[45]:1
Test threw exception
Expression: foo(2)
UndefVarError: Debug not defined
Stacktrace:
[1] top-level scope at none:0
[2] eval(::Module, ::Any) at ./boot.jl:319
[3] eval_user_input(::Any, ::REPL.REPLBackend) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.0/REPL/src/REPL.jl:85
[4] macro expansion at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.0/REPL/src/REPL.jl:117 [inlined]
[5] (::getfield(REPL, Symbol("##28#29")){REPL.REPLBackend})() at ./task.jl:259
ERROR: There was an error during testing
已經到了測試工具了啊~~~
那你有沒有興趣寫寫 log 工具呢XD
https://discourse.julialang.org/t/recommendation-for-logging/4560
推推
@杜岳華,研究一下再跟你說 XD