Oto typowy kod C ++:
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;
...
Wygląda jak porządny idiomatyczny kod C ++ - klasa, funkcja pomocnicza, match
która jest używana przez metody Foo
, niektóre stałe tej funkcji pomocniczej.
A potem chcę napisać testy.
Byłoby całkowicie logiczne napisanie oddzielnego testu jednostkowego match
, ponieważ jest to dość trywialne.
Ale rezyduje w anonimowej przestrzeni nazw.
Oczywiście mogę napisać test, który zadzwoniłby Foo::f()
. Jednak nie będzie to dobry test, jeśli Foo
jest ciężki i skomplikowany, taki test nie izoluje testowanego od innych niepowiązanych czynników.
Więc muszę się wyprowadzić match
i wszystko inne z anonimowej przestrzeni nazw.
Pytanie: po co umieszczać funkcje i stałe w anonimowej przestrzeni nazw, jeśli sprawia, że nie można ich używać w testach?
źródło
foo.cpp
, a nie nagłówek! OP wydaje się rozumieć całkiem dobrze, że nie należy umieszczać anonowych przestrzeni nazw w nagłówku.friend
słowa kluczowego do tego celu nie jest zalecane. Połącz to z założeniem że jeśli ograniczenie metody prowadzi do sytuacji, w której nie można jej więcej bezpośrednio przetestować, oznaczałoby to, że metody prywatne nie byłyby przydatne.Odpowiedzi:
Jeśli chcesz przetestować jednostkowo prywatne szczegóły implementacji, wykonaj ten sam rodzaj dodge dla nienazwanych przestrzeni nazw jak dla prywatnych (lub chronionych) członków klasy:
Włam się i imprezuj.
Podczas gdy nadużywasz klas
friend
, dla nienazwanych przestrzeni nazw nadużywasz#include
mechanizmu, który nawet nie zmusza cię do zmiany kodu.Teraz, gdy Twój kod testowy (lub lepiej tylko coś, aby wszystko ujawnić) znajduje się w tej samej JT, nie ma problemu.
Uwaga: jeśli przetestujesz szczegóły implementacji, test zostanie przerwany, jeśli się zmienią. Naprawdę koniecznie przetestuj tylko te szczegóły implementacji, które i tak wycieką, lub zaakceptuj, że Twój test jest niezwykle efemeryczny.
źródło
Funkcja w twoim przykładzie wygląda na dość złożoną i może być lepiej przenieść ją do nagłówka na potrzeby testów jednostkowych.
Aby odizolować je od reszty świata. I nie tylko funkcje i stałe można umieszczać w anonimowej przestrzeni nazw - dotyczy to także typów.
Jeśli jednak sprawia, że testy jednostkowe są bardzo złożone, oznacza to, że robisz to źle. W takim przypadku funkcja tam nie należy. Następnie nadszedł czas na trochę refaktoryzacji, aby uprościć testowanie.
Tak więc w anonimowej przestrzeni nazw powinny działać tylko bardzo proste funkcje, czasem stałe i typy (w tym typedefs) używane w tej jednostce tłumaczeniowej.
źródło
Kod, który pokazałeś,
match
to dość trywialny 1-liniowy program bez żadnych trudnych przypadków na krawędziach, czy to jest jak uproszczony przykład? W każdym razie założę, że to uproszczone ...To pytanie sprawiło, że wskoczyłem tutaj, ponieważ Deduplicator pokazał już doskonale dobry sposób włamania się i uzyskania dostępu poprzez
#include
oszustwo. Ale sformułowanie tutaj sprawia, że brzmi to jak testowanie każdego wewnętrznego szczegółu implementacji wszystkiego, jest to pewnego rodzaju uniwersalny cel końcowy, gdy jest on daleko od niego.Celem nawet jednostkowych testów nie zawsze jest testowanie każdej drobnej ziarnistej wewnętrznej mikro-jednostki funkcjonalności. To samo pytanie dotyczy funkcji statycznego zakresu plików w C. Możesz nawet trudniej odpowiedzieć na to pytanie, pytając, dlaczego programiści używają
pimpls
w C ++, co wymagałoby zarównofriendship
i#include
oszustwa, jak i białej skrzynki, wymieniając łatwą testowalność szczegółów implementacji w celu poprawy czasów kompilacji, na przykładZ pewnego pragmatycznego punktu widzenia może to zabrzmieć obrzydliwie, ale
match
może nie zostać poprawnie zaimplementowane w niektórych przypadkach krawędzi, które powodują potknięcie. Jeśli jednak jedyna klasa zewnętrznaFoo
, która ma dostęp,match
nie może jej użyć w sposób, który napotyka te przypadki skrajne, to raczej nie ma znaczenia dla poprawności tego,Foo
żematch
ma przypadki skrajne, które nigdy nie zostaną napotkane, chyba żeFoo
zmiany, w którym to momencie testyFoo
zakończą się niepowodzeniem i dowiemy się natychmiast.Bardziej obsesyjny sposób myślenia, chętny do przetestowania każdego szczegółu wewnętrznego wdrożenia (być może np. Oprogramowanie o kluczowym znaczeniu) może chcieć włamać się i imprezować, ale wiele osób niekoniecznie uważa, że to najlepszy pomysł, ponieważ stworzyłoby to najbardziej kruche testy, jakie można sobie wyobrazić. YMMV. Chciałem jednak odnieść się do sformułowania tego pytania, które sprawia, że brzmi to tak, jakby tego rodzaju uber-drobnoziarnista wewnętrzna szczegółowość na poziomie szczegółowości powinna być celem końcowym, kiedy nawet najbardziej rygorystyczny sposób testowania jednostek może się tutaj nieco zrelaksować i unikaj prześwietlania wnętrza każdej klasy.
Dlaczego ludzie definiują funkcje w anonimowych przestrzeniach nazw w C ++ lub jako funkcje statyczne o zasięgu pliku z wewnętrznym powiązaniem w C, ukryte przed światem zewnętrznym? I to głównie o to: aby ukryć je przed światem zewnętrznym. Ma to szereg efektów, od skrócenia czasu kompilacji do zmniejszenia złożoności (to, czego nie można uzyskać gdzie indziej, nie może powodować problemów w innym miejscu) i tak dalej. Prawdopodobnie testowalność prywatnych / wewnętrznych szczegółów implementacyjnych nie jest najważniejszą rzeczą w ludzkich umysłach, kiedy robią to ponad, powiedzmy, skracając czas kompilacji i ukrywając niepotrzebną złożoność przed światem zewnętrznym.
źródło