unit-testing - tutorial - nunit c#




Como faço para testar uma função privada ou uma classe que tenha métodos privados, campos ou classes internas? (20)

Como faço para testar unidade (usando xUnit) uma classe que tem métodos privados internos, campos ou classes aninhadas? Ou uma função que é feita privada por ter ligação interna ( static em C / C ++) ou está em um namespace privado ( anonymous )?

Parece ruim alterar o modificador de acesso para um método ou função apenas para poder executar um teste.


A melhor maneira de testar um método privado é por meio de outro método público. Se isso não puder ser feito, uma das seguintes condições será verdadeira:

  1. O método privado é código morto
  2. Há um cheiro de design perto da classe que você está testando
  3. O método que você está tentando testar não deve ser privado

A resposta da página de perguntas frequentes da JUnit.org :

Mas se você deve ...

Se você estiver usando o JDK 1.3 ou superior, poderá usar a reflexão para subverter o mecanismo de controle de acesso com o auxílio do PrivilegedAccessor . Para detalhes sobre como usá-lo, leia este artigo .

Se você estiver usando o JDK 1.6 ou superior e anotar seus testes com o @Test, poderá usar o Dp4j para injetar reflexos em seus métodos de teste. Para detalhes sobre como usá-lo, veja este script de teste .

PS eu sou o principal contribuinte para Dp4j , pergunte- me se você precisar de ajuda. :)


Como outros já disseram ... não teste diretamente métodos privados. Aqui estão alguns pensamentos:

  1. Mantenha todos os métodos pequenos e focados (fácil de testar, fácil de encontrar o que está errado)
  2. Use ferramentas de cobertura de código. Eu gosto de Cobertura (oh dia feliz, parece que uma nova versão está fora!)

Execute a cobertura de código nos testes de unidade. Se você ver que os métodos não são totalmente testados, adicione aos testes para obter a cobertura. Apontar para 100% de cobertura de código, mas perceba que você provavelmente não conseguirá.



Eu sugiro que você refatorie seu código um pouco. Quando você precisa começar a pensar em usar reflexão ou outro tipo de coisa, apenas para testar seu código, algo está errado com seu código.

Você mencionou diferentes tipos de problemas. Vamos começar com campos privados. No caso de campos privados, eu adicionaria um novo construtor e injetaria campos nele. Em vez disso:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

Eu teria usado isso:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

Isso não será um problema, mesmo com algum código legado. O código antigo estará usando um construtor vazio, e se você me perguntar, o código refatorado ficará mais limpo, e você poderá injetar os valores necessários no teste sem reflexão.

Agora sobre métodos privados. Na minha experiência pessoal quando você tem que stub um método privado para testar, então esse método não tem nada a ver nessa classe. Um padrão comum, nesse caso, seria envolvê- lo dentro de uma interface, como Callable e, em seguida, você passa nessa interface também no construtor (com esse truque de construtor múltiplo):

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

Principalmente tudo o que escrevi parece ser um padrão de injeção de dependência. Na minha experiência pessoal, é realmente útil durante o teste, e acho que esse tipo de código é mais limpo e mais fácil de manter. Eu diria o mesmo sobre classes aninhadas. Se uma classe aninhada contivesse lógica pesada, seria melhor se você a movesse como uma classe privada de pacote e a tivesse injetado em uma classe que precisasse dela.

Há também vários outros padrões de design que usei ao refatorar e manter código herdado, mas tudo depende de casos de seu código para teste. O uso de reflexos não é um problema, mas quando você tem um aplicativo corporativo que é altamente testado e os testes são executados antes de cada implementação, tudo fica muito lento (é irritante e eu não gosto desse tipo de coisa).

Há também injeção setter, mas eu não recomendaria usá-lo. É melhor eu ficar com um construtor e inicializar tudo quando for realmente necessário, deixando a possibilidade de injetar as dependências necessárias.


Eu usei a reflection para fazer isso para Java no passado e, na minha opinião, foi um grande erro.

