Método privado de teste de unidade em c ++ usando uma classe de amigo

15

Sei que essa é uma prática debatida, mas vamos supor que essa seja a melhor opção para mim. Eu estou pensando sobre qual é a técnica real para fazer isso. A abordagem que vejo é a seguinte:

1) Faça a classe de um amigo a classe que é o método que eu quero testar.

2) Na classe friend, crie um método público que chame o método privado da classe testada.

3) Teste os métodos públicos da classe de amigos.

Aqui está um exemplo simples para ilustrar as etapas acima:

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

Editar:

Vejo que, na discussão a seguir, uma das respostas que as pessoas estão se perguntando sobre minha base de código.

Minha classe possui métodos chamados por outros métodos; nenhum desses métodos deve ser chamado fora da classe, portanto deve ser privado. É claro que eles poderiam ser colocados em um método, mas logicamente eles são muito melhores separados. Esses métodos são complicados o suficiente para justificar o teste de unidade e, devido a problemas de desempenho, provavelmente terei que refazer esses métodos; portanto, seria bom fazer um teste para garantir que minha re-fatoração não quebrasse nada. Não sou o único que trabalha na equipe, embora seja o único que está trabalhando neste projeto, incluindo os testes.

Dito isso, minha pergunta não era sobre se é uma boa prática escrever testes de unidade para métodos privados, embora eu aprecie o feedback.

Akavall
fonte
5
Aqui está uma sugestão questionável para uma pergunta questionável. Eu não gosto do acoplamento de amigo, pois o código liberado precisa saber sobre o teste. A resposta de Nir abaixo é uma maneira de aliviar isso, mas ainda não gosto muito de mudar de classe para se adequar ao teste. Como não confio na herança frequentemente, às vezes apenas protejo os métodos privados, e tenho uma classe de teste que herda e expõe conforme necessário. Espero pelo menos três "vaias" por esse comentário, mas a realidade é que a API pública e a API de teste podem diferir e ainda ser distintas da API privada. Meh.
J Trana #
4
@JTrana: Por que não escrever isso como uma resposta adequada?
Bart van Ingen Schenau
Está bem. Este não é um daqueles sobre os quais você se sente super orgulhoso, mas espero que ajude.
J Trana #

Respostas:

23

Uma alternativa ao amigo (bem, em certo sentido) que eu uso com frequência é um padrão que eu conheci como access_by. É bem simples:

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

Agora, suponha que a classe B esteja envolvida no teste A. Você pode escrever isto:

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

Você pode usar essa especialização de access_by para chamar métodos privados de A. Basicamente, o que isso faz é colocar o ônus de declarar amizade no arquivo de cabeçalho da classe que deseja chamar os métodos privados de A. Também permite adicionar amigos a A sem alterar a fonte de A. Linguisticamente, também indica para quem lê a fonte de A que A não indica B como um verdadeiro amigo no sentido de estender sua interface. Em vez disso, a interface de A é completa, conforme determinado, e B precisa de acesso especial a A (o teste é um bom exemplo, eu também usei esse padrão ao implementar ligações de python de aumento, às vezes uma função que precisa ser privada em C ++ é útil para expor na camada python para a implementação).

Nir Friedman
fonte
Curioso sobre um caso de uso válido para tornar o friend access_by, não é o primeiro não amigo suficiente - sendo uma estrutura aninhada, ele teria acesso a tudo em A? por exemplo. coliru.stacked-crooked.com/a/663dd17ed2acd7a3
tangy
10

Se é difícil de testar, está mal escrito

Se você tem uma classe com métodos privados complexos o suficiente para justificar seu próprio teste, a classe está fazendo muito. Dentro, há outra turma tentando sair.

Extraia os métodos particulares que você deseja testar em uma nova classe (ou classes) e torne-os públicos. Teste as novas classes.

Além de tornar o código mais fácil de testar, essa refatoração tornará o código mais fácil de entender e manter.

