Código funcional de teste estatístico de teste de unidade

15

Eu queria perguntar a vocês, em quais casos faz sentido testar o código funcional de tipo estatístico, conforme escrito em haskell, scala, ocaml, nemerle, f # ou haXe (o último é o que realmente me interessa, mas queria explorar o conhecimento das comunidades maiores).

Peço isso porque, pelo meu entendimento:

  • Um aspecto dos testes de unidade é ter as especificações na forma executável. No entanto, ao empregar um estilo declarativo, que mapeia diretamente as especificações formalizadas para a semântica da linguagem, é realmente possível expressar as especificações na forma executável de uma maneira separada, que agrega valor?

  • O aspecto mais óbvio dos testes de unidade é rastrear erros que não podem ser revelados através da análise estática. Como o código funcional seguro do tipo é uma boa ferramenta para codificar extremamente próximo do que o seu analisador estático entende, parece que você pode mudar muita segurança para a análise estática. No entanto, um erro simples como usar em xvez de y(ambas coordenadas) no seu código não pode ser coberto. Esse erro também pode ocorrer ao escrever o código de teste, por isso não tenho certeza se vale a pena o esforço.

  • Os testes de unidade introduzem redundância, o que significa que, quando os requisitos mudam, o código que os implementa e os testes que cobrem esse código devem ser alterados. É claro que essa sobrecarga é constante, portanto, alguém poderia argumentar que realmente não importa. De fato, em linguagens como Ruby, isso realmente não se compara aos benefícios, mas, considerando que a programação funcional de tipo estatístico cobre muitos dos testes de unidade de solo, parece que é uma sobrecarga constante que se pode simplesmente reduzir sem penalidade.

A partir disso, deduziria que os testes de unidade são um pouco obsoletos nesse estilo de programação. Claro que essa afirmação só pode levar a guerras religiosas, então deixe-me resumir isso em uma pergunta simples:

Quando você usa esse estilo de programação, em quais extensões você usa testes de unidade e por quê (que qualidade você espera obter pelo seu código)? Ou o contrário: você tem critérios pelos quais pode qualificar uma unidade de código funcional de tipo estatístico, conforme coberto pelo analisador estático e, portanto, sem necessidade de cobertura de teste de unidade?

back2dos
fonte
4
A propósito, se você ainda não experimentou o QuickCheck , definitivamente deveria.
Jon Purdy
scalacheck.org é o equivalente Scala
V-lâmpada

Respostas:

8

Um aspecto dos testes de unidade é ter as especificações na forma executável. No entanto, ao empregar um estilo declarativo, que mapeia diretamente as especificações formalizadas para a semântica da linguagem, é realmente possível expressar as especificações na forma executável de uma maneira separada, que agrega valor?

Se você tem especificações que podem ser mapeadas diretamente para declarações de função - tudo bem. Mas normalmente esses são dois níveis completamente diferentes de abstrações. Os testes de unidade destinam-se a testar partes de código únicas, escritas como testes de caixa branca pelo mesmo desenvolvedor que está trabalhando na função. As especificações normalmente se parecem com "quando digito esse valor aqui e pressiono esse botão, isso e aquilo devem acontecer". Normalmente, essas especificações levam a muito mais de uma função a ser desenvolvida e testada.

No entanto, um erro simples como usar x em vez de y (sendo ambas as coordenadas) no seu código não pode ser coberto. No entanto, esse erro também pode surgir ao escrever o código de teste, por isso não tenho certeza se vale a pena o esforço.

Seu equívoco é que os testes de unidade são, na verdade, para encontrar bugs em seu código em primeira mão - isso não é verdade, pelo menos, é apenas parcialmente verdade. Eles são feitos para impedir que você introduza erros mais tarde, quando o seu código evoluir. Portanto, quando você testou sua função e seu teste de unidade pela primeira vez (com "x" e "y" corretamente), e depois de refatorar você usa x em vez de y, o teste de unidade mostra isso para você.

Os testes de unidade introduzem redundância, o que significa que, quando os requisitos mudam, o código que os implementa e os testes que cobrem esse código devem ser alterados. É claro que essa sobrecarga é constante, portanto, alguém poderia argumentar que realmente não importa. De fato, em linguagens como Ruby, isso realmente não se compara aos benefícios, mas, considerando que a programação funcional de tipo estatístico cobre muitos dos testes de unidade de solo, parece que é uma sobrecarga constante que se pode simplesmente reduzir sem penalidade.

Na engenharia, a maioria dos sistemas de segurança depende de redundância. Por exemplo, duas quebras no carro, um para-quedas redundante para um mergulhador do céu etc. A mesma idéia se aplica aos testes de unidade. Obviamente, ter mais código para alterar quando os requisitos mudarem pode ser uma desvantagem. Portanto, especialmente em testes de unidade, é importante mantê-los SECA (siga o princípio "Não se repita"). Em um idioma estaticamente tipado, talvez você precise escrever menos testes de unidade do que em um idioma tipicamente fraco. Testes "formais", especialmente, podem não ser necessários - o que é uma coisa boa, pois dá mais tempo para você trabalhar nos testes de unidade importantes que testam coisas substanciais. E não pense apenas porque você é do tipo estático, não precisa de testes de unidade, ainda há muito espaço para introduzir erros durante a refatoração.

Doc Brown
fonte
5

Um aspecto dos testes de unidade é ter as especificações na forma executável. No entanto, ao empregar um estilo declarativo, que mapeia diretamente as especificações formalizadas para a semântica da linguagem, é realmente possível expressar as especificações na forma executável de uma maneira separada, que agrega valor?

É muito improvável que você consiga expressar completamente suas especificações como restrições de tipo.

Quando você usa esse estilo de programação, em quais extensões você usa testes de unidade e por quê (que qualidade você espera obter pelo seu código)?

De fato, um grande benefício desse estilo é que funções puras são mais fáceis de testar em unidade: não é necessário configurar o estado externo ou verificá-lo após a execução.

Frequentemente, a especificação (ou parte dela) de uma função pode ser expressa como propriedades relacionadas ao valor retornado aos argumentos. Nesse caso, o uso do QuickCheck (para Haskell) ou ScalaCheck (para Scala) pode permitir que você anote essas propriedades como expressões do idioma e verifique se elas possuem entradas aleatórias.

Alexey Romanov
fonte
1
Um pouco mais detalhadamente sobre o QuickCheck: a idéia básica é que você escreva "propriedades" (invariantes no seu código) e especifique como gerar entrada potencial. O Quickcheck cria uma tonelada de entradas aleatórias e garante que a sua invariável seja válida em todos os casos. É mais completo que o teste de unidade.
Tikhon Jelvis
1

Você pode pensar em testes de unidade como um exemplo de como usar o código, juntamente com uma descrição do por que é valioso.

Aqui está um exemplo, no qual o tio Bob teve a gentileza de emparelhar comigo em "Game of Life", de John Conway . Eu acho que é um excelente exercício para esse tipo de coisa. A maioria dos testes é de sistema completo, testando o jogo inteiro, mas o primeiro testa apenas uma função - a que calcula os vizinhos ao redor de uma célula. Você pode ver que todos os testes são escritos em forma declarativa, com o comportamento que estamos procurando claramente explicitado.

Também é possível zombar de funções usadas em funções; passando-os para a função (o equivalente a injeção de dependência) ou com estruturas como Midje de Brian Marick .

Lunivore
fonte
0

Sim, os testes de unidade já fazem sentido com o código funcional digitado estaticamente. Um exemplo simples:

prop_encode a = (decode . encode $ a) == a

Você pode forçar prop_encodecom o tipo estático.

Zhen
fonte