Estritamente falando, você não deveria estar escrevendo testes de unidade que testam diretamente métodos privados. O que você deve testar é o contrato público que a classe tem com outros objetos; você nunca deve testar diretamente os componentes internos de um objeto. Se outro desenvolvedor quiser fazer uma pequena alteração interna na classe, o que não afeta o contrato público da turma, ele precisará modificar seu teste baseado em reflexo para garantir que ele funcione. Se você fizer isso repetidamente ao longo de um projeto, os testes de unidade deixarão de ser uma medida útil da integridade do código e se tornarão um obstáculo ao desenvolvimento e um incômodo para a equipe de desenvolvimento.

O que eu recomendo é usar uma ferramenta de cobertura de código, como Cobertura, para garantir que os testes de unidade que você escreve forneçam uma cobertura decente do código em métodos privados. Dessa forma, você indiretamente testa o que os métodos privados estão fazendo e mantém um nível mais alto de agilidade.


Métodos privados são consumidos pelos públicos. Caso contrário, eles são código morto. É por isso que você testa o método público, afirmando os resultados esperados do método público e, portanto, os métodos privados que consome.

Testar métodos privados deve ser testado por depuração antes de executar seus testes de unidade em métodos públicos.

Eles também podem ser depurados usando o desenvolvimento orientado a testes, depurando seus testes de unidade até que todas as suas asserções sejam atendidas.

Eu pessoalmente acredito que é melhor criar classes usando o TDD; criando os stubs do método público e gerando testes de unidade com todas as asserções definidas antecipadamente, de forma que o resultado esperado do método seja determinado antes de ser codificado. Dessa forma, você não segue o caminho errado de fazer com que as asserções de teste de unidade se ajustem aos resultados. Sua turma é robusta e atende aos requisitos quando todos os seus testes de unidade passarem.


No Spring Framework, você pode testar métodos privados usando este método:

ReflectionTestUtils.invokeMethod()

Por exemplo:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

Os métodos privados são chamados por um método público, portanto, as entradas para seus métodos públicos também devem testar métodos privados que são chamados por esses métodos públicos. Quando um método público falha, isso pode ser uma falha no método privado.


Outra abordagem que usei é alterar um método privado para o pacote privado ou protegido e complementá-lo com a anotação @VisibleForTesting da biblioteca do Google Guava.

Isso dirá a qualquer pessoa que usar esse método para tomar cuidado e não acessá-lo diretamente, mesmo em um pacote. Além disso, uma classe de teste não precisa estar no mesmo pacote fisicamente , mas no mesmo pacote na pasta de teste .

Por exemplo, se um método a ser testado estiver em src/main/java/mypackage/MyClass.java , sua chamada de teste deverá ser colocada em src/test/java/mypackage/MyClassTest.java . Dessa forma, você obteve acesso ao método de teste em sua classe de teste.


Quando eu tenho métodos privados em uma classe que são suficientemente complicados, sinto a necessidade de testar os métodos privados diretamente, isso é um cheiro de código: minha classe é muito complicada.

Minha abordagem usual para abordar essas questões é desvendar uma nova classe que contém os bits interessantes. Muitas vezes, esse método e os campos com os quais ele interage, e talvez outro método ou dois podem ser extraídos para uma nova classe.

A nova classe expõe esses métodos como 'públicos', então eles são acessíveis para testes de unidade. As classes novas e antigas agora são mais simples que a original, o que é ótimo para mim (eu preciso manter as coisas simples, ou me perco!).

Note que não estou sugerindo que as pessoas criem aulas sem usar o cérebro delas! O ponto aqui é usar as forças do teste de unidade para ajudá-lo a encontrar boas novas classes.


Se estiver usando o Spring, o ReflectionTestUtils fornece algumas ferramentas úteis que ajudam aqui com o mínimo de esforço. Por exemplo, para configurar um mock em um membro privado sem ser forçado a adicionar um setter público indesejável:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

Se você estiver usando o JUnit, dê uma olhada nos junit-addons . Ele tem a capacidade de ignorar o modelo de segurança Java e acessar métodos e atributos privados.


