Unit Testing

Testing Base Julia

جوليا تحت تطوير سريع ولديها مجموعة اختبارات شاملة للتحقق من الوظائف عبر منصات متعددة. إذا قمت ببناء جوليا من المصدر، يمكنك تشغيل مجموعة الاختبارات هذه باستخدام make test. في التثبيت الثنائي، يمكنك تشغيل مجموعة الاختبارات باستخدام Base.runtests().

Base.runtestsFunction
Base.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 أو المكتبات القياسية قبل تشغيل الاختبارات. إذا تم توفير بذور عبر وسيط الكلمة الرئيسية، يتم استخدامها لتوليد الأرقام العشوائية العالمية في السياق الذي يتم فيه تشغيل الاختبارات؛ وإلا يتم اختيار البذور عشوائيًا.

source

Basic Unit Tests

وحدة Test توفر وظائف بسيطة لاختبار الوحدات. اختبار الوحدات هو وسيلة لرؤية ما إذا كان الكود الخاص بك صحيحًا من خلال التحقق من أن النتائج هي ما تتوقعه. يمكن أن يكون مفيدًا لضمان أن الكود الخاص بك لا يزال يعمل بعد إجراء تغييرات، ويمكن استخدامه عند التطوير كوسيلة لتحديد السلوكيات التي يجب أن يمتلكها الكود الخاص بك عند الانتهاء. قد ترغب أيضًا في الاطلاع على الوثائق لـ adding tests to your Julia Package.

يمكن إجراء اختبار وحدة بسيط باستخدام الماكرو @test و @test_throws:

Test.@testMacro
@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
Julia 1.7

تتطلب وسائط الكلمات الرئيسية broken و skip على الأقل Julia 1.7.

source
Test.@test_throwsMacro
@test_throws exception expr

يختبر أن التعبير expr يرمي exception. قد يحدد الاستثناء إما نوعًا، أو سلسلة، أو تعبيرًا عاديًا، أو قائمة من السلاسل التي تحدث في رسالة الخطأ المعروضة، أو دالة مطابقة، أو قيمة (سيتم اختبارها من أجل المساواة من خلال مقارنة الحقول). لاحظ أن @test_throws لا يدعم شكل الكلمة الرئيسية المتأخرة.

Julia 1.8

تتطلب القدرة على تحديد أي شيء بخلاف نوع أو قيمة كـ 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) (دالة مطابقة)
source

على سبيل المثال، لنفترض أننا نريد التحقق من أن دالتنا الجديدة 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

عادةً ما يتم استخدام عدد كبير من الاختبارات للتأكد من أن الوظائف تعمل بشكل صحيح على مجموعة من المدخلات. في حالة فشل اختبار، فإن السلوك الافتراضي هو رمي استثناء على الفور. ومع ذلك، من المفضل عادةً تشغيل بقية الاختبارات أولاً للحصول على صورة أفضل عن عدد الأخطاء الموجودة في الكود الذي يتم اختباره.

Note

سيقوم @testset بإنشاء نطاق محلي خاص به عند تشغيل الاختبارات فيه.

يمكن استخدام ماكرو @testset لتجميع الاختبارات في مجموعات. سيتم تشغيل جميع الاختبارات في مجموعة الاختبار، وفي نهاية مجموعة الاختبار سيتم طباعة ملخص. إذا فشل أي من الاختبارات، أو لم يكن بالإمكان تقييمه بسبب خطأ، فستقوم مجموعة الاختبار بعد ذلك بإلقاء استثناء TestSetException.

Test.@testsetMacro
@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.
جوليا 1.8

@testset test_func() يتطلب على الأقل جوليا 1.8.

جوليا 1.9

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، يبدأ الماكرو مجموعة اختبارات شفافة مع الكائن المعطى المضاف ككائن سياق لأي اختبار فاشل يحتوي عليه. هذا مفيد عند إجراء مجموعة من الاختبارات ذات الصلة على كائن أكبر واحد ومن المرغوب فيه طباعة هذا الكائن الأكبر عند فشل أي من الاختبارات الفردية. لا تقدم مجموعات الاختبارات الشفافة مستويات إضافية من التعشيش في تسلسل مجموعات الاختبارات ويتم تمريرها مباشرة إلى مجموعة الاختبارات الأصلية (مع إضافة كائن السياق إلى أي اختبارات فاشلة).

جوليا 1.9

@testset let يتطلب على الأقل جوليا 1.9.

جوليا 1.10

تم دعم تعيينات 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

خطأ: حدث خطأ أثناء الاختبار
source
Test.TestSetExceptionType
TestSetException

يتم رميها عندما تنتهي مجموعة الاختبار وليس كل الاختبارات قد نجحت.

source

يمكننا وضع اختباراتنا لدالة 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_logsMacro
@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.

