Você deve testar a ordem de operações? Se sim, como?

4

Imagine uma classe de código psedo como essa que nomeia comparações:

public class NameComparer
{
    public bool DoNamesMatch(Name name1, Name name2)
    {
        if(WholeNameMatch(name1, name2))
        {
            return true;
        }

        return ProcessorIntensiveFuzzyMatcher(name1.Forename, name2.Forename)
                && ProcessorIntensiveFuzzyMatcher(name1.Surname, name2.Surname)
    }

    private bool WholeNameMatch(Name name1, Name name2)
    {
        return (name1.Forename == name2.Forename)
                && (name1.Surname == name2.Surname);
    }

    private bool ProcessorIntensiveFuzzyMatcher(string string1, string string2)
    {
        // complex fuzzy matching
    }
}

A melhor prática sugere que essa classe deve ter apenas um método público e que devemos testar os métodos privados dessa classe através desse método público.

Embora eu possa escrever vários testes que garantam que WholeNameMatch e ProcessorIntensiveFuzzyMatcher façam o que devem fazer, a ordem de operação é importante aqui. Se um desenvolvedor se mover por engano um acima do outro, o programa terá um grande impacto no desempenho.

É desejável - e possível - testar esta ordem de operações através do método público exposto?

    
por Matt Thrower 09.11.2017 / 18:23
fonte

4 respostas

10

A premissa da sua pergunta está incorreta. Você não quer testar que primeiro WholeNameMatch é chamado e, em seguida, ProcessorIntensiveFuzzyMatcher é chamado.

Você quer testar

GIVEN WholeNameMatch returns true
WHEN NamesMatch is called
THEN ProcessorIntensiveFuzzyMatch is not called

Isso é feito facilmente se a correspondência difusa for feita por uma dependência injetada que é ridicularizada pelo teste de unidade. A maioria dos frameworks de simulação permite que você verifique se uma chamada não ocorreu.

Se o fuzzy matcher não for uma dependência injetada, deve ser.

    
por 09.11.2017 / 19:04
fonte
6

@KevinCline deu uma excelente resposta, mas eu gostaria de acrescentar algo que se pode fazer quando injetar o correspondente fuzzy não parece valer a pena, e resolver as coisas de uma maneira mais simples.

É possível simplesmente adicionar uma propriedade pública de leitura booleana LastMatchWasFuzzy a NameComparer e inicializá-la desta maneira:

public bool DoNamesMatch(Name name1, Name name2)
{
    LastMatchWasFuzzy=false;
    if(WholeNameMatch(name1, name2))
    {
        return true;
    }

    LastMatchWasFuzzy = ProcessorIntensiveFuzzyMatcher(name1.Forename, name2.Forename)
                     && ProcessorIntensiveFuzzyMatcher(name1.Surname, name2.Surname);
    return LastMatchWasFuzzy;
}

Isso dá ao chamador uma chance de testar como a última correspondência foi alcançada, o que pode ser benéfico não apenas para um teste de unidade, mas também para outros casos de uso.

Em geral, às vezes não é a pior estratégia adicionar algumas "hachuras de manutenção" a uma classe, o que significa propriedades ou funções apenas para fins de teste (no entanto, isso é sempre uma compensação; elas não devem poluir a API pública de uma forma perturbadora).

    
por 10.11.2017 / 11:58
fonte
5

Best practice suggests that this class only ought to have one public method, and that we ought to unit test the private methods in this class through that public method.

A prática recomendada sugere que os testes não se importam com detalhes de implementação, como métodos privados. Os métodos públicos são uma maneira de expor a funcionalidade para o mundo externo, não uma maneira de testar métodos privados.

While I can write various tests that ensure WholeNameMatch and ProcessorIntensiveFuzzyMatcher both do what they're supposed to do,

Talvez você possa, mas não é isso que você quer. Você quer garantir que a interface pública faça o que deve fazer. Como isso não é relevante para os clientes de sua classe e não deve ser relevante para seus testes.

the order of operation is important here. If a developer mistakenly moves one above the other, the program is going to take a massive performance hit.

A ordem das operações privadas não faz sentido para os clientes da sua classe, incluindo os testes, porque eles nem sequer sabem que essas operações existem, muito menos em que ordem eles são executados. Se o desempenho for importante, teste o desempenho. Alternativamente, siga o conselho de kevin sobre o uso de injeção de dependência e toda a questão sobre métodos privados desaparecerá.

Is it desirable - and possible - to test this order of operations via the exposed public method?

Qualquer coisa sobre métodos privados são detalhes de implementação. Se você testar os detalhes da implementação e depois alterar os detalhes da implementação, seus testes falharão. Isso não é desejável, desde que a interface pública forneça a mesma funcionalidade que seus testes não devem quebrar.

    
por 09.11.2017 / 21:52
fonte
1

Não.

Os testes de unidade devem permitir refatorar ou reescrever o código com segurança, mostrando que tudo ainda funciona como deveria. Mas se o código de teste estiver acoplado aos detalhes de implementação da unidade, você terá que modificar o teste ao mesmo tempo quando alterar o código. O que significa que você pode facilmente introduzir erros, e todo o benefício dos testes de unidade é perdido.

Em suma, os testes unitários devem verificar se os requisitos da unidade são atendidos, mas não se importam com os detalhes da implementação.

É por isso que é uma má prática testar métodos privados. Métodos privados devem ser detalhes de implementação. O mesmo vale para verificar se um método privado é chamado ou não, ou em que ordem.

No seu caso, o comportamento esperado é algum nível de correspondência difusa. Qual algoritmo é usado e se é implementado em um ou mais métodos é um detalhe de implementação.

Imagine que você encontre um algoritmo de correspondência fuzzy melhor que tenha a propriedade de ser tão rápido ou mais rápido quanto uma correspondência normal de string quando houver uma correspondência exata. Isso permite que você pule a caixa especial da correspondência exata. Essa seria uma melhoria que preservaria o comportamento esperado - mas isso faria com que seu teste falhasse! O teste impede você de melhorar o código.

Ou você pode perceber que, se Forename tiver uma correspondência exata, mas Surname não, então não há motivo para realizar uma correspondência fuzzy on Forename novamente. Mas, para evitar isso, será necessária uma reestruturação da ordem das chamadas de método, portanto, muito provavelmente, o teste impedirá essa alteração novamente, embora o comportamento permaneça correto O desempenho e foi aprimorado. Então o teste tem valor negativo.

Mas eu entendo que você quer garantir que comparar strings que são totalmente iguais não seja significativamente mais lento devido ao algoritmo fuzzy. Eu vejo duas possibilidades:

  • o benefício de desempenho para essa otimização é insignificante
  • o benefício de desempenho é significativo

Se for insignificante, você não deve perder tempo em testá-lo. Se for significativo, você deve testá-lo, mas você deve testar o desempenho real (ciclos de tempo ou processador), e não detalhes de implementação irrelevantes.

    
por 10.11.2017 / 14:27
fonte