Se você quiser testar métodos privados de um aplicativo legado em que não é possível alterar o código, uma opção para Java é o jMockit , que permitirá criar mocks para um objeto, mesmo quando eles forem privados para a classe.


Tendo tentado a solução de Cem Catikkas usando a reflexão para Java, eu teria que dizer que a solução dele era mais elegante do que a que descrevi aqui. No entanto, se você estiver procurando uma alternativa ao uso de reflexão e tiver acesso à fonte que está testando, essa ainda será uma opção.

Existe o mérito possível em testar métodos privados de uma classe, particularmente com desenvolvimento orientado a testes , onde você gostaria de criar pequenos testes antes de escrever qualquer código.

A criação de um teste com acesso a membros e métodos privados pode testar áreas de código que são difíceis de segmentar especificamente com acesso apenas a métodos públicos. Se um método público tiver várias etapas envolvidas, ele poderá consistir em vários métodos privados, que poderão ser testados individualmente.

Vantagens:

  • Pode testar uma granularidade mais fina

Desvantagens:

  • O código de teste deve residir no mesmo arquivo que o código-fonte, o que pode ser mais difícil de manter
  • Da mesma forma com arquivos de saída .class, eles devem permanecer dentro do mesmo pacote declarado no código-fonte

No entanto, se o teste contínuo exigir esse método, pode ser um sinal de que os métodos privados devem ser extraídos, o que poderia ser testado da maneira tradicional e pública.

Aqui está um exemplo complicado de como isso funcionaria:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

A classe interna seria compilada para ClassToTest$StaticInnerTest .

Veja também: Java Dica 106: classes internas estáticas para diversão e lucro


Um método privado deve ser acessado apenas na mesma classe. Portanto, não há como testar um método "particular" de uma classe de destino de qualquer classe de teste. Uma saída é que você pode realizar testes unitários manualmente ou alterar seu método de “privado” para “protegido”.

E então um método protegido só pode ser acessado dentro do mesmo pacote onde a classe é definida. Portanto, testar um método protegido de uma classe de destino significa que precisamos definir sua classe de teste no mesmo pacote da classe de destino.

Se todos os itens acima não atenderem às suas necessidades, use o modo de reflexão para acessar o método privado.


Como muitos sugeriram acima, uma boa maneira é testá-los por meio de suas interfaces públicas.

Se você fizer isso, é uma boa ideia usar uma ferramenta de cobertura de código (como Emma) para ver se seus métodos privados estão sendo executados em seus testes.


Por favor, veja abaixo um exemplo;

A seguinte declaração de importação deve ser adicionada:

Whitebox.invokeMethod(obj, "privateMethod", "param1");

Agora você pode passar diretamente o objeto que tem o método privado, o nome do método a ser chamado e os parâmetros adicionais, conforme abaixo.

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

Para Java, eu usaria a reflection , já que não gosto da idéia de alterar o acesso a um pacote no método declarado apenas por uma questão de teste. No entanto, eu normalmente apenas testo os métodos públicos que também devem garantir que os métodos privados estejam funcionando corretamente.

você não pode usar o reflexo para obter métodos privados de fora da classe proprietária, o modificador privado afeta a reflexão também

Isso não é verdade. Você certamente pode, como mencionado na resposta de Cem Catikkas .


Primeiro, vou fazer essa pergunta: Por que seus membros particulares precisam de testes isolados? Eles são tão complexos, fornecendo comportamentos tão complicados que exigem testes além da superfície pública? É o teste de unidade, não o teste de 'linha de código'. Não se preocupe com as pequenas coisas.

Se eles são tão grandes, tão grandes que esses membros privados são, cada um, uma 'unidade' grande em complexidade - considere a possibilidade de refatorar esses membros privados para fora dessa classe.

Se a refatoração é inadequada ou inviável, você pode usar o padrão de estratégia para substituir o acesso a essas funções de membros / classes de membros privados quando estiver sob teste de unidade? No teste de unidade, a estratégia forneceria validação adicional, mas, em versões de lançamento, seria um repasse simples.





tdd