Programação Baseada em Contrato vs Teste de Unidade

13

Sou um programador um tanto defensivo e um grande fã dos contratos de código da Microsofts.

Agora nem sempre posso usar C # e, na maioria dos idiomas, a única ferramenta que tenho são as asserções. Então, geralmente acabo com um código como este:

class
{       
    function()
    {   
         checkInvariants();
         assert(/* requirement */);

         try
         {
             /* implementation */
         }
         catch(...)
         {
              assert(/* exceptional ensures */);                  
         }
         finally
         {
              assert(/* ensures */);
              checkInvariants();
         }
    }

    void checkInvariants()
    {
         assert(/* invariant */);
    }
}

No entanto, esse paradigma (ou o que você chamaria) leva a muita confusão de código.

Comecei a me perguntar se realmente vale a pena o esforço e se o teste de unidade adequado já cobriria isso?

ronag
fonte
6
Embora os testes de unidade permitam mover asserções para fora do código do aplicativo (e, portanto, evitar a confusão), considere que eles não podem verificar tudo o que pode acontecer no sistema de produção real. Portanto, os contratos de código IMO têm algumas vantagens, especialmente no código crítico, onde a correção é particularmente importante.
Giorgio
Então é basicamente tempo de desenvolvimento, capacidade de manutenção, legibilidade versus melhor cobertura de código?
ronag
1
Estou principalmente usando asserção no código para verificar a correção dos parâmetros (em nulo, por exemplo) e no teste de unidade, adicionalmente, verificar essas asserções.
Artjom

Respostas:

14

Eu não acho que você deva pensar nisso como "vs".
Conforme mencionado nos comentários de @Giorgio, os contratos de código devem verificar os invariantes (no ambiente de produção) e os testes de unidade para garantir que o código funcione conforme o esperado quando essas condições forem atendidas.

duros
fonte
2
Eu acho que também é importante testar se o código funciona (por exemplo, lança uma exceção) quando as condições não são atendidas.
svick
6

Os contratos ajudam você a pelo menos uma coisa que os testes de unidade não fazem. Quando você está desenvolvendo uma API pública, não pode testar a unidade como as pessoas usam seu código. No entanto, você ainda pode definir contratos para seus métodos.

Pessoalmente, eu seria rigoroso quanto a contratos somente ao lidar com a API pública de um módulo. Em muitos outros casos, provavelmente não vale o esforço (e você pode usar testes de unidade), mas essa é apenas a minha opinião.

Isso não significa que eu aconselho a não pensar em contratos nesses casos. Eu sempre penso neles. Eu apenas acho que não é necessário sempre codificá-los explicitamente.

Honza Brabec
fonte
1

Como já mencionado, os contratos e testes de unidade têm uma finalidade diferente.

Os contratos são sobre programação defensiva para garantir que os pré-requisitos sejam atendidos, o código é chamado com os parâmetros corretos, etc.

Testes de unidade para garantir que o código funcione em diferentes cenários. São como 'especificações com dentes'.

Afirmações são boas, elas tornam o código robusto. No entanto, se você estiver preocupado com a adição de muito código, também poderá adicionar pontos de interrupção condicionais em alguns locais durante a depuração e reduzir a contagem de afirmações.

Sajad Deyargaroo
fonte
0

Tudo o que você tem em suas chamadas checkVariants () pode ser feito a partir do teste, quanto esforço pode realmente depender de muitas coisas (dependências externas, níveis de acoplamento etc.), mas isso limparia o código de um ponto de vista. Quão testável seria uma base de código desenvolvida contra afirmações sem alguma refatoração, não tenho certeza.

Concordo com o @duros, não deve ser considerado como abordagens exclusivas ou concorrentes. De fato, em um ambiente TDD, você pode até argumentar que as asserções de 'requisito' seriam necessárias para testes :).

As declarações, no entanto, NÃO tornam o código mais robusto, a menos que você realmente faça algo para corrigir a verificação com falha; elas apenas impedem que os dados sejam corrompidos ou similares, geralmente interrompendo o processamento ao primeiro sinal de problema.

Uma solução testada / bem testada normalmente já pensou e / ou descobriu muitas das fontes / razões de entradas e saídas ruins, enquanto desenvolvia os componentes em interação e já lidava com eles mais perto da fonte do problema.

Se sua fonte é externa e você não tem controle sobre ela, para evitar sobrecarregar seu código e lidar com outros problemas de código, considere implementar algum tipo de componente de limpeza / asserção de dados entre a fonte e seu componente e faça suas verificações. .

Também estou curioso para saber quais idiomas o seu uso que não tem algum tipo de xUnit ou outra biblioteca de testes que alguém desenvolveu, eu pensei que havia praticamente algo para tudo nos dias de hoje?

Chris Lee
fonte
0

Além de testes de unidade e contratos de código, pensei em destacar outro aspecto, que é o valor na definição de suas interfaces, para que você elimine ou reduza a possibilidade de chamar seu código incorretamente.

Nem sempre é fácil ou possível, mas definitivamente vale a pena se perguntar: "Posso tornar esse código mais infalível?"

Anders Hejlsberg, criador do C #, disse que um dos maiores erros no C # não era incluir tipos de referência não nulos. É uma das principais razões pelas quais existe tanta confusão necessária no código de guarda.

Ainda assim, a refatoração para ter apenas a quantidade necessária e suficiente de código de proteção torna, na minha experiência, um código mais utilizável e sustentável.

Concorde com @duros no restante.

James World
fonte
0

Faça as duas coisas, mas faça alguns métodos estáticos auxiliares para esclarecer suas intenções. Isto é o que o Google fez para Java, confira code.google.com/p/guava-libraries/wiki/PreconditionsExplained

Alexander Torstling
fonte