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أو يسبب استثناء. يرجعBrokenResultإذا كان كذلك، أوErrorResultإذا كان التعبير يقيم إلى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: MethodErrorWorking 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 جميع النتائج، وإذا كان هناك أي Fails أو Errors، يتم طرح استثناء في نهاية مجموعة الاختبارات الرئيسية (غير المتداخلة)، مع ملخص لنتائج الاختبار.
يمكن إعطاء أي نوع مجموعة اختبارات مخصص (فرعي من 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. يهدف هذا إلى تسهيل إعادة الإنتاج في حالة الفشل، والسماح بإعادة ترتيب سلس لـ @testsets بغض النظر عن تأثيرها على حالة مولد الأرقام العشوائية العالمي.
أمثلة
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). سيتم مطابقة العناصر الموجودة بشكل زوجي مع حقول سجل السجل باستخدام == بشكل افتراضي، مع الحالات الخاصة التي يمكن استخدام Symbols لمستويات السجل القياسية، وRegexs في النمط ستتطابق مع حقول السلسلة أو الرمز باستخدام 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 PassedTest.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
endTest يتحمل مسؤولية الحفاظ على مكدس من مجموعات الاختبار المتداخلة أثناء تنفيذها، ولكن أي تراكم للنتائج هو مسؤولية نوع 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)
endTest.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)إرجاع متجه من Methods التي قد تحتوي على معلمات نوع غير مرتبطة. استخدم recursive=true للاختبار في جميع الوحدات الفرعية.
بشكل افتراضي، أي رموز غير معرفة تؤدي إلى تحذير. يمكن كتم هذا التحذير من خلال تقديم مجموعة من GlobalRefs التي يمكن تخطي التحذير بشأنها. على سبيل المثال، تعيين
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
endCreating a Test Environment
من داخل جذر حزمة Example، انتقل إلى دليل test، قم بتنشيط بيئة جديدة هناك، وأضف حزمة Test إلى البيئة:
shell> cd test
pkg> activate .
(test) pkg> add TestTesting 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)
endWriting Tests for greeting_tests.jl
باستخدام معرفتنا بـ Test.jl، إليك بعض الاختبارات النموذجية التي يمكننا إضافتها إلى greeting_tests.jl:
@testset "Testset 3" begin
@test "Hello world!" == greet()
@test_throws MethodError greet("Antonia")
endTesting 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، لا يتم جمع التغطية خلال مرحلة ما قبل تجميع الحزمة.