Por que o teste de um idioma não é um recurso suportado no nível de sintaxe?

37

Você pode encontrar uma lista interminável de blogs, artigos e sites que promovem os benefícios do teste unitário de seu código-fonte. É quase garantido que os desenvolvedores que programaram os compiladores para Java, C ++, C # e outras linguagens digitadas usaram testes de unidade para verificar seu trabalho.

Então, por que, apesar de sua popularidade, o teste está ausente na sintaxe desses idiomas?

A Microsoft introduziu o LINQ no C # , então por que eles não poderiam adicionar testes?

Não pretendo prever quais seriam essas mudanças de idioma, mas esclarecer por que elas estão ausentes no início.

Como exemplo: sabemos que você pode escrever um forloop sem a sintaxe da forinstrução. Você pode usar whileou if/ gotodeclarações. Alguém decidiu que uma fordeclaração era mais eficiente e a introduziu em um idioma.

Por que os testes não seguiram a mesma evolução das linguagens de programação?

Reactgular
fonte
24
É realmente melhor tornar uma especificação de linguagem mais complexa adicionando sintaxe, em vez de projetar uma linguagem com regras simples que podem ser facilmente estendidas de maneira genérica?
KChaloux
6
O que você quer dizer com "no nível da sintaxe"? Você tem algum detalhe / idéia sobre como isso seria implementado?
SJuan76 3/07
21
Você poderia dar um exemplo de pseudo-código de teste como um recurso suportado por sintaxe? Estou curioso para ver como você acha que seria.
FrustratedWithFormsDesigner
12
@FrustratedWithFormsDesigner Veja a linguagem D para um exemplo.
Doval
4
Outro idioma com recursos internos de teste de unidade é o Rust.
Sebastian Redl

Respostas:

36

Como em muitas coisas, o teste de unidade é melhor suportado no nível da biblioteca, não no nível do idioma. Em particular, o C # tem inúmeras bibliotecas de teste de unidade disponíveis, além de coisas nativas do .NET Framework, como Microsoft.VisualStudio.TestTools.UnitTesting.

Cada biblioteca de Teste de Unidade possui uma filosofia e sintaxe de teste um pouco diferente. Todas as coisas são iguais, mais opções são melhores que menos. Se o teste de unidade fosse inserido no idioma, você estaria bloqueado nas opções do designer do idioma ou estaria usando ... uma biblioteca e evitando completamente os recursos de teste de idioma.

Exemplos

  • Nunit - Estrutura de teste de unidade de uso geral, projetada para o idioma, que tira o máximo proveito dos recursos de linguagem do C #.

  • Moq - estrutura de zombaria que aproveita ao máximo as expressões lambda e as árvores de expressão, sem uma metáfora de registro / reprodução.

Existem muitas outras opções. Bibliotecas como o Microsoft Fakes podem criar zombarias "shims ..." que não exigem que você escreva suas classes usando interfaces ou métodos virtuais.

Linq não é um recurso de idioma (apesar do nome)

Linq é um recurso de biblioteca. Temos muitos recursos novos na própria linguagem C # de graça, como expressões lambda e métodos de extensão, mas a implementação real do Linq está no .NET Framework.

Há algum açúcar sintático que foi adicionado ao C # para tornar as instruções do linq mais limpas, mas esse açúcar não é necessário para usar o linq.

Robert Harvey
fonte
11
Eu concordo com o ponto de que o LINQ não é um recurso de idioma, mas para fazer o LINQ acontecer, é necessário que haja alguns recursos de idioma subjacentes - particularmente tipos genéricos e dinâmicos.
Wyatt Barnett
@ WyattBarnett: Sim, e o mesmo vale para testes de unidade fáceis - como reflexão e atributos.
Doc Brown
8
O LINQ precisava de lambdas e árvores de expressão. Os genéricos já estavam em C # (e .Net em geral, estão presentes no CLR), e a dinâmica não tem nada a ver com LINQ; você pode fazer o LINQ sem dinâmico.
Arthur van Leeuwen
O DBIx :: Class do Perl é um exemplo de funcionalidade semelhante ao LINQ implementada mais de 10 anos após todos os recursos necessários para implementá-la estarem na linguagem.
Slebetman
2
@ Carson63000 Eu acho que você quer dizer tipos anônimos em combinação com a varpalavra-chave :)
M.Mimpen
21

