Existe algum motivo para os testes não estarem escritos em linha com o código que eles testam?

90

Eu tenho lido um pouco sobre Programação Literária recentemente, e isso me fez pensar ... Bem escrito testes, especialmente as especificações do estilo BDD, podem fazer um trabalho melhor em explicar o que o código faz do que em prosa e têm a grande vantagem de verificar sua própria precisão.

Eu nunca vi testes escritos em linha com o código que eles testam. Isso ocorre apenas porque as linguagens não tendem a simplificar a separação de aplicativos e códigos de teste quando escritas no mesmo arquivo de origem (e ninguém facilitou isso) ou existe uma razão mais baseada em princípios para separar o código de teste do código do aplicativo?

    
por Chris Devereux 25.02.2013 / 14:28
fonte

12 respostas

89

A única vantagem que posso pensar em testes inline seria reduzir o número de arquivos a serem gravados. Com os IDEs modernos, isso não é tão importante assim.

Existem, no entanto, algumas desvantagens óbvias no teste em linha:

  • Ele viola separação de interesses . Isso pode ser discutível, mas para mim testar a funcionalidade é uma responsabilidade diferente da implementação.
  • Você teria que introduzir novos recursos de idioma para distinguir entre testes / implementação ou arriscaria borrar a linha entre os dois.
  • É difícil trabalhar com arquivos de origem maiores: mais difícil de ler, mais difícil de entender, é mais provável que você tenha que lidar com conflitos de controle de origem.
  • Acho que seria mais difícil colocar seu chapéu de "testador", por assim dizer. Se você está olhando para os detalhes da implementação, ficará mais tentado a pular a implementação de certos testes.
por 25.02.2013 / 15:15
fonte
36

Eu posso pensar em alguns:

  • Legibilidade. A interpolação de códigos e testes "reais" dificultará a leitura do código real.

  • Código inchado. Mistura código "real" e código de teste nos mesmos arquivos / classes / qualquer que seja a probabilidade de resultar em arquivos compilados maiores, etc. Isso é particularmente importante para idiomas com ligação tardia.

  • Você pode não querer que seus clientes / clientes vejam seu código de teste. (Eu não gosto deste motivo ... mas se você está trabalhando em um projeto de código fechado, o código de teste é improvável para ajudar o cliente de qualquer maneira.)

Agora, existem soluções possíveis para cada um desses problemas. Mas IMO, é mais simples não ir lá em primeiro lugar.

Vale a pena observar que, nos primeiros dias, os programadores Java faziam esse tipo de coisa; por exemplo. incluindo um método main(...) em uma classe para facilitar o teste. Essa ideia desapareceu quase completamente. É prática da indústria implementar testes separadamente usando uma estrutura de teste de algum tipo.

Também vale a pena observar que a Programação Alfabetizada (como concebida por Knuth) nunca pegou na indústria de engenharia de software.

    
por 25.02.2013 / 14:38
fonte
13

Por muitas das mesmas razões que você tenta evitar um strong acoplamento entre classes em seu código, também é uma boa idéia evitar o acoplamento desnecessário entre testes e código.

Criação: Os testes e o código podem ser escritos em momentos diferentes, por pessoas diferentes.

Controle: Se os testes forem usados para especificar requisitos, você certamente desejaria que eles estivessem sujeitos a regras diferentes sobre quem pode alterá-los e quando o código real é.

Reutilização: Se você colocar os testes em linha, não poderá usá-los com outro trecho de código.

Imagine que você tem um pedaço de código que faz o trabalho corretamente, mas deixa muito a desejar em termos de desempenho, capacidade de manutenção, etc. Você decide substituir esse código por um código novo e melhorado. Usar o mesmo conjunto de testes pode ajudá-lo a verificar se o novo código produz os mesmos resultados que o código antigo.

Selecionabilidade: Manter os testes separados do código facilita a escolha dos testes que você deseja executar.

Por exemplo, você pode ter um pequeno conjunto de testes relacionados apenas ao código em que você está trabalhando e um conjunto maior que testa o projeto inteiro.

    
por 25.02.2013 / 20:03
fonte
13

