Aqui está um código C ++ típico:
foo.hpp
#pragma once
class Foo {
public:
void f();
void g();
...
};
foo.cpp
#include "foo.hpp"
namespace {
const int kUpperX = 111;
const int kAlternativeX = 222;
bool match(int x) {
return x < kUpperX || x == kAlternativeX;
}
} // namespace
void Foo::f() {
...
if (match(x)) return;
...
Parece um código C ++ idiomático decente - uma classe, uma função auxiliar match
que é usada pelos métodos de Foo
, algumas constantes para essa função auxiliar.
E então eu quero escrever testes.
Seria perfeitamente lógico escrever um teste de unidade separado match
, porque é bastante não trivial.
Mas ele reside em um espaço para nome anônimo.
Claro que posso escrever um teste que chamaria Foo::f()
. No entanto, não será um bom teste se Foo
for pesado e complicado; esse teste não isolará o testado de outros fatores não relacionados.
Então eu tenho que mudar match
e todo o resto do espaço para nome anônimo.
Pergunta: qual é o sentido de colocar funções e constantes no espaço para nome anônimo, se as torna inutilizáveis nos testes?
fonte
foo.cpp
, não o cabeçalho! O OP parece entender muito bem que você não deve colocar nenhum espaço para nome em um cabeçalho.friend
não é recomendável abusar da palavra - chave para esse fim. que, se uma restrição para um método leva a uma situação em que você não pode mais testá-lo diretamente, isso implicaria que métodos privados não eram úteis.Respostas:
Se você quiser testar os detalhes da implementação privada, faça o mesmo tipo de esquiva para espaços de nome não nomeados que para membros da classe privados (ou protegidos):
Entre e festeje.
Enquanto nas classes que você abusa
friend
, nos namespaces não nomeados, você abusa do#include
mecanismo, que nem o força a alterar o código.Agora que seu código de teste (ou melhor, apenas algo para expor tudo) está na mesma TU, não há problema.
Uma palavra de cautela: Se você testar os detalhes da implementação, seu teste será interrompido se eles mudarem. Certifique-se de testar apenas os detalhes da implementação que vazarão de qualquer maneira ou aceite que seu teste é extraordinariamente efêmero.
fonte
A função no seu exemplo parece bastante complexa e pode ser melhor movê-la para o cabeçalho, para fins de teste de unidade.
Para torná-los isolados do resto do mundo. E não são apenas as funções e constantes que você pode colocar no espaço para nome anônimo - também é para tipos.
No entanto, se tornar seus testes de unidade muito complexos, você estará fazendo errado. Nesse caso, a função não pertence a isso. Chegou a hora de um pouco de refatoração para simplificar os testes.
Portanto, no espaço para nome anônimo, devem haver apenas funções muito simples, às vezes constantes e tipos (incluindo typedefs) usados nessa unidade de tradução.
fonte
O código que você mostrou
match
é um liner 1 bastante trivial, sem casos extremos complicados, ou é como um exemplo simplificado? Enfim, eu vou assumir que é simplificado ...Esta pergunta é o que queria me fazer entrar aqui, já que o Deduplicator já mostrava uma maneira perfeitamente boa de invadir e obter acesso através de
#include
truques. Mas a redação aqui faz parecer que testar todos os detalhes internos de implementação de tudo é algum tipo de objetivo final universal, quando está longe disso.O objetivo dos testes unitários nem sempre é testar todas as micro-unidades granulares internas de funcionalidade. A mesma pergunta se aplica a funções estáticas de escopo de arquivo em C. Você pode até tornar a pergunta mais difícil de responder, perguntando por que os desenvolvedores usam
pimpls
em C ++ que exigiria tanto truquesfriendship
quanto#include
truques na caixa branca, negociando facilmente a testabilidade dos detalhes da implementação para melhorar os tempos de compilação, por exemploDe um tipo de perspectiva pragmática, pode parecer grosseiro, mas
match
pode não ser implementado corretamente com alguns casos extremos que o fazem tropeçar. No entanto, se a única classe externaFoo
, que tem acesso,match
não pode usá-la de maneira a encontrar esses casos extremos, então é bastante irrelevante a correção deFoo
quematch
esses casos extremos nunca serão encontrados, a menosFoo
que sejam alterados, nesse momento os testesFoo
falharão e saberemos imediatamente.Uma mentalidade mais obsessiva, ansiosa por testar todos os detalhes de implementação interna (talvez um software de missão crítica, por exemplo) pode querer entrar e festejar, mas muitas pessoas não acham necessariamente que essa é a melhor ideia, pois isso criaria o testes mais frágeis que se possa imaginar. YMMV. Mas eu só queria abordar a redação desta pergunta, que faz parecer que esse tipo de testabilidade de nível interno de detalhes ultrafinos deve ser um objetivo final, quando mesmo a mentalidade mais rigorosa de teste de unidade pode relaxar um pouco aqui e evite radiografar os internos de todas as classes.
Então, por que as pessoas definem funções em namespaces anônimos em C ++ ou como funções estáticas no escopo de arquivos com ligação interna em C, ocultas do mundo externo? E é isso principalmente: escondê-los do mundo exterior. Isso tem vários efeitos, da redução do tempo de compilação à redução da complexidade (o que não pode ser acessado em outro lugar não pode causar problemas em outro lugar) e assim por diante. Provavelmente, a testabilidade dos detalhes de implementação privada / interna não é a primeira coisa em que as pessoas pensam, por exemplo, reduzindo o tempo de construção e ocultando a complexidade desnecessária do mundo exterior.
fonte