source
Test.TestLoggerType
TestLogger(; 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
source
Test.LogRecordType
LogRecord

يخزن نتائج حدث سجل واحد. الحقول:

  • level: LogLevel لرسالة السجل
  • message: المحتوى النصي لرسالة السجل
  • _module: وحدة حدث السجل
  • group: مجموعة السجل (بشكل افتراضي، اسم الملف الذي يحتوي على حدث السجل)
  • id: معرف حدث السجل
  • file: الملف الذي يحتوي على حدث السجل
  • line: السطر داخل الملف لحدث السجل
  • kwargs: أي معلمات مفتاحية تم تمريرها إلى حدث السجل
source

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.@inferredMacro
@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}
[...]
source
Test.@test_deprecatedMacro
@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"
source
Test.@test_warnMacro
@test_warn msg expr

اختبر ما إذا كانت تقييم expr ينتج عنه مخرجات stderr تحتوي على سلسلة msg أو تتطابق مع التعبير العادي msg. إذا كانت msg دالة منطقية، يتم اختبار ما إذا كانت msg(output) تعيد true. إذا كانت msg عبارة عن مجموعة أو مصفوفة، يتم التحقق من أن مخرجات الخطأ تحتوي/تتطابق مع كل عنصر في msg. تعيد نتيجة تقييم expr.

انظر أيضًا @test_nowarn للتحقق من عدم وجود مخرجات خطأ.

ملاحظة: لا يمكن اختبار التحذيرات الناتجة عن @warn باستخدام هذه الماكرو. استخدم @test_logs بدلاً من ذلك.

source
Test.@test_nowarnMacro
@test_nowarn expr

اختبر ما إذا كانت تقييم expr ينتج عنه إخراج فارغ stderr (لا تحذيرات أو رسائل أخرى). يُرجع نتيجة تقييم expr.

ملاحظة: لا يمكن اختبار غياب التحذيرات الناتجة عن @warn باستخدام هذه الماكرو. استخدم @test_logs بدلاً من ذلك.

source

Broken Tests

إذا فشل الاختبار باستمرار، يمكن تغييره لاستخدام ماكرو @test_broken. سيشير هذا إلى أن الاختبار معطل إذا استمر الاختبار في الفشل وينبه المستخدم عبر خطأ إذا نجح الاختبار.

Test.@test_brokenMacro
@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)
source

@test_skip متاح أيضًا لتخطي اختبار دون تقييم، ولكن مع احتساب الاختبار المتخطي في تقارير مجموعة الاختبارات. لن يتم تشغيل الاختبار ولكنه يعطي نتيجة مكسورة.

Test.@test_skipMacro
@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)
source

Test result types

Test.ResultType
Test.Result

تنتج جميع الاختبارات كائن نتيجة. قد يتم تخزين هذا الكائن أو لا، اعتمادًا على ما إذا كانت الاختبار جزءًا من مجموعة اختبارات.

source
Test.PassType
Test.Pass <: Test.Result

كانت حالة الاختبار صحيحة، أي أن التعبير تم تقييمه إلى صحيح أو تم طرح الاستثناء الصحيح.

source
Test.FailType
Test.Fail <: Test.Result

كانت حالة الاختبار خاطئة، أي أن التعبير تم تقييمه إلى خطأ أو لم يتم رمي الاستثناء الصحيح.

source
Test.ErrorType
Test.Error <: Test.Result

لم يكن من الممكن تقييم شرط الاختبار بسبب استثناء، أو تم تقييمه إلى شيء آخر غير Bool. في حالة @test_broken، يتم استخدامه للإشارة إلى حدوث Pass Result غير متوقع.

source
Test.BrokenType
Test.Broken <: Test.Result

شرط الاختبار هو النتيجة المتوقعة (الفاشلة) لاختبار معطل، أو تم تخطيه صراحة باستخدام @test_skip.

source

Creating Custom AbstractTestSet Types

يمكن للحزم إنشاء أنواع فرعية خاصة بها من AbstractTestSet عن طريق تنفيذ طرق record و finish. يجب أن تحتوي النوع الفرعي على مُنشئ ذو وسيط واحد يأخذ سلسلة وصف، مع تمرير أي خيارات كوسائط مفتاحية.

Test.recordFunction
record(ts::AbstractTestSet, res::Result)

سجل نتيجة لمجموعة الاختبار. يتم استدعاء هذه الدالة بواسطة بنية @testset في كل مرة تكتمل فيها ماكرو @test المحتوى، ويتم إعطاؤها نتيجة الاختبار (التي يمكن أن تكون Error). سيتم استدعاء هذا أيضًا مع Error إذا تم طرح استثناء داخل كتلة الاختبار ولكن خارج سياق @test.

source
Test.finishFunction
finish(ts::AbstractTestSet)

