Unit Testing
Testing Base Julia
جوليا تحت تطوير سريع ولديها مجموعة اختبارات شاملة للتحقق من الوظائف عبر منصات متعددة. إذا قمت ببناء جوليا من المصدر، يمكنك تشغيل مجموعة الاختبارات هذه باستخدام make test
. في التثبيت الثنائي، يمكنك تشغيل مجموعة الاختبارات باستخدام Base.runtests()
.
Base.runtests
— FunctionBase.runtests(tests=["all"]; ncores=ceil(Int, Sys.CPU_THREADS / 2),
exit_on_error=false, revise=false, [seed])
قم بتشغيل اختبارات الوحدة في جوليا المدرجة في tests
، والتي يمكن أن تكون إما سلسلة نصية أو مصفوفة من السلاسل النصية، باستخدام معالجات ncores
. إذا كانت exit_on_error
تساوي false
، فعندما يفشل اختبار واحد، ستظل جميع الاختبارات المتبقية في ملفات أخرى قيد التشغيل؛ وإلا سيتم تجاهلها، عندما تكون exit_on_error == true
. إذا كانت revise
تساوي true
، يتم استخدام حزمة Revise
لتحميل أي تعديلات على Base
أو المكتبات القياسية قبل تشغيل الاختبارات. إذا تم توفير بذور عبر وسيط الكلمة الرئيسية، يتم استخدامها لتوليد الأرقام العشوائية العالمية في السياق الذي يتم فيه تشغيل الاختبارات؛ وإلا يتم اختيار البذور عشوائيًا.
Basic Unit Tests
وحدة Test
توفر وظائف بسيطة لاختبار الوحدات. اختبار الوحدات هو وسيلة لرؤية ما إذا كان الكود الخاص بك صحيحًا من خلال التحقق من أن النتائج هي ما تتوقعه. يمكن أن يكون مفيدًا لضمان أن الكود الخاص بك لا يزال يعمل بعد إجراء تغييرات، ويمكن استخدامه عند التطوير كوسيلة لتحديد السلوكيات التي يجب أن يمتلكها الكود الخاص بك عند الانتهاء. قد ترغب أيضًا في الاطلاع على الوثائق لـ adding tests to your Julia Package.
يمكن إجراء اختبار وحدة بسيط باستخدام الماكرو @test
و @test_throws
:
Test.@test
— Macro@test ex
@test f(args...) key=val ...
@test ex broken=true
@test ex skip=true
اختبر أن التعبير ex
يقيم إلى true
. إذا تم تنفيذه داخل @testset
، ارجع Pass
Result
إذا كان كذلك، وFail
Result
إذا كان false
، وError
Result
إذا لم يكن بالإمكان تقييمه. إذا تم تنفيذه خارج @testset
، ارمِ استثناءً بدلاً من إرجاع Fail
أو Error
.
أمثلة
julia> @test true
Test Passed
julia> @test [1, 2] + [2, 1] == [3, 3]
Test Passed
شكل @test f(args...) key=val...
يعادل كتابة @test f(args..., key=val...)
، وهو مفيد عندما يكون التعبير عبارة عن استدعاء باستخدام بناء الجملة infix مثل المقارنات التقريبية:
julia> @test π ≈ 3.14 atol=0.01
Test Passed
هذا يعادل الاختبار الأكثر قبحًا @test ≈(π, 3.14, atol=0.01)
. من الخطأ تقديم أكثر من تعبير واحد ما لم يكن الأول تعبير استدعاء والباقي تعيينات (k=v
).
يمكنك استخدام أي مفتاح لوسائط key=val
، باستثناء broken
و skip
، التي لها معاني خاصة في سياق @test
:
broken=cond
تشير إلى اختبار يجب أن ينجح ولكن يفشل باستمرار حاليًا عندما يكونcond==true
. اختبارات أن التعبيرex
يقيم إلىfalse
أو يسبب استثناء. يرجعBroken
Result
إذا كان كذلك، أوError
Result
إذا كان التعبير يقيم إلىtrue
. يتم تقييم@test ex
عندما يكونcond==false
.skip=cond
يميز اختبارًا لا ينبغي تنفيذه ولكن يجب تضمينه في تقرير ملخص الاختبار كـBroken
، عندما يكونcond==true
. يمكن أن يكون هذا مفيدًا للاختبارات التي تفشل بشكل متقطع، أو اختبارات الوظائف التي لم يتم تنفيذها بعد. يتم تقييم@test ex
عندما يكونcond==false
.
أمثلة
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
تتطلب وسائط الكلمات الرئيسية broken
و skip
على الأقل Julia 1.7.
Test.@test_throws
— Macro@test_throws exception expr
يختبر أن التعبير expr
يرمي exception
. قد يحدد الاستثناء إما نوعًا، أو سلسلة، أو تعبيرًا عاديًا، أو قائمة من السلاسل التي تحدث في رسالة الخطأ المعروضة، أو دالة مطابقة، أو قيمة (سيتم اختبارها من أجل المساواة من خلال مقارنة الحقول). لاحظ أن @test_throws
لا يدعم شكل الكلمة الرئيسية المتأخرة.
تتطلب القدرة على تحديد أي شيء بخلاف نوع أو قيمة كـ exception
وجود Julia v1.8 أو أحدث.
أمثلة
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))."
في المثال الأخير، بدلاً من مطابقة سلسلة واحدة، كان من الممكن بدلاً من ذلك تنفيذ ذلك مع:
["Try", "Complex"]
(قائمة من السلاسل)r"Try sqrt\([Cc]omplex"
(تعبير عادي)str -> occursin("complex", str)
(دالة مطابقة)
على سبيل المثال، لنفترض أننا نريد التحقق من أن دالتنا الجديدة foo(x)
تعمل كما هو متوقع:
julia> using Test
julia> foo(x) = length(x)^2
foo (generic function with 1 method)
إذا كانت الحالة صحيحة، يتم إرجاع Pass
:
julia> @test foo("bar") == 9
Test Passed
julia> @test foo("fizz") >= 10
Test Passed
إذا كانت الحالة خاطئة، فسيتم إرجاع فشل
ويتم طرح استثناء:
julia> @test foo("f") == 20
Test Failed at none:1
Expression: foo("f") == 20
Evaluated: 1 == 20
ERROR: There was an error during testing
إذا لم يكن بالإمكان تقييم الشرط بسبب حدوث استثناء، والذي يحدث في هذه الحالة لأن length
غير معرف للرموز، يتم إرجاع كائن Error
ويتم طرح استثناء:
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
إذا كنا نتوقع أن تقييم تعبير يجب أن يرمي استثناءً، فيمكننا استخدام @test_throws
للتحقق من حدوث ذلك:
julia> @test_throws MethodError foo(:cat)
Test Passed
Thrown: MethodError
Working with Test Sets
عادةً ما يتم استخدام عدد كبير من الاختبارات للتأكد من أن الوظائف تعمل بشكل صحيح على مجموعة من المدخلات. في حالة فشل اختبار، فإن السلوك الافتراضي هو رمي استثناء على الفور. ومع ذلك، من المفضل عادةً تشغيل بقية الاختبارات أولاً للحصول على صورة أفضل عن عدد الأخطاء الموجودة في الكود الذي يتم اختباره.
سيقوم @testset
بإنشاء نطاق محلي خاص به عند تشغيل الاختبارات فيه.
يمكن استخدام ماكرو @testset
لتجميع الاختبارات في مجموعات. سيتم تشغيل جميع الاختبارات في مجموعة الاختبار، وفي نهاية مجموعة الاختبار سيتم طباعة ملخص. إذا فشل أي من الاختبارات، أو لم يكن بالإمكان تقييمه بسبب خطأ، فستقوم مجموعة الاختبار بعد ذلك بإلقاء استثناء TestSetException
.
Test.@testset
— Macro@testset [CustomTestSet] [options...] ["الوصف"] begin test_ex end
@testset [CustomTestSet] [options...] ["الوصف $v"] for v in itr test_ex end
@testset [CustomTestSet] [options...] ["الوصف $v, $w"] for v in itrv, w in itrw test_ex end
@testset [CustomTestSet] [options...] ["الوصف"] test_func()
@testset let v = v, w = w; test_ex; end
مع begin/end أو استدعاء دالة
عند استخدام @testset، مع begin/end أو استدعاء دالة واحدة، يبدأ الماكرو مجموعة اختبارات جديدة لتقييم التعبير المعطى.
إذا لم يتم إعطاء نوع مجموعة اختبارات مخصص، فإنه يتضمن افتراضيًا إنشاء DefaultTestSet
. تسجل DefaultTestSet
جميع النتائج، وإذا كان هناك أي Fail
s أو Error
s، يتم طرح استثناء في نهاية مجموعة الاختبارات الرئيسية (غير المتداخلة)، مع ملخص لنتائج الاختبار.
يمكن إعطاء أي نوع مجموعة اختبارات مخصص (فرعي من AbstractTestSet
) وسيتم استخدامه أيضًا لأي استدعاءات متداخلة لـ @testset
. يتم تطبيق الخيارات المعطاة فقط على مجموعة الاختبارات حيث تم إعطاؤها. تقبل مجموعة الاختبارات الافتراضية ثلاثة خيارات منطقية:
verbose
: إذا كانتtrue
، يتم عرض ملخص النتائج لمجموعات الاختبارات المتداخلة حتى عندما تمر جميعها (الافتراضي هوfalse
).showtiming
: إذا كانتtrue
، يتم عرض مدة كل مجموعة اختبارات معروضة (الافتراضي هوtrue
).failfast
: إذا كانتtrue
، فإن أي فشل في الاختبار أو خطأ سيؤدي إلى عودة مجموعة الاختبارات وأي مجموعات اختبارات فرعية على الفور (الافتراضي هوfalse
). يمكن أيضًا تعيين هذا عالميًا عبر متغير البيئةJULIA_TEST_FAILFAST
.
@testset test_func()
يتطلب على الأقل جوليا 1.8.
failfast
يتطلب على الأقل جوليا 1.9.
تقبل سلسلة الوصف التداخل من مؤشرات الحلقة. إذا لم يتم تقديم وصف، يتم بناء واحد بناءً على المتغيرات. إذا تم تقديم استدعاء دالة، سيتم استخدام اسمها. تتجاوز سلاسل الوصف الصريحة هذا السلوك.
بشكل افتراضي، سيعيد ماكرو @testset
كائن مجموعة الاختبارات نفسه، على الرغم من أنه يمكن تخصيص هذا السلوك في أنواع مجموعات الاختبارات الأخرى. إذا تم استخدام حلقة for
، فإن الماكرو يجمع ويعيد قائمة بقيم الإرجاع لطريقة finish
، والتي ستعيد افتراضيًا قائمة بكائنات مجموعة الاختبارات المستخدمة في كل تكرار.
قبل تنفيذ جسم @testset
، هناك استدعاء ضمني لـ Random.seed!(seed)
حيث أن seed
هو البذور الحالية لمولد الأرقام العشوائية العالمي. علاوة على ذلك، بعد تنفيذ الجسم، يتم استعادة حالة مولد الأرقام العشوائية العالمي إلى ما كانت عليه قبل @testset
. يهدف هذا إلى تسهيل إعادة الإنتاج في حالة الفشل، والسماح بإعادة ترتيب سلس لـ @testset
s بغض النظر عن تأثيرها على حالة مولد الأرقام العشوائية العالمي.
أمثلة
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;
ملخص الاختبار: | تمر إجمالي الوقت
الهويات المثلثية | 4 4 0.2s
@testset for
عند استخدام @testset for
، يبدأ الماكرو اختبارًا جديدًا لكل تكرار من الحلقة المقدمة. دلالات كل مجموعة اختبارات متطابقة بخلاف ذلك مع حالة begin/end
(كما لو تم استخدامها لكل تكرار حلقي).
@testset let
عند استخدام @testset let
، يبدأ الماكرو مجموعة اختبارات شفافة مع الكائن المعطى المضاف ككائن سياق لأي اختبار فاشل يحتوي عليه. هذا مفيد عند إجراء مجموعة من الاختبارات ذات الصلة على كائن أكبر واحد ومن المرغوب فيه طباعة هذا الكائن الأكبر عند فشل أي من الاختبارات الفردية. لا تقدم مجموعات الاختبارات الشفافة مستويات إضافية من التعشيش في تسلسل مجموعات الاختبارات ويتم تمريرها مباشرة إلى مجموعة الاختبارات الأصلية (مع إضافة كائن السياق إلى أي اختبارات فاشلة).
@testset let
يتطلب على الأقل جوليا 1.9.
تم دعم تعيينات let
متعددة منذ جوليا 1.10.
أمثلة
julia> @testset let logi = log(im)
@test imag(logi) == π/2
@test !iszero(real(logi))
end
فشل الاختبار في none:3
التعبير: !(iszero(real(logi)))
السياق: logi = 0.0 + 1.5707963267948966im
خطأ: حدث خطأ أثناء الاختبار
julia> @testset let logi = log(im), op = !iszero
@test imag(logi) == π/2
@test op(real(logi))
end
فشل الاختبار في none:3
التعبير: op(real(logi))
السياق: logi = 0.0 + 1.5707963267948966im
op = !iszero
خطأ: حدث خطأ أثناء الاختبار
Test.TestSetException
— TypeTestSetException
يتم رميها عندما تنتهي مجموعة الاختبار وليس كل الاختبارات قد نجحت.
يمكننا وضع اختباراتنا لدالة foo(x)
في مجموعة الاختبار:
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
يمكن أيضًا تداخل مجموعات الاختبار:
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
بالإضافة إلى استدعاء الدوال:
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
يمكن استخدام هذا للسماح بتحليل مجموعات الاختبار، مما يسهل تشغيل مجموعات الاختبار الفردية عن طريق تشغيل الوظائف المرتبطة بدلاً من ذلك. لاحظ أنه في حالة الوظائف، سيتم إعطاء مجموعة الاختبار اسم الوظيفة المستدعاة. في حالة عدم وجود أي فشل في مجموعة اختبار متداخلة، كما حدث هنا، سيتم إخفاؤها في الملخص، ما لم يتم تمرير خيار 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
إذا كان لدينا فشل في الاختبار، فستظهر فقط التفاصيل الخاصة بمجموعات الاختبار الفاشلة:
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
يمكن استخدام الماكرو @test_logs
لاختبار بيانات السجل، أو استخدام TestLogger
.
Test.@test_logs
— Macro@test_logs [log_patterns...] [keywords] التعبير
اجمع قائمة بسجلات السجل التي تم إنشاؤها بواسطة التعبير
باستخدام collect_test_logs
، تحقق من أنها تتطابق مع تسلسل log_patterns
، وأعد قيمة التعبير
. توفر keywords
بعض التصفية البسيطة لسجلات السجل: يتحكم الكلمة الرئيسية min_level
في الحد الأدنى لمستوى السجل الذي سيتم جمعه للاختبار، وتحدد الكلمة الرئيسية match_mode
كيفية إجراء المطابقة (الافتراضي :all
يتحقق من أن جميع السجلات والأنماط تتطابق بشكل زوجي؛ استخدم :any
للتحقق من أن النمط يتطابق على الأقل مرة واحدة في أي مكان في التسلسل).
أكثر نمط سجل مفيد هو مجموعة بسيطة من الشكل (level,message)
. يمكن استخدام عدد مختلف من عناصر المجموعة لمطابقة بيانات التعريف الأخرى للسجل، والتي تتوافق مع المعاملات المرسلة إلى AbstractLogger
عبر وظيفة handle_message
: (level,message,module,group,id,file,line)
. سيتم مطابقة العناصر الموجودة بشكل زوجي مع حقول سجل السجل باستخدام ==
بشكل افتراضي، مع الحالات الخاصة التي يمكن استخدام Symbol
s لمستويات السجل القياسية، وRegex
s في النمط ستتطابق مع حقول السلسلة أو الرمز باستخدام occursin
.
أمثلة
اعتبر وظيفة تسجل تحذيرًا، وعدة رسائل تصحيح:
function foo(n)
@info "Doing foo with n=$n"
for i=1:n
@debug "Iteration $i"
end
42
end
يمكننا اختبار رسالة المعلومات باستخدام
@test_logs (:info,"Doing foo with n=2") foo(2)
إذا أردنا أيضًا اختبار رسائل التصحيح، يجب تمكينها باستخدام الكلمة الرئيسية min_level
:
using Logging
@test_logs (:info,"Doing foo with n=2") (:debug,"Iteration 1") (:debug,"Iteration 2") min_level=Logging.Debug foo(2)
إذا كنت تريد اختبار أن بعض الرسائل المحددة يتم إنشاؤها مع تجاهل البقية، يمكنك تعيين الكلمة الرئيسية match_mode=:any
:
using Logging
@test_logs (:info,) (:debug,"Iteration 42") min_level=Logging.Debug match_mode=:any foo(100)
يمكن ربط الماكرو مع @test
لاختبار القيمة المعادة أيضًا:
@test (@test_logs (:info,"Doing foo with n=2") foo(2)) == 42
إذا كنت تريد اختبار غياب التحذيرات، يمكنك حذف تحديد أنماط السجل وتعيين min_level
وفقًا لذلك:
# اختبار أن التعبير لا يسجل أي رسائل عندما يكون مستوى السجل تحذير:
@test_logs min_level=Logging.Warn @info("Some information") # ينجح
@test_logs min_level=Logging.Warn @warn("Some information") # يفشل
إذا كنت تريد اختبار غياب التحذيرات (أو رسائل الخطأ) في stderr
التي لم يتم إنشاؤها بواسطة @warn
، انظر @test_nowarn
.
Test.TestLogger
— TypeTestLogger(; min_level=Info, catch_exceptions=false)
أنشئ TestLogger
الذي يلتقط الرسائل المسجلة في حقل logs::Vector{LogRecord}
.
قم بتعيين min_level
للتحكم في LogLevel
، وcatch_exceptions
لتحديد ما إذا كان يجب التقاط الاستثناءات التي تم طرحها كجزء من توليد حدث السجل، وrespect_maxlog
لتحديد ما إذا كان يجب اتباع قاعدة تسجيل الرسائل مع maxlog=n
لبعض الأعداد الصحيحة n
في أقصى حد n
مرات.
انظر أيضًا: LogRecord
.
أمثلة
julia> using Test, Logging
julia> f() = @info "Hi" number=5;
julia> test_logger = TestLogger();
julia> with_logger(test_logger) do
f()
@info "Bye!"
end
julia> @test test_logger.logs[1].message == "Hi"
Test Passed
julia> @test test_logger.logs[1].kwargs[:number] == 5
Test Passed
julia> @test test_logger.logs[2].message == "Bye!"
Test Passed
Test.LogRecord
— TypeLogRecord
يخزن نتائج حدث سجل واحد. الحقول:
level
:LogLevel
لرسالة السجلmessage
: المحتوى النصي لرسالة السجل_module
: وحدة حدث السجلgroup
: مجموعة السجل (بشكل افتراضي، اسم الملف الذي يحتوي على حدث السجل)id
: معرف حدث السجلfile
: الملف الذي يحتوي على حدث السجلline
: السطر داخل الملف لحدث السجلkwargs
: أي معلمات مفتاحية تم تمريرها إلى حدث السجل
Other Test Macros
نظرًا لأن الحسابات على القيم العائمة قد تكون غير دقيقة، يمكنك إجراء فحوصات المساواة التقريبية باستخدام إما @test a ≈ b
(حيث ≈
، الذي يتم كتابته عبر إكمال التبويب لـ \approx
، هو الدالة isapprox
) أو استخدام 4d61726b646f776e2e436f64652822222c20226973617070726f782229_40726566
مباشرة.
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
يمكنك تحديد التسامحات النسبية والمطلقة عن طريق تعيين وسيطتي rtol
و atol
في دالة isapprox
، على التوالي، بعد مقارنة ≈
:
julia> @test 1 ≈ 0.999999 rtol=1e-5
Test Passed
لاحظ أن هذه ليست ميزة محددة لـ ≈
ولكنها ميزة عامة لماكرو @test
: يتم تحويل @test a <op> b key=val
بواسطة الماكرو إلى @test op(a, b, key=val)
. ومع ذلك، فهي مفيدة بشكل خاص لاختبارات ≈
.
Test.@inferred
— Macro@inferred [AllowedType] f(x)
يختبر أن تعبير الاستدعاء f(x)
يُرجع قيمة من نفس النوع الذي استنتجه المترجم. من المفيد التحقق من استقرار النوع.
يمكن أن يكون f(x)
أي تعبير استدعاء. يُرجع نتيجة f(x)
إذا تطابقت الأنواع، وError
Result
إذا وجد أنواعًا مختلفة.
اختياريًا، يُخفف AllowedType
الاختبار، مما يجعله ينجح عندما يتطابق نوع f(x)
مع النوع المستنتج مع مراعاة AllowedType
، أو عندما يكون نوع الإرجاع فرعًا من AllowedType
. هذا مفيد عند اختبار استقرار النوع للدوال التي تُرجع اتحادًا صغيرًا مثل Union{Nothing, T}
أو 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: return type Int64 does not match inferred return type 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: return type Float64 does not match inferred return type 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: return type Int64 does not match inferred return type Union{Missing, Float64, Int64}
[...]
Test.@test_deprecated
— Macro@test_deprecated [pattern] expression
عند استخدام --depwarn=yes
، اختبر أن expression
يصدر تحذير إهمال وأعد القيمة لـ expression
. سيتم مطابقة سلسلة رسالة السجل مع pattern
الذي يكون افتراضيًا r"deprecated"i
.
عند استخدام --depwarn=no
، ببساطة أعد نتيجة تنفيذ expression
. عند استخدام --depwarn=error
، تحقق من أنه تم طرح ErrorException.
أمثلة
# تم إهماله في جوليا 0.7
@test_deprecated num2hex(1)
# يمكن اختبار القيمة المعادة عن طريق الربط مع @test:
@test (@test_deprecated num2hex(1)) == "0000000000000001"
Test.@test_warn
— Macro@test_warn msg expr
اختبر ما إذا كانت تقييم expr
ينتج عنه مخرجات stderr
تحتوي على سلسلة msg
أو تتطابق مع التعبير العادي msg
. إذا كانت msg
دالة منطقية، يتم اختبار ما إذا كانت msg(output)
تعيد true
. إذا كانت msg
عبارة عن مجموعة أو مصفوفة، يتم التحقق من أن مخرجات الخطأ تحتوي/تتطابق مع كل عنصر في msg
. تعيد نتيجة تقييم expr
.
انظر أيضًا @test_nowarn
للتحقق من عدم وجود مخرجات خطأ.
ملاحظة: لا يمكن اختبار التحذيرات الناتجة عن @warn
باستخدام هذه الماكرو. استخدم @test_logs
بدلاً من ذلك.
Test.@test_nowarn
— Macro@test_nowarn expr
اختبر ما إذا كانت تقييم expr
ينتج عنه إخراج فارغ stderr
(لا تحذيرات أو رسائل أخرى). يُرجع نتيجة تقييم expr
.
ملاحظة: لا يمكن اختبار غياب التحذيرات الناتجة عن @warn
باستخدام هذه الماكرو. استخدم @test_logs
بدلاً من ذلك.
Broken Tests
إذا فشل الاختبار باستمرار، يمكن تغييره لاستخدام ماكرو @test_broken
. سيشير هذا إلى أن الاختبار معطل
إذا استمر الاختبار في الفشل وينبه المستخدم عبر خطأ
إذا نجح الاختبار.
Test.@test_broken
— Macro@test_broken ex
@test_broken f(args...) key=val ...
تشير إلى اختبار يجب أن ينجح ولكنه يفشل باستمرار في الوقت الحالي. تختبر أن التعبير ex
يقيم إلى false
أو يسبب استثناء. تعيد Result
من نوع Broken
إذا حدث ذلك، أو Result
من نوع Error
إذا قيم التعبير إلى true
. هذا يعادل @test ex broken=true
.
شكل @test_broken f(args...) key=val...
يعمل كما هو الحال في ماكرو @test
.
أمثلة
julia> @test_broken 1 == 2
Test Broken
Expression: 1 == 2
julia> @test_broken 1 == 2 atol=0.1
Test Broken
Expression: ==(1, 2, atol = 0.1)
@test_skip
متاح أيضًا لتخطي اختبار دون تقييم، ولكن مع احتساب الاختبار المتخطي في تقارير مجموعة الاختبارات. لن يتم تشغيل الاختبار ولكنه يعطي نتيجة مكسورة
.
Test.@test_skip
— Macro@test_skip ex
@test_skip f(args...) key=val ...
يحدد اختبارًا لا ينبغي تنفيذه ولكن يجب تضمينه في تقارير ملخص الاختبار كـ Broken
. يمكن أن يكون هذا مفيدًا للاختبارات التي تفشل بشكل متقطع، أو اختبارات الوظائف التي لم يتم تنفيذها بعد. هذا يعادل @test ex skip=true
.
يعمل شكل @test_skip f(args...) key=val...
كما هو الحال في ماكرو @test
.
أمثلة
julia> @test_skip 1 == 2
Test Broken
Skipped: 1 == 2
julia> @test_skip 1 == 2 atol=0.1
Test Broken
Skipped: ==(1, 2, atol = 0.1)
Test result types
Test.Result
— TypeTest.Result
تنتج جميع الاختبارات كائن نتيجة. قد يتم تخزين هذا الكائن أو لا، اعتمادًا على ما إذا كانت الاختبار جزءًا من مجموعة اختبارات.
Test.Pass
— TypeTest.Pass <: Test.Result
كانت حالة الاختبار صحيحة، أي أن التعبير تم تقييمه إلى صحيح أو تم طرح الاستثناء الصحيح.
Test.Fail
— TypeTest.Fail <: Test.Result
كانت حالة الاختبار خاطئة، أي أن التعبير تم تقييمه إلى خطأ أو لم يتم رمي الاستثناء الصحيح.
Test.Error
— TypeTest.Error <: Test.Result
لم يكن من الممكن تقييم شرط الاختبار بسبب استثناء، أو تم تقييمه إلى شيء آخر غير Bool
. في حالة @test_broken
، يتم استخدامه للإشارة إلى حدوث Pass
Result
غير متوقع.
Test.Broken
— TypeTest.Broken <: Test.Result
شرط الاختبار هو النتيجة المتوقعة (الفاشلة) لاختبار معطل، أو تم تخطيه صراحة باستخدام @test_skip
.
Creating Custom AbstractTestSet
Types
يمكن للحزم إنشاء أنواع فرعية خاصة بها من AbstractTestSet
عن طريق تنفيذ طرق record
و finish
. يجب أن تحتوي النوع الفرعي على مُنشئ ذو وسيط واحد يأخذ سلسلة وصف، مع تمرير أي خيارات كوسائط مفتاحية.
Test.record
— Functionrecord(ts::AbstractTestSet, res::Result)
سجل نتيجة لمجموعة الاختبار. يتم استدعاء هذه الدالة بواسطة بنية @testset
في كل مرة تكتمل فيها ماكرو @test
المحتوى، ويتم إعطاؤها نتيجة الاختبار (التي يمكن أن تكون Error
). سيتم استدعاء هذا أيضًا مع Error
إذا تم طرح استثناء داخل كتلة الاختبار ولكن خارج سياق @test
.
Test.finish
— Functionfinish(ts::AbstractTestSet)
قم بأي معالجة نهائية ضرورية لمجموعة الاختبار المعطاة. يتم استدعاء هذا بواسطة بنية @testset
بعد تنفيذ كتلة الاختبار.
يجب على الأنواع الفرعية المخصصة لـ AbstractTestSet
استدعاء record
على الوالد (إذا كان موجودًا) لإضافة نفسها إلى شجرة نتائج الاختبار. يمكن تنفيذ ذلك كما يلي:
if get_testset_depth() != 0
# ربط مجموعة الاختبار هذه بمجموعة الاختبار الوالد
parent_ts = get_testset()
record(parent_ts, self)
return self
end
Test
يتحمل مسؤولية الحفاظ على مكدس من مجموعات الاختبار المتداخلة أثناء تنفيذها، ولكن أي تراكم للنتائج هو مسؤولية نوع AbstractTestSet
. يمكنك الوصول إلى هذا المكدس باستخدام طرق get_testset
و get_testset_depth
. لاحظ أن هذه الوظائف غير مصدرة.
Test.get_testset
— Functionget_testset()
استرجع مجموعة الاختبار النشطة من التخزين المحلي للمهمة. إذا لم تكن هناك مجموعة اختبار نشطة، استخدم مجموعة الاختبار الافتراضية الاحتياطية.
Test.get_testset_depth
— Functionget_testset_depth()
إرجاع عدد مجموعات الاختبار النشطة، دون احتساب مجموعة الاختبار الافتراضية
Test
يضمن أيضًا أن استدعاءات @testset
المتداخلة تستخدم نفس نوع AbstractTestSet
الفرعي كوالدها ما لم يتم تعيينه بشكل صريح. إنه لا ينقل أي خصائص لمجموعة الاختبار. يمكن تنفيذ سلوك وراثة الخيارات بواسطة الحزم باستخدام بنية المكدس التي يوفرها Test
.
قد يبدو تعريف نوع فرعي أساسي من AbstractTestSet
كما يلي:
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
ويبدو أن استخدام مجموعة الاختبار تلك:
@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
لكي تستخدم مجموعة اختبارات مخصصة وتظهر النتائج المسجلة كجزء من أي مجموعة اختبارات افتراضية خارجية، يجب أيضًا تعريف Test.get_test_counts
. قد يبدو هذا كما يلي:
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
يمسك الحالة لجمع نتائج مجموعة الاختبار بشكل متكرر لأغراض العرض.
الحقول:
customized
: ما إذا كانت الدالةget_test_counts
قد تم تخصيصها لمجموعة الاختبارAbstractTestSet
التي ينتمي إليها كائن العد هذا. إذا تم تعريف طريقة مخصصة، يجب دائمًا تمريرtrue
إلى المُنشئ.passes
: عدد استدعاءات@test
الناجحة.fails
: عدد استدعاءات@test
الفاشلة.errors
: عدد استدعاءات@test
التي حدثت بها أخطاء.broken
: عدد استدعاءات@test
المعطلة.passes
: العدد التراكمي لاستدعاءات@test
الناجحة.fails
: العدد التراكمي لاستدعاءات@test
الفاشلة.errors
: العدد التراكمي لاستدعاءات@test
التي حدثت بها أخطاء.broken
: العدد التراكمي لاستدعاءات@test
المعطلة.duration
: إجمالي المدة التي استغرقتها مجموعة الاختبارAbstractTestSet
المعنية، كـString
منسق.
Test.get_test_counts
— Function" gettestcounts(::AbstractTestSet) -> TestCounts
دالة تكرارية تحسب عدد نتائج الاختبار من كل نوع مباشرة في مجموعة الاختبار، وتجمعها عبر مجموعات الاختبار الفرعية.
يجب على AbstractTestSet
المخصصة تنفيذ هذه الدالة للحصول على إجمالياتها محسوبة ومعروضة مع DefaultTestSet
أيضًا.
إذا لم يتم تنفيذ ذلك لمجموعة TestSet
مخصصة، فإن الطباعة تعود للإبلاغ عن x
للفشل و ?s
للمدة.
Test.format_duration
— Functionformat_duration(::AbstractTestSet)
إرجاع سلسلة منسقة لطباعة المدة التي استغرقها مجموعة الاختبار.
إذا لم يتم تعريفها، يتم الرجوع إلى "?s"
.
Test.print_test_results
— Functionprint_test_results(ts::AbstractTestSet, depth_pad=0)
طباعة نتائج AbstractTestSet
كجدول منسق.
يشير depth_pad
إلى مقدار الحشو الذي يجب إضافته أمام جميع المخرجات.
يتم استدعاؤه داخل Test.finish
، إذا كانت مجموعة الاختبار التي تم إنهاؤها
هي مجموعة الاختبار العليا.
Test utilities
Test.GenericArray
— Typeيمكن استخدام GenericArray
لاختبار واجهات برمجة التطبيقات للمصفوفات العامة التي تتبع واجهة AbstractArray
، لضمان أن الوظائف يمكن أن تعمل مع أنواع المصفوفات بخلاف نوع Array
القياسي.
Test.GenericDict
— Typeيمكن استخدام GenericDict
لاختبار واجهات برمجة التطبيقات الخاصة بالقواميس العامة التي تتبع واجهة AbstractDict
، لضمان أن الوظائف يمكن أن تعمل مع الأنواع المرتبطة بالإضافة إلى نوع Dict
القياسي.
Test.GenericOrder
— Typeيمكن استخدام GenericOrder
لاختبار واجهات برمجة التطبيقات لدعمها لأنواع مرتبة عامة.
Test.GenericSet
— Typeيمكن استخدام GenericSet
لاختبار واجهات برمجة التطبيقات الخاصة بالمجموعات العامة التي تتبع واجهة AbstractSet
، لضمان أن الوظائف يمكن أن تعمل مع أنواع المجموعات بخلاف الأنواع القياسية Set
و BitSet
.
Test.GenericString
— Typeيمكن استخدام GenericString
لاختبار واجهات برمجة التطبيقات الخاصة بالسلاسل العامة التي تتبع واجهة AbstractString
، لضمان أن الوظائف يمكن أن تعمل مع أنواع السلاسل بخلاف نوع String
القياسي.
Test.detect_ambiguities
— Functiondetect_ambiguities(mod1, mod2...; recursive=false,
ambiguous_bottom=false,
allowed_undefineds=nothing)
إرجاع متجه من أزواج (Method,Method)
من الطرق الغامضة المعرفة في الوحدات المحددة. استخدم recursive=true
للاختبار في جميع الوحدات الفرعية.
يتحكم ambiguous_bottom
في ما إذا كانت الغموضات التي يتم تحفيزها فقط بواسطة معلمات نوع Union{}
مشمولة؛ في معظم الحالات، من المحتمل أن ترغب في تعيين هذا إلى false
. انظر Base.isambiguous
.
انظر Test.detect_unbound_args
لشرح allowed_undefineds
.
allowed_undefineds
يتطلب على الأقل Julia 1.8.
Test.detect_unbound_args
— Functiondetect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing)
إرجاع متجه من Method
s التي قد تحتوي على معلمات نوع غير مرتبطة. استخدم recursive=true
للاختبار في جميع الوحدات الفرعية.
بشكل افتراضي، أي رموز غير معرفة تؤدي إلى تحذير. يمكن كتم هذا التحذير من خلال تقديم مجموعة من GlobalRef
s التي يمكن تخطي التحذير بشأنها. على سبيل المثال، تعيين
allowed_undefineds = Set([GlobalRef(Base, :active_repl),
GlobalRef(Base, :active_repl_backend)])
سوف يكتم التحذيرات حول Base.active_repl
و Base.active_repl_backend
.
allowed_undefineds
يتطلب على الأقل Julia 1.8.
Workflow for Testing Packages
باستخدام الأدوات المتاحة لنا في الأقسام السابقة، إليك سير عمل محتمل لإنشاء حزمة وإضافة اختبارات لها.
Generating an Example Package
لإجراء هذه العملية، سنقوم بإنشاء حزمة تسمى Example
:
pkg> generate Example
shell> cd Example
shell> mkdir test
pkg> activate .
Creating Sample Functions
الشرط رقم واحد لاختبار حزمة هو وجود وظائف للاختبار. لهذا، سنضيف بعض الوظائف البسيطة إلى Example
التي يمكننا اختبارها. أضف ما يلي إلى 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
من داخل جذر حزمة Example
، انتقل إلى دليل test
، قم بتنشيط بيئة جديدة هناك، وأضف حزمة Test
إلى البيئة:
shell> cd test
pkg> activate .
(test) pkg> add Test
Testing Our Package
الآن، نحن مستعدون لإضافة اختبارات إلى Example
. من الممارسات القياسية إنشاء ملف داخل دليل test
يسمى runtests.jl
يحتوي على مجموعات الاختبار التي نريد تشغيلها. اذهب وأنشئ ذلك الملف داخل دليل test
وأضف الكود التالي إليه:
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
سنحتاج إلى إنشاء هذين الملفين المضمنين، math_tests.jl
و greeting_tests.jl
، وإضافة بعض الاختبارات إليهما.
ملاحظة: لاحظ كيف أننا لم نضطر إلى تحديد إضافة
Example
إلىProject.toml
لبيئةtest
. هذه فائدة من نظام اختبار جوليا حيث يمكنك read about more here.
Writing Tests for math_tests.jl
باستخدام معرفتنا بـ Test.jl
، إليك بعض الاختبارات النموذجية التي يمكننا إضافتها إلى 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
باستخدام معرفتنا بـ Test.jl
، إليك بعض الاختبارات النموذجية التي يمكننا إضافتها إلى greeting_tests.jl
:
@testset "Testset 3" begin
@test "Hello world!" == greet()
@test_throws MethodError greet("Antonia")
end
Testing Our Package
الآن بعد أن أضفنا اختباراتنا وملف runtests.jl
في test
، يمكننا اختبار حزمة Example
من خلال العودة إلى جذر بيئة حزمة Example
وإعادة تفعيل بيئة Example
:
shell> cd ..
pkg> activate .
من هناك، يمكننا أخيرًا تشغيل مجموعة الاختبارات الخاصة بنا على النحو التالي:
(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
وإذا سارت الأمور بشكل صحيح، يجب أن ترى مخرجات مشابهة لما سبق. باستخدام Test.jl
، يمكن إضافة اختبارات أكثر تعقيدًا للحزم، ولكن يجب أن توجه هذه الخطوات المطورين في الاتجاه الصحيح لكيفية البدء في اختبار الحزم التي أنشأوها بأنفسهم.
Code Coverage
يمكن تمكين تتبع تغطية الشيفرة أثناء الاختبارات باستخدام علامة pkg> test --coverage
(أو على مستوى أدنى باستخدام --code-coverage
كوسيلة إدخال لجوليا). هذا مفعل بشكل افتراضي في julia-runtest إجراء GitHub.
لتقييم التغطية، يمكنك إما فحص ملفات .cov
التي تم إنشاؤها بجانب ملفات المصدر محليًا، أو في CI استخدم GitHub action julia-processcoverage.
منذ Julia 1.11، لا يتم جمع التغطية خلال مرحلة ما قبل تجميع الحزمة.