Na verdade, você pode pensar em projeto por contrato ao fazer isso. O problema é que a maioria das linguagens de programação não permite que você escreva código como este :( É muito fácil testar pré-condições manualmente, mas as condições de postagem são um desafio real sem alterar a maneira como você escreve código (um enorme IMO negativo). / p>

Michael Feathers fez uma apresentação sobre isso e essa é uma das muitas maneiras pelas quais ele menciona que você pode melhorar a qualidade do código.

    
por 21.03.2013 / 19:43
fonte
10

Aqui estão alguns motivos adicionais em que posso pensar:

  • ter testes em uma biblioteca separada torna mais fácil vincular somente essa biblioteca à sua estrutura de teste e não ao seu código de produção (isso pode ser evitado por algum pré-processador, mas por que criar uma coisa assim quando a solução mais fácil é escrever os testes em um lugar separado)

  • testes de uma função, uma classe, uma biblioteca são tipicamente escritos de um ponto de vista de "usuários" (um usuário daquela função / classe / biblioteca). Esse "código de uso" é normalmente escrito em um arquivo ou biblioteca separado, e um teste pode ser mais claro ou "mais realista" se imitar essa situação.

por 25.02.2013 / 15:14
fonte
5

Se os testes estiverem em linha, será necessário remover o código necessário para o teste quando você enviar o produto ao seu cliente. Assim, um local extra onde você armazena seus testes simplesmente separa o código que você precisa e o código que o cliente precisa.

    
por 25.02.2013 / 14:38
fonte
4

Isto é em resposta a um grande número de comentários sugerindo que testes inline não são feitos porque é difícil impossível remover o código de teste das compilações de versão. Isso é falso. Quase todos os compiladores e montadores suportam isso já, com linguagens compiladas, como C, C ++, C #, isso é feito com o que é chamado de diretivas de compilador.

No caso do c # (acredito que o c ++ também, a sintaxe pode ter um pouco diferente dependendo do compilador que você está usando) é assim que você pode fazer isso.

#define DEBUG //  = true if c++ code
#define TEST /* can also be defined in the make file for c++ or project file for c# and applies to all associated .cs/.cpp files */

//somewhere in your code
#if DEBUG
// debug only code
#elif TEST
// test only code
#endif

Como isso usa diretivas de compilador, o código não existirá nos arquivos executáveis criados se os sinalizadores não estiverem definidos. É também assim que você faz programas "escreva uma vez, compile duas vezes" para múltiplas plataformas / hardware.

    
por 04.05.2013 / 23:02
fonte
3

Essa ideia simplesmente equivale a um método "Self_Test" dentro do contexto de um projeto baseado em objeto ou orientado a objeto. Se estiver usando uma linguagem baseada em objetos compilada como Ada, todo o código de autoteste será marcado pelo compilador como não utilizado (nunca invocado) durante a compilação de produção e, portanto, tudo será otimizado - nada disso aparecerá no executável resultante.

Usar um método "Self_Test" é uma ótima idéia, e se os programadores estivessem realmente preocupados com a qualidade, todos estariam fazendo isso. Uma questão importante, no entanto, é que o método "Self_Test" precisa ter disciplina intensa, pois não pode acessar nenhum dos detalhes da implementação e deve, em vez disso, confiar apenas em todos os outros métodos publicados dentro da especificação do objeto. Obviamente, se o autoteste falhar, a implementação precisará ser alterada. O autoteste deve testar rigorosamente todas as propriedades publicadas dos métodos do objeto, mas nunca confiar de maneira alguma em detalhes de qualquer implementação específica.

Linguagens orientadas a objetos e orientadas a objetos frequentemente fornecem exatamente esse tipo de disciplina com relação a métodos externos ao objeto testado (eles reforçam a especificação do objeto, impedindo qualquer acesso aos detalhes de sua implementação e gerando um erro de compilação se tentativa é detectada). Mas todos os métodos internos do objeto recebem acesso completo a todos os detalhes da implementação. Portanto, o método de autoteste está em uma situação única: ele precisa ser um método interno devido à sua natureza (o autoteste é obviamente um método do objeto testado), mas ele precisa receber toda a disciplina de compilador de um método externo ( tem que ser independente dos detalhes de implementação do objeto). Poucas se qualquer linguagem de programação fornece a capacidade de disciplinar o método interno de um objeto como se fosse um método externo. Então, esse é um importante problema de design de linguagem de programação.

Na falta de suporte adequado à linguagem de programação, a melhor maneira de fazer isso é criar um objeto complementar. Em outras palavras, para cada objeto que você codifica (vamos chamá-lo de "Big_Object"), você também cria um segundo objeto complementar cujo nome consiste em um sufixo padrão concatenado com o nome do objeto "real" (nesse caso, "Big_Object_Self_Test" "), e cuja especificação consiste em um único método (" Big_Object_Self_Test.Self_Test (This_Big_Object: Big_Object) retorna Boolean; "). O objeto complementar dependerá da especificação do objeto principal, e o compilador aplicará completamente toda a disciplina dessa especificação na implementação do objeto complementar.

    
por 04.05.2013 / 19:01
fonte
2

Usamos testes inline com nosso código Perl. Há um módulo, Test :: Inline , que gera arquivos de teste a partir do código embutido.

Não sou particularmente bom em organizar meus testes e os achei mais fáceis e com maior probabilidade de serem mantidos quando incorporados.

Respondendo a algumas das preocupações levantadas:

  • Os testes embutidos são escritos em seções POD, portanto, não fazem parte do código real. Eles são ignorados pelo intérprete, então não há código inchado.
  • Nós usamos Dobradura do Vim para ocultar as seções de teste. A única coisa que você vê é uma única linha acima de cada método sendo testado como +-- 33 lines: #test---- . Quando você quer trabalhar com o teste, basta expandi-lo.
  • O módulo Test :: Inline "compila" os testes para arquivos compatíveis com TAP normais, para que eles possam coexistir com os testes tradicionais.

Para referência:

por 04.05.2013 / 23:04
fonte
1

O Erlang 2 suporta testes inline. Qualquer expressão booleana no código que não é usada (por exemplo, atribuída a uma variável ou passada) é automaticamente tratada como um teste e avaliada pelo compilador; se a expressão é falsa, o código não compila.

    
por 04.05.2013 / 21:38
fonte
1

Outro motivo para separar os testes é que você costuma usar bibliotecas adicionais ou até diferentes para testes do que para a implementação real. Se você misturar testes e implementação, o uso acidental de bibliotecas de teste na implementação não poderá ser detectado pelo compilador.

Além disso, os testes tendem a ter muito mais linhas de código do que as partes de implementação testadas, portanto, você terá problemas para encontrar a implementação entre todos os testes. : -)

    
por 04.06.2013 / 10:52
fonte
0

Isso não é verdade. É muito melhor colocar seus testes unitários ao lado do código de produção quando o código de produção, especialmente quando a rotina de produção é pura.

Se você estiver desenvolvendo com o .NET, por exemplo, poderá colocar seu código de teste no assembly de produção e usar Scalpel para removê-los antes de enviar.

    
por 06.05.2016 / 23:07
fonte