Teste unitário com código funcional tipado estaticamente

15

Eu queria perguntar a vocês, em que casos faz sentido testar o código funcional do tipo estático, como escrito em haskell, scala, ocaml, nemerle, f # ou haXe (o último é o que me interessa, mas Eu queria aproveitar o conhecimento das comunidades maiores).

Pergunto isso porque, do meu entendimento:

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

  • O aspecto mais óbvio dos testes unitários é rastrear erros que não podem ser revelados por meio da análise estática. Como esse tipo de código funcional seguro é uma boa ferramenta para codificar muito próximo do que seu analisador estático entende, parece que você pode mudar muita segurança em relação à análise estática. No entanto, um erro simples como usar x em vez de y (ambos sendo coordenadas) em seu código não pode ser coberto. OTOH tal erro também poderia surgir ao escrever o código de teste, por isso não tenho certeza se vale a pena o esforço.

  • Os testes de unidade apresentam 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. Esta sobrecarga, claro, é sobre constante, então alguém poderia argumentar, que isso realmente não importa. De fato, em linguagens como o Ruby, ele realmente não se compara aos benefícios, mas dado que a programação funcional com tipos estáticos abrange muitos testes de unidade de solo, parece que é uma sobrecarga constante que pode ser reduzida sem penalidade.

A partir disso, deduzo que os testes de unidade são um tanto obsoletos nesse estilo de programação. É claro que tal alegação só pode levar a guerras religiosas, então deixe-me resumir a uma simples pergunta:

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

    
por back2dos 24.11.2011 / 11:11
fonte

4 respostas

8

One aspect of unit tests is to have the specs in runnable form. However when employing a declarative style, that directly maps the formalized specs to language semantics, is it even actually possible to express the specs in runnable form in a separate way, that adds value?

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

However a simple mistake like using x instead of y (both being coordinates) in your code cannot be covered. However such a mistake could also arise while writing the test code, so I am not sure whether it is worth the effort.

Seu equívoco é que os testes de unidade são realmente para encontrar bugs em seu código em primeira mão - isso não é verdade, pelo menos, é apenas parcialmente verdadeiro. Eles são feitos para evitar que você introduza bugs posteriormente quando o código evoluir. Então, quando você testou sua função pela primeira vez e seu teste de unidade funcionou (com "x" e "y" corretamente no lugar), e então, ao refatorar você usa x em vez de y, o teste de unidade mostrará isso a você. / p>

Unit tests do introduce redundancy, which means that when requirements change, the code implementing them and the tests covering this code must both be changed. This overhead of course is about constant, so one could argue, that it doesn't really matter. In fact, in languages like Ruby it really doesn't compared to the benefits, but given how statically typed functional programming covers a lot of the ground unit tests are intended for, it feels like it's a constant overhead one can simply reduce without penalty.

Na engenharia, a maioria dos sistemas de segurança depende de redundância. Por exemplo, duas quebras em um carro, um pára-quedas redundante para um pára-quedista etc. A mesma ideia se aplica aos testes de unidade. Naturalmente, ter mais código para mudar quando os requisitos mudam pode ser uma desvantagem. Portanto, especialmente em testes unitários, é importante mantê-los DRY (siga o princípio "Dont Repeat Yourself"). Em uma linguagem com tipagem estática, você pode ter que escrever menos testes de unidade do que em uma linguagem de tipificação fraca. Especialmente testes "formais" podem não ser necessários - o que é uma coisa boa, já que lhe dá mais tempo para trabalhar nos testes unitários importantes que testam coisas substanciais. E não pense apenas porque você está com tipos estáticos, você não precisa de testes unitários, ainda há muito espaço para introduzir erros durante a refatoração.

    
por 24.11.2011 / 13:31
fonte
5

One aspect of unit tests is to have the specs in runnable form. However when employing a declarative style, that directly maps the formalized specs to language semantics, is it even actually possible to express the specs in runnable form in a separate way, that adds value?

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

When you use such a programming style, to which extents do you use unit tests and why (what quality is it you hope to gain for your code)?

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

Geralmente, a especificação (ou parte dela) de uma função pode ser expressa como propriedades relacionando o valor retornado aos argumentos. Neste caso, usando QuickCheck (para Haskell) ou ScalaCheck (para o Scala) permite que você anote essas propriedades como expressões do idioma e verifique se elas contêm entradas aleatórias.

    
por 24.11.2011 / 11:55
fonte
1

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

Veja um exemplo, em que o tio Bob era gentil o suficiente para 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 todo o jogo, mas o primeiro testa apenas uma função - aquela que calcula os vizinhos ao redor de uma célula. Você pode ver que todos os testes foram escritos de forma declarativa, com o comportamento que procuramos claramente definido.

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

    
por 11.12.2011 / 20:14
fonte
0

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

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

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

    
por 24.11.2011 / 11:55
fonte