class: center, middle, inverse, title-slide # dittodb ## simplificando las pruebas con bases de datos ### Mauricio ‘Pachá’ Vargas · PUC Chile ### ConectaR 2021 ### 2021-02-04 --- background-position: 50% 50% background-size: cover class: inverse, center, top # ¿En qué consisten las pruebas? --- class: inverse, center, middle ## Una forma de verificar que el código que escribimos haga lo que esperamos que haga. --- # Es más fácil decirlo que hacerlo <br /><br /> Cualquier prueba es mejor que no hacer pruebas -- <br /><br /> pero una buena prueba vale su peso en oro (metafóricamente) --- # Una buena prueba es * de rápida ejecución * expresiva * no requiere configuraciones especiales -- # Pero las bases de datos son * (relativamente) lentas * dependen de variables o configuraciones externas * requieren una conexión o configuración personalizada --- # IC + BBDD = ☠️ La *Integración Continua* (como GitHub Actions, Travis, AppVeyor) es maravillosa, permite ejecutar código en la ☁️ 💻 de alguien más. <br/> <br/> -- Pero para correr el código en un entorno debemos configurarlo. -- No se puede iterar de forma interactiva al hacer esto. <br/> <br/> -- Incluso instalar dependencias de R puede ser un problema. --- background-image: url("images/db-test-ci-db-hard-more-again.png") background-position: 50% 50% background-size: contain class: center, bottom --- background-image: url("images/db-test-ci-db-hard-more.png") background-position: 0% 20% background-size: contain class: center, bottom -- # Ya pasamos por esto <br/> para que no tengas que hacerlo --- background-position: 50% 50% background-size: cover class: center, top # ¿Como ayuda `dittodb`? --- # Imagina que tenemos una función ```r #' Get random airline(s) #' ... get_an_airline <- function(n = 1) { con <- DBI::dbConnect(RPostgres::Postgres(), dbname = "nycflights") on.exit(DBI::dbDisconnect(con)) query <- paste0( "SELECT carrier, name FROM airlines ", "ORDER BY random() LIMIT ", n ) out <- dbGetQuery(con, query) if (out$name == "Testy McAirline") { stop("This is the test airline 🙃") } return(out) } ``` --- # ¿Cómo podemos probar la función? Localmente es "fácil": * configuramos un servidor PostgreSQL * nos aseguramos de que exista el usuario * agregamos los datos de prueba * obtenemos el beneficio -- Con una IC hacemos "exactamente" lo mismo: * configuramos un servidor PostgreSQL * nos aseguramos de que exista el usuario * agregamos los datos de prueba -- <br/> <div class = "large">️ ☠️ ☠️ ☠️ </div> --- # `dittodb` te permite Escribir pruebas que operan de igual modo que si estuvieramos conectados a una base de datos real usando `with_mock_db({...})` y sin la necesidad de configurar una base de datos. <br/ > ```r with_mock_db({ test_that("We get one airline", { one_airline <- get_an_airline() expect_is(one_airline, "data.frame") expect_equal(nrow(one_airline), 1) expect_equal(one_airline$carrier, "9E") expect_equal(one_airline$name, "Endeavor Air Inc.") }) }) ``` --- # Terminología de pruebas * mocks -- objetos pre-programados con un valor esperado y que constituyen una especificación * fixtures -- datos usados por los objetos de tipo mock para configurarse o proveer información * stubs -- proveen respuestas embebidas a llamadas durante las pruebas, habitualmente no responden a cualquier cosa fuera del propósito para el cual se escribieron en la prueba * spies -- stubs, pero proporcionan información acerca de cómo se llamaron * fakes -- objetos que tienen una implementación, pero habitualmente toma un atajo que no los hace convenientes para su uso en producción (una BBDD en memoria es un buen ejemplo) * 😱 Referencia: https://www.martinfowler.com/articles/mocksArentStubs.html --- # Capturando interacciones Captura las interacciones con BBDD para que escribir y editar lo siguiente sea fácil. ```r capture_db_requests({ one_airline <- get_an_airline() }) ``` <br /> Donde `one_airline` es ```r carrier name 1 AS Alaska Airlines Inc. ``` --- # Capturando interacciones La respuesta se guarda en `testthat/tests/nycflights` en el archivo `SELECT-c3427d.R`: ```r structure( list(carrier = "AS", name = "Alaska Airlines Inc."), class = "data.frame", row.names = c(NA, -1L) ) ``` -- La ruta es `testthat/tests` y luego el nombre de la BBDD `nycflights` a la que nos conectamos. -- El nombre de archivo comienza con el verbo SQL `SELECT`, seguido de un identificador único para la query. Ya que el identificador es único, el resultado siempre será el mismo. La forma de modificar esto es cambiar la ruta. --- # `dittodb` permite .flex-box[ Escribir tests como el siguiente ```r with_mock_db({ test_that("We get one airline", { one_airline <- get_an_airline() expect_is(one_airline, "data.frame") expect_equal(nrow(one_airline), 1) expect_equal(one_airline$carrier, "9E") expect_equal(one_airline$name, "Endeavor Air Inc.") }) }) ``` .pull-right[... ¡se puede ejecutar en cualquier parte!] ] --- # `dittodb` permite Escribir casos que incluyen condiciones de borde (e.g. cosas que no deberían suceder en la BBDD) ```r with_mock_path(path = "error_db_fixt/", { with_mock_db({ test_that("We get one airline", { expect_error( get_an_airline(), "This is the test airline \U1F643 ) }) }) }) ``` --- background-position: 50% 50% background-size: cover class: center, top # ¡Es simple! --- background-position: 50% 50% background-size: cover class: center, top # Demo --- # ¡Gracias! .flex-box[ `dittodb` es parte de **rOpenSci** y está documentado en [_dittodb.jonkeane.com_](https://dittodb.jonkeane.com) Se puede instalar desde CRAN mediante `install.packages("dittodb")` Agradecimientos especiales a Jonathan Keane @jonkeane y a [`httptest`](https://enpiar.com/r/httptest) por la inspiración. ]