Há muitas razões. Eric Lippert afirmou muitas vezes que o motivo feature Xnão está no C # é porque não está no orçamento. Os designers de idiomas não têm uma quantidade infinita de tempo nem dinheiro para implementar as coisas, e cada novo recurso tem custos de manutenção associados. Manter o idioma o mais pequeno possível não é apenas mais fácil para os designers de idiomas - também é mais fácil para quem escreve implementações e ferramentas alternativas (por exemplo, IDEs). Além disso, quando algo é implementado em termos do idioma, e não em parte, você obtém portabilidade gratuitamente. Se o teste de unidade estiver sendo implementado como uma biblioteca, você precisará escrevê-lo apenas uma vez e ele funcionará em qualquer implementação em conformidade da linguagem.

Vale a pena notar que D tem suporte no nível de sintaxe para testes de unidade . Não sei por que eles decidiram incluir isso, mas vale a pena notar que D deve ser uma "linguagem de programação de sistemas de alto nível". Os designers queriam que fosse viável para o tipo de código inseguro e de baixo nível C ++ tradicionalmente usado, e um erro no código inseguro é incrivelmente caro - comportamento indefinido. Então, suponho que fazia sentido para eles dedicar um esforço extra a qualquer coisa que o ajudasse a verificar se algum código não seguro funciona. Por exemplo, você pode impor que apenas determinados módulos confiáveis ​​possam executar operações inseguras, como acessos não verificados à matriz ou aritmética de ponteiros.

O desenvolvimento rápido também era uma prioridade para eles, tanto que eles criaram um objetivo de design que o código D compila com rapidez suficiente para torná-lo utilizável como uma linguagem de script. Testes de unidade de cozimento diretamente no idioma, para que você possa executar seus testes, basta passar um sinalizador extra para o compilador.

No entanto, acho que uma ótima biblioteca de testes de unidade faz muito mais do que apenas encontrar alguns métodos e executá-los. Veja o QuickCheck de Haskell, por exemplo, que permite testar coisas como "para todos os x e y f (x, y) == f (y, x)". O QuickCheck é melhor descrito como um gerador de teste de unidade e permite que você teste as coisas em um nível mais alto do que "para esta entrada, estou esperando esta saída". QuickCheck e Linq não são tão diferentes - ambos são idiomas específicos de domínio. Portanto, em vez de usar o suporte ao teste de unidade em um idioma, por que não adicionar os recursos necessários para tornar as DSLs práticas? Você acabará não apenas com testes de unidade, mas com um idioma melhor como resultado.

Doval
fonte
3
Para permanecer no mundo .Net, existe o FsCheck, que é uma porta do QuickCheck para F #, com alguns auxiliares para uso do C #.
Arthur van Leeuwen
Para ficar no mundo .NET? Esta pergunta não tem nada a ver com o .NET.
Route de milhas
@MilesRout C # faz parte do .NET. A pergunta e a resposta falam sobre C # com freqüência e também falam sobre LINQ.
Zachary Dow
A pergunta não está marcada como .NET ou C #.
Route de milhas
@MilesRout Isso pode ser verdade, mas você não pode dizer razoavelmente que ele não tem nada a ver com isso apenas porque não possui a tag. :)
Zachary Dow
13

Porque testar, e particularmente desenvolvimento orientado a testes, é um fenômeno profundamente contra-intuitivo.

Quase todo programador começa sua carreira acreditando que é muito melhor no gerenciamento da complexidade do que realmente é. O fato de nem mesmo o maior programador conseguir escrever programas grandes e complexos sem erros graves, a menos que eles usem muitos testes de regressão é seriamente decepcionante e até vergonhoso para muitos profissionais. Daí a desinclinação predominante contra testes regulares, mesmo entre profissionais que já deveriam conhecer melhor.

Eu acho que o fato de os testes religiosos estarem lentamente se tornando mais populares e esperados se deve em grande parte ao fato de que, com a explosão da capacidade de armazenamento e do poder de computação, sistemas maiores do que nunca estão sendo construídos - e sistemas extremamente grandes são particularmente propensos ao colapso da complexidade que não pode ser gerenciado sem a rede de segurança dos testes de regressão. Como resultado, mesmo os desenvolvedores particularmente obstinados e iludidos agora admitem, relutantemente, que precisam de testes e sempre precisarão de testes (se isso soa como a confissão em uma reunião de AA, isso é bastante intencional - é necessário admitir, para muitos, a necessidade de uma rede de segurança. indivíduos).

Atualmente, a maioria dos idiomas populares data de antes dessa mudança de atitude; portanto, eles têm pouco suporte interno para testes: eles têm assert, mas não têm contratos. Tenho certeza de que, se a tendência persistir, os idiomas futuros terão mais suporte no idioma do que no nível da biblioteca.