Kevin Cline
fonte
1
Concordo plenamente com esta resposta, se você não puder testar completamente seus métodos privados testando os métodos públicos, algo não está certo e refatorar os métodos privados para sua própria classe seria uma boa solução.
precisa saber é o seguinte
4
Na minha base de código, uma classe possui um método muito complexo que inicializa um gráfico computacional. Ele chama várias subfunções em sequência para alcançar vários aspectos disso. Cada subfunção é bastante complicada e a soma total do código é muito complicada. No entanto, as subfunções não têm sentido se não forem chamadas nessa classe e na sequência correta. Tudo o que o usuário se importa é que o gráfico computacional esteja sendo totalmente inicializado; os intermediários são inúteis para o usuário. Por favor, gostaria de saber como devo refatorar isso e por que faz mais sentido do que apenas testar os métodos privados.
Nir Friedman
1
@Nir: Faça o trivial: extraia uma classe com todos esses métodos públicos e faça da sua classe existente uma fachada em torno da nova classe.
Kevin cline #
Esta resposta está correta, mas realmente depende dos dados com os quais você está trabalhando. No meu caso, não me são fornecidos dados de teste reais, por isso preciso criar alguns observando dados em tempo real e, em seguida, "injetar" no meu aplicativo e ver como Um único dado é complexo demais para lidar, portanto, seria muito mais fácil criar artificialmente dados parciais de teste que são apenas um subconjunto dos dados em tempo real para segmentar cada um deles, além de reproduzir os dados em tempo real. A função privada não é complexa suficiente para ligar para implementação de vários outros classe pequena (com a respectiva funcionalidade)
rbaleksandar
4

Você não deveria estar testando métodos privados. Período. As classes que usam sua classe se preocupam apenas com os métodos que ela fornece, não com os métodos usados ​​para funcionar.

Se você está preocupado com a cobertura do código, precisa encontrar configurações que permitam testar esse método privado a partir de uma das chamadas de método públicas. Se você não pode fazer isso, qual é o sentido de ter o método em primeiro lugar? É simplesmente código inacessível.

Ampt
fonte
6
O objetivo dos métodos privados é facilitar o desenvolvimento (por meio da separação de preocupações ou da manutenção de DRY ou de várias coisas), mas deve ser não permanente. Eles são privados por esse motivo. Eles podem aparecer, desaparecer ou alterar drasticamente a funcionalidade de uma implementação para a seguinte, e é por isso que vinculá-los a testes de unidade nem sempre é prático ou mesmo útil.
Ampt
8
Nem sempre pode ser prático ou útil, mas isso está muito longe de dizer que você nunca deve testá-los. Você está falando sobre os métodos privados como se fossem os métodos privados de outra pessoa; "eles podem aparecer, desaparecer ...". Não, eles não podiam. Se você estiver testando-os diretamente, deve ser porque você mesmo os mantém. Se você alterar a implementação, você altera os testes. Em resumo, sua declaração geral é injustificada. Embora seja bom avisar o OP sobre isso, a pergunta dele ainda é justificada, e sua resposta não a responde de fato.
Nir Friedman
2
Permitam-me observar também: o OP disse antecipadamente que está ciente de que é uma prática debatida. Então, se ele quiser fazer isso de qualquer maneira, talvez ele realmente tenha boas razões para isso? Nenhum de nós conhece os detalhes de sua base de código. No código com o qual trabalho, temos alguns programadores muito experientes e especializados, e eles acharam que era útil testar métodos particulares em alguns casos.
Nir Friedman
2
-1, uma resposta como "Você não deve testar métodos privados". IMHO não é útil. O tópico quando testar e quando não testar métodos particulares foi discutido neste site suficientemente. O OP mostrou que ele está ciente dessa discussão e, claramente, ele está procurando soluções sob o pressuposto de que, no seu caso, testar métodos privados é o caminho a percorrer.
Doc Brown
3
Acho que o problema aqui é que esse pode ser um problema XY muito clássico, já que o OP acha que ele precisa testar seus métodos particulares por um motivo ou outro, quando, na realidade, ele poderia abordar o teste de um ponto de vista mais pragmático, visualizando o privado. métodos como meras funções auxiliares para os públicos, que são o contrato com os usuários finais da classe.
Ampt
3

