Eu tenho lido um pouco sobre programação alfabética recentemente, e isso me fez pensar ... Testes bem escritos, especialmente especificações no estilo BDD, podem fazer um trabalho melhor ao explicar o que o código faz do que a prosa e tem 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 os idiomas não tendem a facilitar a separação do código do aplicativo e do teste quando escritos no mesmo arquivo de origem (e ninguém facilitou), ou existe um motivo mais básico para que as pessoas separem o código de teste do código do aplicativo?
testing
unit-testing
bdd
literate-programming
Chris Devereux
fonte
fonte
Respostas:
A única vantagem que posso pensar nos testes em linha seria reduzir o número de arquivos a serem gravados. Com os IDEs modernos, isso realmente não é grande coisa.
Existem, no entanto, várias desvantagens óbvias nos testes em linha:
fonte
Eu posso pensar em alguns:
Legibilidade. A intercalação de códigos e testes "reais" tornará mais difícil a leitura do código real.
Código inchaço. Misturar código "real" e código de teste nos mesmos arquivos / classes / o que provavelmente resultar em arquivos compilados maiores, etc. Isso é particularmente importante para idiomas com ligação tardia.
Você pode não querer que seus clientes vejam seu código de teste. (Não gosto desse motivo ... mas se você estiver trabalhando em um projeto de código fechado, é improvável que o código de teste ajude o cliente de qualquer maneira.)
Agora, existem soluções possíveis para cada um desses problemas. Mas na OMI, é mais simples não ir para lá em primeiro lugar.
Vale observar que, nos primeiros dias, os programadores Java costumavam fazer esse tipo de coisa; por exemplo, incluindo um
main(...)
método em uma classe para facilitar o teste. Essa idéia desapareceu quase completamente. É prática da indústria implementar testes separadamente usando algum tipo de estrutura de teste.Também vale a pena observar que a programação alfabetizada (como concebida por Knuth) nunca se destacou na indústria de engenharia de software.
fonte
Na verdade, você pode pensar em Design por contrato como fazendo isso. O problema é que a maioria das linguagens de programação não permite escrever códigos como este :( É muito fácil testar pré-condições manualmente, mas as condições pós são um verdadeiro desafio sem alterar a maneira como você escreve um código (um IMO negativo enorme).
Michael Feathers tem uma apresentação sobre isso e esta é uma das muitas maneiras pelas quais ele menciona que você pode melhorar a qualidade do código.
fonte
Por muitas das mesmas razões pelas quais você tenta evitar um acoplamento rígido entre classes no seu código, também é uma boa idéia evitar um acoplamento desnecessário entre testes e código.
Criação: Testes e código podem ser escritos em momentos diferentes, por pessoas diferentes.
Controle: se testes são usados para especificar requisitos, você certamente deseja que eles estejam 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ê tenha um pedaço de código que faz o trabalho corretamente, mas deixa muito a desejar em termos de desempenho, manutenção, qualquer que seja. Você decide substituir esse código por um código novo e aprimorado. 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 de quais testes você deseja executar.
Por exemplo, você pode ter um pequeno conjunto de testes relacionados apenas ao código em que está trabalhando no momento e um conjunto maior que testa todo o projeto.
fonte
Aqui estão algumas razões adicionais em que posso pensar:
a realização de testes em uma biblioteca separada facilita a vinculação apenas dessa 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 isso quando a solução mais fácil é gravar os testes no um local separado)
testes de uma função, classe e biblioteca são tipicamente escritos do ponto de vista de "usuários" (um usuário dessa função / classe / biblioteca). Esse "uso de código" é tipicamente escrito em um arquivo ou biblioteca separado e um teste pode ser mais claro ou "mais realista" se imitar essa situação.
fonte
Se os testes estivessem em linha, seria necessário remover o código necessário para o envio ao enviar o produto ao seu cliente. Portanto, um local extra onde você armazena seus testes simplesmente separa o código que você precisa e o código que seu cliente precisa.
fonte
Essa idéia simplesmente equivale a um método "Self_Test" dentro do contexto de um design baseado em objeto ou orientado a objeto. Se estiver usando uma linguagem baseada em objeto 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 idéia extremamente boa, e se os programadores estivessem realmente preocupados com a qualidade, todos estariam fazendo isso. Uma questão importante, porém, é que o método "Self_Test" precisa ter disciplina intensa, pois não pode acessar nenhum detalhe da implementação e deve confiar apenas em todos os outros métodos publicados na especificação do objeto. Obviamente, se o autoteste falhar, a implementação precisará mudar. O autoteste deve estar testando rigorosamente todas as propriedades publicadas dos métodos do objeto, mas nunca confiando em nenhum detalhe de qualquer implementação específica.
Linguagens baseadas em objetos e orientadas a objetos freqüentemente 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 qualquer tentativa desse tipo for detectada ) Mas todos os métodos internos do objeto têm 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 que está sendo testado), mas precisa receber toda a disciplina do compilador de um método externo ( deve ser independente dos detalhes de implementação do objeto). Poucas ou nenhuma linguagem de programação fornece a capacidade de disciplinar um objeto ' s método interno como se fosse um método externo. Portanto, esse é um problema importante de design da linguagem de programação.
Na ausência 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 Booleano; "). O objeto complementar dependerá da especificação do objeto principal e o compilador aplicará totalmente toda a disciplina dessa especificação contra a implementação do objeto complementar.
fonte
Isso ocorre em resposta a um grande número de comentários, sugerindo que os testes em linha 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 já suportam isso, com linguagens compiladas, como C, C ++, C #, isso é feito com as chamadas diretivas do compilador.
No caso de c # (acredito que também em c ++, a sintaxe pode ter um pouco diferente dependendo do compilador que você está usando), é assim que você pode fazê-lo.
Como isso usa diretivas do compilador, o código não existirá nos arquivos executáveis criados se os sinalizadores não estiverem definidos. É assim também que você cria programas "escreva uma vez, compile duas vezes" para várias plataformas / hardware.
fonte
Usamos testes em linha com nosso código Perl. Existe 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 incluídos.
Respondendo a algumas das preocupações levantadas:
+-- 33 lines: #test----
. Quando você deseja trabalhar com o teste, basta expandi-lo.Para referência:
fonte
O Erlang 2 realmente suporta testes em linha. Qualquer expressão booleana no código que não é usada (por exemplo, atribuída a uma variável ou aprovada) é automaticamente tratada como um teste e avaliada pelo compilador; se a expressão for falsa, o código não será compilado.
fonte
Outro motivo para separar testes é que você costuma usar bibliotecas adicionais ou até diferentes para testar e não para a implementação real. Se você combinar testes e implementação, o uso acidental de bibliotecas de teste na implementação não poderá ser capturado 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. :-)
fonte
Isso não é verdade. É muito melhor colocar seus testes de unidade 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 no .NET, por exemplo, poderá colocar seu código de teste no assembly de produção e usar o Scalpel para removê-los antes de enviar.
fonte