Kilian Foth
fonte
3
Você superestima um pouco o seu caso. Embora o teste de unidade seja inquestionavelmente de suma importância, a criação de programas da maneira correta , da maneira que minimiza a complexidade desnecessária, é igualmente importante, se não mais. Idiomas como Haskell têm sistemas de tipos que melhoram a confiabilidade e a provabilidade dos programas escritos neles, e as práticas recomendadas de codificação e design evitam muitas das armadilhas do código não testado, tornando o teste de unidade menos crítico do que seria.
Robert Harvey
11
@RobertHarve ... e o teste de unidade é uma maneira muito boa de empurrar indiretamente os desenvolvedores para isso. Trabalhar na API durante a criação de testes, em vez de usá-la com outro código, ajuda a colocá-los na mentalidade correta para limitar ou remover abstrações com vazamento ou chamadas de método ímpares. Não acho que o KillianFoth esteja exagerando em nada.
Izkata
@ Robert: A única coisa que eu acredito que ele exagera é "teste religiosa está lentamente se tornando mais mainstream" Se lentamente está definido em termos de prazos Geographic ele provavelmente não é muito longe ..... :)
mattnz
+1 No meu trabalho atual, estou impressionado com a mudança nas atitudes de desenvolvimento com as novas bibliotecas e tecnologias disponíveis. Pela primeira vez na minha carreira, esses caras estão me ensinando a usar um processo de desenvolvimento mais sofisticado, em vez de me sentir como se estivesse no topo de uma colina gritando com o vento. Concordo que é apenas uma questão de tempo até que essas atitudes encontrem expressão na sintaxe da língua nativa.
Rob
11
Desculpe, mais um pensamento: comentário de Robert Harvey. No momento, os sistemas de tipos são uma boa opção, mas realmente ficam aquém das restrições de definição de tipos - eles abordam a interface, mas não restringem o comportamento correto. (ou seja, 1 + 1 == 3 será compilado, mas pode ou não ser verdadeiro, dependendo da implementação de +). Gostaria de saber se a próxima tendência será ver sistemas de tipos mais expressivos que combinam o que consideramos um teste de unidade agora.
Rob
6

Muitos idiomas têm suporte para teste. As afirmações de C são testes de que o programa pode falhar. É aí que a maioria das linguagens para, mas Eiffel e, mais recentemente, o Ada 2012 têm pré-variantes (coisas que os argumentos de uma função devem passar) e pós-variantes (coisas que a saída de uma função deve passar), com a Ada oferecendo a capacidade de referenciar os argumentos iniciais no pós-variante. O Ada 2012 também oferece invariantes de tipo, portanto, sempre que um método é chamado em uma classe Ada, o invariante de tipo é verificado antes do retorno.

Esse não é o tipo de teste completo que uma boa estrutura de teste pode oferecer, mas é um tipo importante de teste que os idiomas podem suportar melhor.

prosfilaes
fonte
2
Tendo feito um pouco de trabalho em Eiffel e sendo um usuário extensivo de afirmações, minha experiência foi que tudo o que você pode fazer que seja de natureza contratual ajuda a descobrir muitos bugs.
Blrfl
2

Alguns defensores de linguagens funcionais fortemente tipificadas argumentariam que esses recursos de linguagem reduzem ou eliminam a necessidade de testes de unidade.

Dois, imho, um bom exemplo disso é o F # for Fun and Profit aqui e aqui

Pessoalmente, ainda acredito no valor dos testes de unidade, mas existem alguns pontos válidos. Por exemplo, se um estado ilegal é irrepresentável no código, não é apenas desnecessário escrever testes de unidade para este caso, mas também é impossível.

Pete
fonte
2

Eu diria que você perdeu a adição de alguns dos recursos necessários porque eles não estavam sendo destacados como nos testes de unidade .

Por exemplo, o teste de unidade em C # é direcionado principalmente pelo uso de atributos. O recurso Atributos personalizados fornece um mecanismo de extensão avançado que permite que estruturas como o NUnit iterem e competam, com coisas como testes baseados em teoria e parametrizados .

Isso me leva ao meu segundo ponto principal - não sabemos o suficiente sobre o que faz uma boa testabilidade para incorporá-lo a um idioma. O ritmo da inovação nos testes é muito mais rápido do que outras construções de linguagem, por isso precisamos ter mecanismos flexíveis em nossas línguas para deixar a liberdade de inovar.

Eu sou um usuário, mas não sou fanático por TDD - é muito útil em alguns casos, especialmente para melhorar seu design thinking. Não é necessariamente útil para sistemas legados maiores - testes de nível superior com bons dados e automação provavelmente trarão mais benefícios, juntamente com uma forte cultura de inspeção de código .

Outra leitura relevante:

Andy Dent
fonte