lunes, 18 de marzo de 2024 From rOpenSci (https://ropensci.org/es/blog/2024/03/18/dry-damp/). Except where otherwise noted, content on this site is licensed under the CC-BY license.
rOpenSci ha incorporado la segunda cohorte de Campeones ¡! Su formación comenzó con una sesión sobre el estilo del código a la que siguieron tres sesiones sobre los fundamentos del desarrollo de paquetes R, y finalizó con una sesión sobre desarrollo avanzado de paquetes R que consistió en un popurrí de consejos más un debate, seguido de tiempo para aplicar estos principios a los paquetes de los participantes. Aquí quiero compartir uno de los temas tratados: La comprobación de paquetes y, en particular, los principios DRY Don´t Repeat Yourself (“no te repitas”) y DAMP Descriptive And Meaningful Phrases (“frases descriptivas y significativas”). Para este tema, utilizamos un repositorio de GitHub que contiene un paquete R cuyos diferentes commits ilustran los dos principios. En cada paso compartiremos un commit o diff que ilustre los cambios realizados.
Primer commit: Configurar nuestros archivos de prueba, trabajos-de-prueba.R y prueba-ok.R
Estos dos archivos de prueba iniciales definen un objeto llamado test_object
en el nivel superior, que se utiliza en dos pruebas de cada archivo.
prueba-trabajos.R
test_object <- list(a = 1, b = 2)
test_that("multiplication works", {
expect_equal(test_object[["b"]] * 2, 4)
})
test_that("addition works", {
expect_equal(test_object[["a"]] + 2, 3)
})
y
prueba-ok.R
test_object <- list(a = 1, b = 2)
test_that("division works", {
expect_equal(test_object[["b"]] / 2, 1)
})
test_that("substraction works", {
expect_equal(test_object[["a"]] - 1, 0)
})
No es un patrón óptimo porque no puedes mirar cada test_that()
ya que prueba de forma aislada y no comprende rápidamente lo que ocurre.
En un archivo de pruebas muy largo incluso tendrías que desplazarte hacia arriba y abajo! 😱
Además, estamos siendo repetitivos al definir el mismo objeto de prueba en dos archivos de prueba.
Siguiente compromiso: SECAR estos archivos
En esta fase nos acordamos rápidamente de DRY, Don’t Repeat Yourself (No te repitas), y de la mecánica de archivos de ayuda de testthat:
archivos cuyos nombres empiezan por helper-
se cargan antes de todas las pruebas.
Así que creamos un archivo de ayuda (helper-swamp.R
) dentro del cual test_object
esté definido, por lo tanto, ¡disponible para las pruebas!
En tests/testthat/helper-swamp.R
,
test_object <- list(a = 1, b = 2)
En los archivos de prueba, eliminamos la primera línea que definía test_object
.
Pero las cosas aún no son perfectas.
Cuando miramos cualquiera de los archivos de prueba, no podemos saber realmente qué test_object
ya que su nombre no es “descriptivo y significativo”.
Además, ahora tenemos test_object
que es siempre definida aunque no se utilice en una prueba.
En el mejor de los casos, esto es innecesario e inútil; en el peor, podría tener efectos secundarios no deseados, ¡sobre todo con código más complejo! 1
Tercer compromiso: Aplicar los principios DAMP
En el archivo de ayuda, tests/testthat/helper-swamp.R
refactorizamos el código en una función con un nombre más significativo (¡al menos hagamos como si lo fuera!).
basic_list <- function() {
list(a = 1, b = 2)
}
A continuación, usamos esta función para definir el objeto en todas las pruebas en las que sea necesario. Así, los archivos de prueba pasan a ser
test_that("division works", {
test_object <- basic_list()
expect_equal(test_object[["b"]] / 2, 1)
})
y
test_that("substraction works", {
test_object <- basic_list()
expect_equal(test_object[["a"]] - 1, 0)
})
Ahora, aunque la definición real de la lista básica no está en todas las pruebas, tenemos una mejor idea de lo que ocurre al leer la prueba.
Además, si la prueba fallara, en la consola podríamos ejecutar devtools::load_all()
y ejecutar el código de la prueba, como devtools::load_all()
carga los archivos de ayuda de la prueba así basic_list()
están disponibles.
El equilibrio entre DRY (“No te repitas”) y DAMP (“Frases descriptivas y significativas”) es un compromiso. Para mantener la analogía del agua, también tenemos que asegurarnos de que nuestro código no tenga efectos que puedan “filtrarse” inesperadamente. Lo que debemos procurar son pruebas autocontenidas que podamos entender y ejecutar sin demasiado contexto.
Otra consideración que no hemos tratado aquí son las pruebas que requieren elementos específicos, como variables de entorno u opciones. En esos casos, intenta utilizar conr como withr::local_envvar()
en cada prueba que lo requiera.
Una idea poderosa del libro “Ingeniería de Software en Google” de Titus Winters, Tom Manshreck y Hyrum Wright, es que el código puede permitirse ser un poco menos obvio porque tiene pruebas que lo cubren, pero el código de prueba, que no está cubierto por pruebas, no tiene este lujo.
Los tres capítulos sobre pruebas de paquetes del libro R packages de Hadley Wickham y Jenny Bryan son una lectura muy recomendable: Conceptos básicos de las pruebas, Diseñar tu conjunto de pruebas, Técnicas de prueba avanzadas.
Entrada del blog Por qué los buenos desarrolladores escriben malas pruebas unitarias por Michael Lynch.
Esto es una fuga. En otra prueba, uno podría preguntarse por qué existe algún objeto, por qué se ha establecido una opción concreta, etc. y es una pesadilla depurarlo. ↩︎