segunda-feira, 18 de março de 2024 From rOpenSci (https://ropensci.org/pt/blog/2024/03/18/dry-damp/). Except where otherwise noted, content on this site is licensed under the CC-BY license.
rOpenSci’s O segundo grupo de campeões e campeãs foi integrado! O treinamento começou com uma sessão sobre estilo de código, foi seguido por três sessões sobre os fundamentos do desenvolvimento de pacotes R e terminou com uma sessão sobre desenvolvimento avançado de pacotes R que consistiu em um pot-pourri de dicas com discussão, seguido de tempo para aplicar esses princípios aos pacotes das pessoas participantes. Aqui, quero compartilhar um dos tópicos abordados: Testes de pacotes e, em particular, os princípios DRY (“não se repita”) e DAMP (“frases descritivas e significativas”). Para esse tópico, usamos um repositório do GitHub que contém um pacote R cujos diferentes commits ilustram os dois princípios. Em cada etapa, compartilharemos um commit ou diff que ilustra as alterações feitas.
Primeiro commit: Configurar nossos arquivos de teste, test-works.R e test-ok.R
Esses arquivos de teste iniciais definem um objeto chamado test_object no nível superior, que é usado em dois testes em cada arquivo.
test-works.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)
})
e
test-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)
})
Esse não é um padrão ideal porque você não pode examinar cada teste test_that() isoladamente e entender rapidamente o que está acontecendo.
Em um arquivo de teste muito longo, você teria que rolar a tela para cima e para baixo! Além disso, estamos sendo repetitivos ao definir o mesmo objeto de teste em dois arquivos de teste.
Próximo commit: Descompactar esses arquivos
Nesta etapa, lembramos diligentemente sobre DRY, Don’t Repeat Yourself (Não se repita), e sobre a mecânica de arquivos auxiliares de teste:
arquivos cujos nomes começam com helper- são carregados antes de todos os testes.
Portanto, criamos um arquivo auxiliar (helper-swamp.R) no qual o test_object é definido e, desta forma, disponível para testes!
Em tests/testthat/helper-swamp.R,
test_object <- list(a = 1, b = 2)
Nos arquivos de teste, removemos a primeira linha que definia test_object.
Agora as coisas ainda não estão perfeitas.
Quando olhamos para qualquer um dos arquivos de teste, não podemos realmente saber o que é test_object, pois seu nome não é “descritivo e significativo”.
Além disso, agora temos test_object que é sempre definido, mesmo que não seja usado em um teste.
Na melhor das hipóteses, isso é desnecessário e inútil; na pior, pode ter efeitos colaterais indesejados, especialmente em códigos mais complexos! 1
Terceiro compromisso: Aplicar os princípios do DAMP
No arquivo auxiliar, tests/testthat/helper-swamp.R reescrevemos o código em um arquivo função com um nome mais significativo (pelo menos vamos fingir que é!).
basic_list <- function() {
list(a = 1, b = 2)
}
Em seguida, chamamos essa função para definir o objeto em todos os testes em que ele for necessário. Assim, os arquivos de teste se tornam
test_that("division works", {
test_object <- basic_list()
expect_equal(test_object[["b"]] / 2, 1)
})
e
test_that("substraction works", {
test_object <- basic_list()
expect_equal(test_object[["a"]] - 1, 0)
})
Agora, embora a definição real da lista básica não esteja em todos os testes, temos uma ideia melhor do que está acontecendo ao ler o teste.
Além disso, se o teste falhasse, poderíamos executar devtools::load_all() no console e executar o código do teste, já que devtools::load_all() carrega os arquivos auxiliares do testthat, tornando basic_list() disponível.
O equilíbrio entre DRY (“Don’t repeat yourself”) e DAMP (“Descriptive and meaningful phrases”) é uma troca. Para manter a analogia com a água, também precisamos garantir que nosso código não tenha efeitos que possam “vazar” inesperadamente. O que devemos buscar são testes autônomos que possamos entender e executar sem muito contexto.
Outra consideração que não abordamos aqui são os testes que exigem elementos específicos, como variáveis de ambiente ou opções. Nesses casos, tente usar withr como withr::local_envvar() em cada teste que o exigir.
Uma ideia poderosa do livro “Software Engineering at Google” de Titus Winters, Tom Manshreck e Hyrum Wright, é que o código pode se dar ao luxo de ser um pouco menos óbvio porque tem testes que o cobrem, mas o código de teste, que não é coberto por testes, não tem esse privilégio.
Os três capítulos sobre testes de pacotes do livro R packages, de Hadley Wickham e Jenny Bryan, são uma leitura altamente recomendada: Testing basics, Designing your test suite, Advanced testing techniques.
Postagem no blog Why Good Developers Write Bad Unit Tests por Michael Lynch.
Isso é um vazamento. Em outro teste, você poderia se perguntar por que um objeto existe, por que uma opção específica foi definida, etc., e isso é um pesadelo para depurar. ↩︎