Existem algumas opções para fazer isso, mas lembre-se de que elas (em essência) modificam a interface pública de seus módulos, para fornecer acesso a detalhes de implementação interna (transformando efetivamente o teste de unidade em dependências de cliente fortemente acopladas, onde você deve ter nenhuma dependência).

  • você pode adicionar uma declaração de amigo (classe ou função) à classe testada.

  • você pode adicionar #define private publicao início dos seus arquivos de teste antes de #includeinserir o código testado. No entanto, caso o código testado seja uma biblioteca já compilada, isso poderá fazer com que os cabeçalhos não correspondam mais ao código binário já compilado (e causem UB).

  • você pode inserir uma macro na sua classe testada e decidir posteriormente o que essa macro significa (com uma definição diferente para testar o código). Isso permitiria testar internamente, mas também permitiria que o código do cliente de terceiros invadisse sua classe (criando sua própria definição na declaração que você adicionou).

utnapistim
fonte
2

Aqui está uma sugestão questionável para uma pergunta questionável. Eu não gosto do acoplamento de amigo, já que o código liberado precisa saber sobre o teste. A resposta de Nir é uma maneira de aliviar isso, mas ainda não gosto muito de mudar de classe para se adequar ao teste.

Como não confio na herança com frequência, às vezes apenas protejo os métodos privados, e tenho uma classe de teste que herda e expõe conforme necessário. A realidade é que a API pública e a API de teste podem diferir e ainda ser distintas da API privada, o que deixa você em uma espécie de ligação.

Aqui está um exemplo prático desse tipo para o qual recorro. Eu escrevo código incorporado e confiamos bastante em máquinas de estado. A API externa não precisa necessariamente saber sobre o estado interno da máquina de estado, mas o teste deve (sem dúvida) testar a conformidade com o diagrama da máquina de estado no documento de design. Eu posso expor um getter de "estado atual" como protegido e, em seguida, dar acesso ao teste, permitindo que eu teste a máquina de estado mais completamente. Costumo achar esse tipo de aula difícil de testar como uma caixa preta.

J Trana
fonte
Embora seja uma abordagem Java, isso é bastante padrão para tornar o nível padrão das funções privadas, permitindo que outras classes no mesmo pacote (as classes de teste) possam vê-las.
0

Você pode escrever seu código com várias soluções alternativas para impedir que você use amigos.

Você pode escrever classes e nunca ter nenhum método privado. Tudo o que você precisa fazer é criar funções de implementação na unidade de compilação, deixar sua classe chamá-las e passar os membros de dados que eles precisam acessar.

E sim, isso significa que você pode alterar as assinaturas ou adicionar novos métodos de "implementação" sem alterar o cabeçalho no futuro.

Você tem que ponderar se vale ou não a pena. E muito dependerá realmente de quem verá seu cabeçalho.

Se eu estiver usando uma biblioteca de terceiros, prefiro não ver declarações de amigos para seus testadores de unidade. Também não quero construir a biblioteca deles e executar os testes quando o fizer. Infelizmente, muitas bibliotecas de código aberto de terceiros que eu construí fazem isso.

Testar é o trabalho dos escritores da biblioteca, não de seus usuários.

No entanto, nem todas as classes são visíveis para o usuário da sua biblioteca. Muitas classes são "implementação" e você as implementa da melhor maneira para garantir que elas funcionem corretamente. Nesses, você ainda pode ter métodos e membros particulares, mas deseja que os testadores de unidade os testem. Portanto, vá em frente e faça dessa maneira, se isso levar a um código robusto mais rápido, fácil de manter para quem precisa fazer isso.

Se os usuários de sua classe estiverem todos dentro da sua própria empresa ou equipe, você também poderá relaxar um pouco mais sobre essa estratégia, supondo que ela seja permitida pelos padrões de codificação da sua empresa.

CashCow
fonte