Um teste de unidade é considerado frágil se falhar quando a lógica de negócios mudar?

27

Por favor veja o código abaixo; testa para ver se uma pessoa com Sexo feminino é elegível para a oferta1:

[Fact]
public void ReturnsFalseWhenGivenAPersonWithAGenderOfFemale()
{
    var personId = Guid.NewGuid();
    var gender = "F";
    var person = new Person(personId, gender);

    var id = Guid.NewGuid();
    var offer1 = new Offer1(id,"Offer1");
    Assert.False(offer1.IsEligible(person));
}

Este teste de unidade foi bem-sucedido. No entanto, falhará se a 'Oferta1' for oferecida às fêmeas no futuro.

É aceitável dizer - se a lógica de negócios em torno da oferta 1 for alterada, o teste de unidade deverá ser alterado. Observe que em alguns casos (para algumas ofertas) a lógica de negócios é alterada no banco de dados da seguinte maneira:

update Offers set Gender='M' where offer=1;

e, em alguns casos, no modelo de domínio como este:

if (Gender=Gender.Male)
{
  //do something
}

Observe também que, em alguns casos, a lógica do domínio por trás oferece alterações regularmente e, em alguns casos, não.

w0051977
fonte
2
Pense de outro ângulo: você deseja ter testes que não falharam quando você altera a lógica do sistema em teste?
Fabio

Respostas:

77

Isso não é frágil no sentido usual. Um teste de unidade é considerado frágil se quebrar devido a alterações na implementação que não afetam o comportamento em teste. Mas se a própria lógica de negócios mudar, um teste dessa lógica deve ser interrompido.

Dito isto, se a lógica de negócios realmente muda com frequência, talvez não seja apropriado codificar as expectativas nos testes de unidade. Em vez disso, você pode testar se as configurações no banco de dados afetam as ofertas conforme o esperado.

O nome do teste Returns False When Given A Person With A Gender Of Femalenão descreve uma regra de negócios. Uma regra de negócios seria algo parecido Offers Applicable to M should not be applied to persons of gender F.

Portanto, você pode escrever um teste que confirme que, se uma oferta for definida como aplicável apenas a pessoas do tipo M, uma pessoa do tipo F não será indicada como elegível para ela. Este teste garantirá que a lógica funcione mesmo que a configuração das ofertas específicas seja alterada.

JacquesB
fonte
@ JaquesB, então não seria um teste de unidade? ou seria? Acredito que seria um teste de integração se o banco de dados estivesse envolvido. Isso esta certo? Você está dizendo que não use testes de unidade se a lógica de negócios mudar muito?
W0051977
@ w0051977: Depende de como você escreve o teste. Se o teste incluir realmente alterar a alteração de algo em um banco de dados, seria um teste de integração.
JacquesB
3
@ w0051977 melhor idéia - não faça com que o repositório seja uma dependência do componente responsável pela implementação das regras de negócios. Tenha uma orquestração de nível superior que chame o repositório e invoque as regras de negócios. Agora você pode testar as regras de negócios isoladamente.
Ant P
5
@ w0051977 é claro que é - testes especificam comportamento. Se as regras que regem o comportamento de um componente forem alteradas, os testes deverão ser alterados para refletir a mudança de comportamento. O que não precisa mudar são testes que especificam comportamentos diferentes do que está mudando. Se o comportamento for determinado pelo banco de dados, um teste que cubra algum outro código não é inerentemente relacionado e não deve ser alterado, a menos que a lógica do banco de dados esteja dentro do escopo do teste. Esse escopo é para você definir e a semântica de se é um teste de unidade ou de integração não é realmente importante.
Ant P
3
@ w0051977 estendendo um pouco essa ideia, se a lógica de negócios mudar ou se um bug for corrigido e os testes não precisarem ser ajustados, é um sinal de que os testes não estão cobrindo casos suficientes e geralmente devem ser expandidos.
Morgen
14

Quando a propriedade é definida no banco de dados de produção (ou em um clone para teste), este não é um teste de unidade . Um teste de unidade verifica uma unidade de trabalho e não requer um estado externo específico para funcionar. Isso pressupõe que Offer1está definido no banco de dados como uma oferta apenas para homens. Esse é o estado externo. Portanto, este é mais um teste de integração , especificamente um sistema ou teste de aceitação . Observe que os testes de aceitação geralmente não são com script (não são executados em uma estrutura de teste, mas são executados manualmente por seres humanos).

