اختبار الوحدات باستخدام testthat
اكتب كتل test_that()، واستخدم التوقعات، وشغّل الاختبارات باستخدام devtools::test()
اختبار الوحدات باستخدام testthat درس مجاني في R Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في R Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة R Academy 4 دروس في المجموع.
لماذا نستخدم اختبارات الوحدات؟
تتحقق اختبارات الوحدات تلقائيًا من أن الدوال الفردية تعمل بشكل صحيح. فهي تكشف التراجعات عند تغيير الشيفرة، وتعمل بوصفها توثيقًا قابلًا للتنفيذ، وتمنحك الثقة لإعادة هيكلة الشيفرة بأمان. وتُعد حزمة testthat إطار الاختبار القياسي لحزم R.
إعداد testthat
يضيف usethis::use_testthat() حزمة testthat إلى Suggests في DESCRIPTION، وينشئ tests/testthat/، وينشئ أيضًا شيفرة التشغيل tests/testthat.R. شغّله مرة واحدة عند تهيئة حزمة جديدة.
# usethis::use_testthat()
#
# Creates:
# tests/
# testthat.R <- runner (do not edit)
# testthat/
# (empty — write test files here)
#
# Updates DESCRIPTION:
# Suggests: testthat (>= 3.0.0)
# Config/testthat/edition: 3إنشاء ملف اختبار
ينشئ usethis::use_test('add') الملف tests/testthat/test-add.R. ووفقًا للاصطلاح، تُسمى ملفات الاختبار test-{function_name}.R. يجمع كل ملف اختبارات دالة أو ميزة واحدة.
# usethis::use_test('add') # creates tests/testthat/test-add.R
#
# Content of test-add.R:
# test_that('add() returns correct sum', {
# expect_equal(add(1, 2), 3)
# expect_equal(add(-1, 1), 0)
# expect_equal(add(0.1, 0.2), 0.3, tolerance = 1e-7)
# })بنية test_that()
يجمع test_that('description', { ... }) التوقعات المترابطة. ينبغي أن تُكمل سلسلة الوصف الجملة «test that ...»، وأن تكون محددة بما يكفي لتكون مفيدة في رسائل الفشل.
# Good test_that descriptions:
# test_that('add() handles negative numbers', { ... })
# test_that('add() recycles length-1 vectors', { ... })
# test_that('add() returns NA when input contains NA', { ... })
#
# Bad (too vague):
# test_that('it works', { ... })
# test_that('test1', { ... })expect_equal() وexpect_identical()
يختبر expect_equal(actual, expected) مع سماحية عددية للأعداد ذات الفاصلة العائمة. ويتطلب expect_identical(actual, expected) تطابقًا تامًا، بما في ذلك النوع. في معظم الحالات، يُفضّل استخدام expect_equal().
# test_that('add() adds correctly', {
# expect_equal(add(1, 2), 3) # numeric equality
# expect_equal(add(0.1, 0.2), 0.3) # tolerance handles floating point
# expect_identical(add(1L, 2L), 3L) # exact type match: integer
# expect_identical(add(1.0, 2.0), 3.0) # exact type match: double
# })expect_error() وexpect_warning()
اختبر أن الدوال تنتج الأخطاء والتحذيرات الصحيحة. مرّر نمط regex لمطابقة رسالة الخطأ — فهذا يضمن طرح الخطأ الصحيح، وليس أي خطأ عشوائي.
# test_that('add() validates input types', {
# expect_error(
# add('a', 2),
# regexp = 'numeric' # message must contain 'numeric'
# )
# expect_error(
# add(NULL, 1),
# regexp = 'numeric'
# )
# })
#
# test_that('sqrt() warns on negative input', {
# expect_warning(sqrt(-1))
# })expect_true() وexpect_false()
تختبر expect_true(expr) وexpect_false(expr) الشروط المنطقية. استخدمهما عند اختبار الدوال أو الشروط التي تعيد قيمة منطقية واحدة.
# test_that('is_positive() returns correct logical', {
# expect_true(is_positive(5))
# expect_true(is_positive(0.001))
# expect_false(is_positive(0))
# expect_false(is_positive(-3))
# })
#
# # Also useful for vector tests:
# test_that('add() result has correct length', {
# result <- add(c(1,2,3), c(4,5,6))
# expect_true(length(result) == 3)
# })دوال توقع إضافية
توفر testthat العديد من دوال التوقع لسيناريوهات مختلفة:
expect_length(x, n)— التحقق من طول المتجهexpect_type(x, 'double')— التحقق من النوع الأساسيexpect_s3_class(x, 'data.frame')— التحقق من صنف S3expect_null(x)— التحقق من كون القيمة NULLexpect_match(string, regexp)— التحقق من نمط السلسلة
# test_that('add() output has correct type and length', {
# result <- add(c(1.0, 2.0), c(3.0, 4.0))
# expect_type(result, 'double')
# expect_length(result, 2)
# })
#
# test_that('summary_stats() returns a data frame', {
# result <- summary_stats(rnorm(100))
# expect_s3_class(result, 'data.frame')
# })تشغيل الاختبارات باستخدام devtools::test()
يشغّل devtools::test() (Ctrl+Shift+T) جميع ملفات الاختبار ويعرض ملخصًا للاختبارات الناجحة والفاشلة والتحذيرات. وتعرض حالات فشل الاختبار الفردية التوقع الذي فشل والقيم الفعلية مقابل المتوقعة.
# devtools::test()
#
# Example output:
# == Testing mypackage ====================================
# v | OK F W S | Context
# v | 3 | add [0.1s]
# v | 4 | subtract [0.1s]
# x | 2 1 | multiply [0.2s]
# -- Failure (test-multiply.R:5): multiply() handles zero
# multiply(5, 0) not equal to 0.
# Actual: 5
# Expected: 0
# ==========================================================
# [ FAIL 1 | WARN 0 | SKIP 0 | PASS 9 ]تغطية الاختبار باستخدام covr
تقيس covr::package_coverage() النسبة المئوية من أسطر حُزمتك التي تنفذها الاختبارات. ويفتح covr::report() تقرير HTML يعرض الأسطر التي غطتها الاختبارات (بالأخضر) والأسطر التي لم تغطها (بالأحمر). استهدف تغطية لا تقل عن 80٪.
# library(covr)
# cov <- package_coverage()
# print(cov)
#
# Example output:
# mypackage Coverage: 87.50%
# R/add.R: 100.00%
# R/subtract.R: 100.00%
# R/utils.R: 62.50% <- needs more tests!
#
# covr::report() # interactive HTML report
# covr::zero_coverage(cov) # list uncovered linesاختبار الحالات الحدية
تغطي الاختبارات الجيدة المسار المعتاد والحالات الحدية أيضًا:
- المدخلات الفارغة:
numeric(0)وcharacter(0) - مدخلات NA: هل تنقل الدالة NA أم تعالجها؟
- مدخلات بطول 1 مقابل مدخلات بطول n
- القيم الحدية: 0 والأعداد السالبة والقيم الكبيرة جدًا
- الأنواع غير الصحيحة: ماذا يحدث عندما يمرر المستخدم سلسلة إلى دالة عددية؟
# test_that('add() handles edge cases', {
# expect_equal(add(numeric(0), numeric(0)), numeric(0)) # empty
# expect_true(is.na(add(NA, 1))) # NA propagation
# expect_equal(add(1, c(1,2,3)), c(2,3,4)) # recycling
# expect_equal(add(.Machine$integer.max, 0L), # boundary
# .Machine$integer.max)
# })تحقق سريع: expect_error()
تستدعي expect_error(my_fn('bad'), regexp = 'invalid input'). ما الذي يتحقق منه هذا الاختبار؟
مراجعة اختبار الوحدات
اختبار حزمة R باستخدام testthat:
usethis::use_testthat()— إعداد البنية التحتية للاختبارات مرة واحدةusethis::use_test('fn')— إنشاءtests/testthat/test-fn.Rtest_that('description', {...})— تجميع التوقعات ذات الصلةexpect_equal()وexpect_error()وexpect_warning()وexpect_true()وexpect_false()— دوال التوقع الأساسيةdevtools::test()— تشغيل جميع الاختبارات (Ctrl+Shift+T)covr::package_coverage()— قياس تغطية الاختبارات
الأسئلة الشائعة
هل درس «اختبار الوحدات باستخدام testthat» مجاني؟
نعم — نص درس «اختبار الوحدات باستخدام testthat» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة R Academy، انتقل إلى CoddyKit PRO. تتضمن دورة R Academy 4 دروس في المجموع.
ماذا ستتعلم في «اختبار الوحدات باستخدام testthat»؟
اكتب كتل test_that()، واستخدم التوقعات، وشغّل الاختبارات باستخدام devtools::test() تتمرن على R Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ R Academy؟
لا تُشترط خبرة سابقة. R Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «اختبار الوحدات باستخدام testthat»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس R Academy هذا؟
نعم. كل درس في R Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- بنية الحزم باستخدام usethis وdevtools
- توثيق الدوال باستخدام roxygen2
- اختبار الوحدات باستخدام testthat
- إرسال الحزم إلى CRAN وصيانتها