0Pricing
R Academy · Урок

Модульное тестирование с 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') — проверить класс S3
  • expect_null(x) — проверить значение NULL
  • expect_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.R
  • test_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 — локальная установка не требуется.

Все уроки этого курса

  1. Структура пакета с usethis и devtools
  2. Документирование функций с roxygen2
  3. Модульное тестирование с testthat
  4. Публикация в CRAN и сопровождение пакета
← Назад к R Academy