قم بأي معالجة نهائية ضرورية لمجموعة الاختبار المعطاة. يتم استدعاء هذا بواسطة بنية @testset بعد تنفيذ كتلة الاختبار.

يجب على الأنواع الفرعية المخصصة لـ AbstractTestSet استدعاء record على الوالد (إذا كان موجودًا) لإضافة نفسها إلى شجرة نتائج الاختبار. يمكن تنفيذ ذلك كما يلي:

if get_testset_depth() != 0
    # ربط مجموعة الاختبار هذه بمجموعة الاختبار الوالد
    parent_ts = get_testset()
    record(parent_ts, self)
    return self
end
source

Test يتحمل مسؤولية الحفاظ على مكدس من مجموعات الاختبار المتداخلة أثناء تنفيذها، ولكن أي تراكم للنتائج هو مسؤولية نوع AbstractTestSet. يمكنك الوصول إلى هذا المكدس باستخدام طرق get_testset و get_testset_depth. لاحظ أن هذه الوظائف غير مصدرة.

Test.get_testsetFunction
get_testset()

استرجع مجموعة الاختبار النشطة من التخزين المحلي للمهمة. إذا لم تكن هناك مجموعة اختبار نشطة، استخدم مجموعة الاختبار الافتراضية الاحتياطية.

source
Test.get_testset_depthFunction
get_testset_depth()

إرجاع عدد مجموعات الاختبار النشطة، دون احتساب مجموعة الاختبار الافتراضية

source

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.TestCountsType
TestCounts

يمسك الحالة لجمع نتائج مجموعة الاختبار بشكل متكرر لأغراض العرض.

الحقول:

  • 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 منسق.
source
Test.get_test_countsFunction

" gettestcounts(::AbstractTestSet) -> TestCounts

دالة تكرارية تحسب عدد نتائج الاختبار من كل نوع مباشرة في مجموعة الاختبار، وتجمعها عبر مجموعات الاختبار الفرعية.

يجب على AbstractTestSet المخصصة تنفيذ هذه الدالة للحصول على إجمالياتها محسوبة ومعروضة مع DefaultTestSet أيضًا.

إذا لم يتم تنفيذ ذلك لمجموعة TestSet مخصصة، فإن الطباعة تعود للإبلاغ عن x للفشل و ?s للمدة.

source
Test.format_durationFunction
format_duration(::AbstractTestSet)

إرجاع سلسلة منسقة لطباعة المدة التي استغرقها مجموعة الاختبار.

إذا لم يتم تعريفها، يتم الرجوع إلى "?s".

source
Test.print_test_resultsFunction
print_test_results(ts::AbstractTestSet, depth_pad=0)

طباعة نتائج AbstractTestSet كجدول منسق.

يشير depth_pad إلى مقدار الحشو الذي يجب إضافته أمام جميع المخرجات.

يتم استدعاؤه داخل Test.finish، إذا كانت مجموعة الاختبار التي تم إنهاؤها هي مجموعة الاختبار العليا.

source

Test utilities

Test.GenericArrayType

يمكن استخدام GenericArray لاختبار واجهات برمجة التطبيقات للمصفوفات العامة التي تتبع واجهة AbstractArray، لضمان أن الوظائف يمكن أن تعمل مع أنواع المصفوفات بخلاف نوع Array القياسي.

source
Test.GenericDictType

يمكن استخدام GenericDict لاختبار واجهات برمجة التطبيقات الخاصة بالقواميس العامة التي تتبع واجهة AbstractDict، لضمان أن الوظائف يمكن أن تعمل مع الأنواع المرتبطة بالإضافة إلى نوع Dict القياسي.

source
Test.GenericOrderType

يمكن استخدام GenericOrder لاختبار واجهات برمجة التطبيقات لدعمها لأنواع مرتبة عامة.

source
Test.GenericSetType

يمكن استخدام GenericSet لاختبار واجهات برمجة التطبيقات الخاصة بالمجموعات العامة التي تتبع واجهة AbstractSet، لضمان أن الوظائف يمكن أن تعمل مع أنواع المجموعات بخلاف الأنواع القياسية Set و BitSet.

source
Test.GenericStringType

يمكن استخدام GenericString لاختبار واجهات برمجة التطبيقات الخاصة بالسلاسل العامة التي تتبع واجهة AbstractString، لضمان أن الوظائف يمكن أن تعمل مع أنواع السلاسل بخلاف نوع String القياسي.

source
Test.detect_ambiguitiesFunction
detect_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.

Julia 1.8

allowed_undefineds يتطلب على الأقل Julia 1.8.

source
Test.detect_unbound_argsFunction
detect_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.

Julia 1.8

allowed_undefineds يتطلب على الأقل Julia 1.8.

source

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

منذ Julia 1.11، لا يتم جمع التغطية خلال مرحلة ما قبل تجميع الحزمة.