Quando a propriedade é definida no modelo de domínio com uma ifinstrução, o mesmo teste é um teste de unidade. E pode ser quebradiço. Mas o verdadeiro problema é que o código é quebradiço. Como regra geral, seu código será mais resiliente se o comportamento dos negócios for configurável em vez de codificado. Porque uma implantação rápida para corrigir um pequeno erro de codificação deve ser rara. Mas um requisito comercial que muda sem aviso prévio é apenas uma terça-feira (algo que acontece semanalmente).

Você pode estar usando uma estrutura de teste de unidade para executar o teste. Mas as estruturas de teste de unidade não se limitam à execução de testes de unidade. Eles também podem executar testes de integração.

Se você estivesse escrevendo um teste de unidade, criaria ambos persone offer1desde o início, sem depender do estado do banco de dados. Algo como

[Fact]
public void ReturnsFalseWhenGivenAPersonWithAGenderOfFemale()
{
    var personId = Guid.NewGuid();
    var gender = "F";
    var person = new Person(personId, gender);

    var id = Guid.NewGuid();
    var offer1 = new Offer1(id, "ReturnsFalseWhenGivenAPersonWithAGenderOfFemale");
    offer1.markLimitedToGender("M");

    Assert.False(offer1.IsEligible(person));
}

Observe que isso não muda com base na lógica de negócios. Não é afirmar que offer1rejeita mulheres. Está fazendo offer1o tipo de oferta que rejeita mulheres.

Você pode criar e configurar o banco de dados como parte do teste. No C #, usando NUnit, ou no JUnit de Java, você configuraria o banco de dados em um Setupmétodo. Presumivelmente, sua estrutura de teste tem uma noção semelhante. Nesse método, você pode inserir registros no banco de dados com SQL.

Se for difícil escrever código que substitua um banco de dados de teste pelo banco de dados de produção, isso soa como uma fraqueza de teste no seu aplicativo. Para o teste, seria melhor usar algo como injeção de dependência que permita a substituição. Em seguida, você pode escrever testes independentes das regras de negócios atuais.

Um benefício colateral disso é que muitas vezes é mais fácil para o proprietário da empresa (não necessariamente o proprietário da empresa, mais parecido com a pessoa responsável por este produto na hierarquia corporativa) configurar as regras de negócios diretamente. Como se você possui esse tipo de estrutura técnica, é fácil permitir que o proprietário da empresa use uma interface de usuário (UI) para configurar a oferta. O proprietário da empresa selecionaria a limitação na interface do usuário e emitia a markLimitedToGender("M")chamada. Então, quando a oferta persistir no banco de dados, ela será armazenada. Mas você não precisaria armazenar a oferta para usá-la. Portanto, seus testes podem criar e configurar uma oferta que não existe no banco de dados.

No sistema, conforme descrito, o proprietário da empresa precisaria fazer uma solicitação ao grupo técnico, que emitisse o SQL apropriado e atualizaria os testes. Ou o grupo técnico precisa editar seu código e testes (ou testes e depois código). Essa parece uma abordagem bastante pesada. Você consegue. Mas o seu software (não apenas o seu teste) seria menos quebradiço se você não precisasse fazer isso.

TL; DR : você pode escrever testes como este, mas é melhor escrever seu software para não precisar fazer isso.

mdfst13
fonte
Eu uso a liberdade de melhorar alguns detalhes menores em sua resposta. Por favor, verifique se tenho suas intenções certas.
Doc Brown
Não há indicação na postagem original de que um banco de dados esteja envolvido. Afirmar que supõe que a Offer1 já esteja no banco de dados é bizarro.
Winston Ewert
2
@WinstonEwert: há uma indicação clara, você deve ler a pergunta com mais cuidado. Também não percebi isso na primeira leitura, mas é de fato o que o OP está falando.
Doc Brown
@ mdfst13, desculpe, eu perdi isso. No entanto, o que o OP está dizendo é que às vezes as condições estão em um banco de dados e outras no modelo de domínio. Sua resposta está perfeitamente correta no primeiro caso e irreverentemente no segundo. Se você editar sua resposta para esclarecer esse ponto, removerei meu voto apressado.
Winston Ewert