FoRt: Paketerstellung mit devtools & testthat

FoRt: Paketerstellung mit devtools & testthat
Fabian Scheipl Institut für Statistik LMU München
Entwicklungsstil
Übliches Vorgehen
Entwicklung eines einzelnen Skripts:
• herumprobieren in der Kommandozeile
• modifiziere bis Rechner gehorcht, dann kopiere Befehle in ein File
ODER
• Arbeit mit mehreren Files
• source() auf alles & Output begutachten
• modifizieren & wiederholen
Beides ist chaotisch & fehlerträchtig wenn man saubere, reproduzierbare Analysen oder eben Pakete entwickeln
möchte.
Entwicklungsstile
• konfirmatorisches Programmieren: nur das “wie” ist unklar
– Programmiererin weiß genau was sie tun muss & welches Ergebnis die vorzunehmenden Änderungen
haben sollen
– test-driven development
• exploratives Programmieren:
– lose Idee was am Ende rauskommen soll
– nicht sicher über Dinge wie die richtige Kapselung der Sub-Funktionen, Details der inputs/outputs,
etc.
• möglichst konfirmatorisch arbeiten wenn man etwas neues von Grund auf entwickelt (top-down programming!)
• Debugging und Feature-Erweiterung zu Beginn oft eher explorativ
devtools
Paketentwicklung ohne devtools
Kommandozeilen-Befehle für ein Quellcode-Paket im Ordner pkg:
• R CMD SHLIB pkg kompiliert C/C++/Fortran-Code in pkg/src
• R CMD check pkg überprüft ob pkg ein valides R-Paket ist
1
• R CMD build pkg erzeugt package bundle pkg.tar.gz bzw. pkg.zip
• mit devtools direkt aus R heraus aufrufbar.
• mit devtools & RStudio per Button / Tastenkombi.
Unter Windows: benötigt zusätzlich Installation von Rtools.
Wichtige devtools Befehle
•
•
•
•
dev_mode(): schalte in developer mode (spezielle .libPaths())
load_all("pkg"): simuliert library("pkg")
test("pkg"): lässt Tests in inst/tests/ laufen und gibt Ergebnisse aus
document("pkg"): lässt roxygen2::roxygenize über das Paket laufen um Hifeseiten, NAMESPACE, etc.
zu aktualisieren
Wichtige devtools Befehle
Sobald alles zu funktionieren scheint:
• check_doc("pkg") um erzeugte Hilfeseiten zu validieren
• run_examples("pkg") um Beispiele zu validieren
• check("pkg") um vollständiges R CMD check durchzuführen, mit den selben Optionen wie auf CRAN
(aber nur auf einer R-Version!)
• build("pkg") und build_win("pkg") um bundles und Windows binaries zu erstellen.
Paket-Erstellung in RStudio
• Neues Projekt als “Neues R-Paket” anlegen
• Stellt alle diese Funktionen als Buttons/Shortcuts zur Verfügung
Exploratives Programmieren
Workflow:
1.
2.
3.
4.
5.
6.
ändere Code und lade neu mit load_all()
begutachte Effekt der Änderungen
wiederhole 1. und 2. bis Ziel erreicht
(aktualisiere Doku, generiere Hilfe mit document())
check("pkg")
build("pkg")
In RStudio einfach über Buttons/Shortcuts
2
Konfirmatorisches Programmieren
Workflow:
1. schreibe Test für Bug oder zu implementierendes Feature. Stelle sicher das der Test versagt.
2. ändere Code und lade neu mit load_all()
3. lasse alle Tests die sich für das Paket angesammelt haben laufen (mit test() falls testthat benutzt
wird, sonst per source() der Testscripts.)
4. wiederhole 2. und 3. bis kein Test mehr versagt
5. (aktualisiere Doku, generiere Hilfe mit document())
6. check("pkg")
7. build("pkg")
In RStudio einfach über Buttons/Shortcuts.
Konfirmatorisches Programmieren
• weniger Frust, weniger Bugs
• bessere Codestruktur:
– leicht zu testen ⇔ gutes Design (Kapselung!)
– defensives Programmieren: Tests schreiben heißt Fehlerquellen antizipieren
• leichter Wiedereinstieg nach Pause
– versagende Tests als to do Liste
• mehr Selbstvertrauen um große Änderungen zu wagen:
– Verschlimmbesserungen sofort sichtbar falls Testabdeckung gut genug ist
testthat
testthat für konfirmatorisches Programmieren
hierarchische Struktur:
• expectation() beschreibt das erwartete Ergebnis eines Befehls
– Wert, Klasse, korrekte Fehlermeldung, Rechenzeit, . . . ?
– definiert mit expect_that()
• test_that() gruppiert mehrere expectations für eine Funktion oder ein Feature
• context() gruppiert mehrere thematisch verwandte Tests
expectation
• überprüft ob ein Wert einer Erwartung entspricht. Wenn nicht, dann error
• Syntax z.B. expect_that(a, equals(b))
3
Expectations
Vollständig
Abkrzng
expect_that(x, is_true())
expect_true(x)
expect_that(x, is_false())
expect_false(x)
expect_that(x, is_a(y))
expect_is(x, y)
expect_that(x, equals(y))
expect_equal(x, y)
expect_that(x, is_equivalent_to(y))
expect_equivalent(x, y)
expect_that(x, is_identical_to(y))
expect_identical(x, y)
expect_that(x, throws_error(y))
expect_error(x, y)
expect_that(x, gives_warning(y))
expect_warning(x, y)
expect_that(x, matches(y))
expect_match(x, y)
expect_that(x, prints_text(y))
expect_output(x, y)
expect_that(x, shows_message(y))
expect_message(x, y)
expect_that(x, takes_less_than(time))
Tests
• Fokus auf eine einzige, wohl-definierte Teilfunktionalität
• informative Namen vergeben die den Satz “Test that . . . ” vervollständigen.
• jeder Test läuft in seiner eigenen Umgebung, muss also in sich abgeschlossen sein
library(testthat)
test_that("trigonometric functions match identities.", {
expect_that(sin(pi / 4), equals(1 / sqrt(2)))
expect_that(cos(pi / 4), equals(1 / sqrt(2)))
expect_that(tan(pi / 4), equals(1))
})
4
Tests die versagen produzieren aussagekräftige Fehler:
test_that("this will end badly.", {
expect_equal(cos(0), 0)
})
Error: Test failed: 'this will end badly.'
Not expected: cos(0) not equal to 0
Mean absolute difference: 1.
context
. . . gruppiert mehrere thematisch verwandte Tests.
library(testthat)
context("Accuracy for trig functions:")
test_that("trigonometric functions match identities", {
expect_that(sin(pi / 4), equals(1 / sqrt(2)))
expect_that(cos(pi / 4), equals(1 / sqrt(2)))
expect_that(tan(pi / 4), equals(1))
})
test_that("trigonometric functions remain accurate
for large arguments", {
expect_that(sin(2e7 * pi + pi / 4), equals(1 / sqrt(2)))
expect_that(cos(2e7 * pi + pi / 4), equals(1 / sqrt(2)))
expect_that(tan(2e7 * pi + pi / 4), equals(1))
})
Tests testen
Ausserhalb eines Pakets:
• testthat::test_file() oder testthat::test_dir() um alle Tests in einer Datei / einem Ordner
laufen zu lassen. Namen von Test-Skripten fangen mit “test-” an.
Innerhalb eines Pakets:
•
•
•
•
testthat::test_package(<pkgname>), testthat::test_check()
devtools::test()
R CMD check, devtools::check()
R Studio Button / Shortcut Ctrl-Shift-T
R CMD check
• alle Testfiles in Unterordner tests/testthat
• Namen von Test-Files fangen mit “test-” an.
• Datei tests/test-all.R mit
5
library(testthat)
test_check("pkgname")
• anlegen mit devtools::use_testthat()
• DESCRIPTION muss Suggests: testthat enthalten
• R CMD check versagt wenn nicht alle Tests erfolgreich verlaufen
6