Модульное тестирование с 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', { ... }) объединяет связанные проверки. Строка описания должна дополнять предложение «проверить, что ...» и быть достаточно конкретной, чтобы приносить пользу в сообщениях об ошибках.
# 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()
Проверяйте, что функции создают правильные ошибки и предупреждения. Передавайте шаблон регулярного выражения для сопоставления с сообщением об ошибке — это гарантирует, что возникла нужная ошибка, а не любая другая.
# 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 включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Структура пакета с usethis и devtools
- Документирование функций с roxygen2
- Модульное тестирование с testthat
- Публикация в CRAN и сопровождение пакета