Testy jednostkowe za pomocą testthat
Pisz bloki test_that(), używaj asercji i uruchamiaj testy za pomocą devtools::test().
Testy jednostkowe za pomocą testthat to bezpłatna lekcja R Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej R Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs R Academy zawiera 4 lekcji w sumie.
Dlaczego testy jednostkowe?
Testy jednostkowe automatycznie sprawdzają, czy poszczególne funkcje działają prawidłowo. Wykrywają regresje po zmianie kodu, pełnią funkcję wykonywalnej dokumentacji i dają pewność, że można bezpiecznie refaktoryzować kod. Pakiet testthat to standardowy framework do testowania pakietów R.
Konfiguracja testthat
usethis::use_testthat() dodaje testthat do sekcji Suggests w pliku DESCRIPTION, tworzy katalog tests/testthat/ oraz skrypt uruchamiający tests/testthat.R. Należy uruchomić to polecenie raz podczas inicjalizowania nowego pakietu.
# 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: 3Tworzenie pliku testów
usethis::use_test('add') tworzy plik tests/testthat/test-add.R. Zgodnie z konwencją pliki testów noszą nazwy test-{function_name}.R. Każdy plik grupuje testy jednej funkcji lub funkcjonalności.
# 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)
# })Struktura test_that()
test_that('description', { ... }) grupuje powiązane oczekiwania. Ciąg znaków z opisem powinien uzupełniać zdanie „test that ...” i być na tyle szczegółowy, aby komunikaty o błędach były użyteczne.
# 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() i expect_identical()
expect_equal(actual, expected) wykonuje test z tolerancją numeryczną dla liczb zmiennoprzecinkowych. expect_identical(actual, expected) wymaga dokładnej równości, także pod względem typu. W większości przypadków lepiej użyć 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() i expect_warning()
Należy sprawdzać, czy funkcje generują właściwe błędy i ostrzeżenia. Do dopasowania komunikatu błędu należy przekazać wzorzec wyrażenia regularnego — dzięki temu można upewnić się, że zgłaszany jest właściwy błąd, a nie dowolny błąd.
# 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() i expect_false()
expect_true(expr) i expect_false(expr) testują warunki logiczne. Należy ich używać podczas testowania predykatów lub warunków zwracających pojedynczą wartość logiczną.
# 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)
# })Więcej funkcji oczekiwań
testthat udostępnia wiele funkcji oczekiwań przeznaczonych do różnych scenariuszy:
expect_length(x, n)— sprawdza długość wektoraexpect_type(x, 'double')— sprawdza typ bazowyexpect_s3_class(x, 'data.frame')— sprawdza klasę S3expect_null(x)— sprawdza, czy wartość to NULLexpect_match(string, regexp)— sprawdza wzorzec w ciągu znaków
# 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')
# })Uruchamianie testów za pomocą devtools::test()
devtools::test() (Ctrl+Shift+T) uruchamia wszystkie pliki testów i wyświetla podsumowanie zaliczonych testów, niepowodzeń oraz ostrzeżeń. Przy każdym nieudanym teście wyświetlane jest niespełnione oczekiwanie oraz wartości rzeczywista i oczekiwana.
# 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 ]Pokrycie testami za pomocą covr
covr::package_coverage() mierzy, jaki odsetek wierszy kodu pakietu jest wykonywany przez testy. covr::report() otwiera raport HTML pokazujący wiersze objęte testami (na zielono) i nieobjęte testami (na czerwono). Należy dążyć do pokrycia wynoszącego co najmniej 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 linesTestowanie przypadków brzegowych
Dobre testy obejmują nie tylko typowy przebieg, lecz także przypadki brzegowe:
- Puste dane wejściowe:
numeric(0),character(0) - Dane wejściowe NA: czy funkcja przekazuje dalej wartości NA, czy je obsługuje?
- Dane wejściowe o długości 1 i o długości n
- Wartości graniczne: 0, liczby ujemne, bardzo duże wartości
- Nieprawidłowe typy: co się stanie, gdy użytkownik przekaże ciąg znaków do funkcji numerycznej?
# 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)
# })Szybkie sprawdzenie: expect_error()
Wywołują Państwo expect_error(my_fn('bad'), regexp = 'invalid input'). Co sprawdza ten test?
Powtórzenie: testy jednostkowe
Testowanie pakietu R za pomocą testthat:
usethis::use_testthat()— jednorazowe skonfigurowanie infrastruktury testowejusethis::use_test('fn')— utworzenie plikutests/testthat/test-fn.Rtest_that('description', {...})— grupowanie powiązanych oczekiwańexpect_equal(),expect_error(),expect_warning(),expect_true(),expect_false()— podstawowe funkcje oczekiwańdevtools::test()— uruchomienie wszystkich testów (Ctrl+Shift+T)covr::package_coverage()— pomiar pokrycia testami
Często zadawane pytania
Czy lekcja „Testy jednostkowe za pomocą testthat” jest bezpłatna?
Tak — pełny tekst „Testy jednostkowe za pomocą testthat” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu R Academy, przejdź na CoddyKit PRO. Kurs R Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Testy jednostkowe za pomocą testthat”?
Pisz bloki test_that(), używaj asercji i uruchamiaj testy za pomocą devtools::test(). Ćwiczysz R Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć R Academy?
Nie wymagamy żadnego doświadczenia. R Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Testy jednostkowe za pomocą testthat”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji R Academy?
Tak. Każda lekcja R Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Struktura pakietu za pomocą usethis i devtools
- Dokumentowanie funkcji za pomocą roxygen2
- Testy jednostkowe za pomocą testthat
- Przesyłanie pakietu do CRAN i jego utrzymanie