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?


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.


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.


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.


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?


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.


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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.