Czasami funkcje prywatne są po prostu jeszcze nie wyodrębnionymi wewnętrznymi jednostkami funkcjonalności. Dlaczego więc ich nie przetestować?

9

Czasami prywatne funkcje modułu lub klasy są po prostu jeszcze do wyodrębnienia wewnętrznymi jednostkami funkcjonalności, które mogą zasługiwać na własne testy. Dlaczego więc ich nie przetestować? My będziemy pisać testy do nich później, jeśli / kiedy są one wyodrębnione. Dlaczego więc nie napisać testów teraz, gdy są one nadal częścią tego samego pliku?

Aby zademonstrować:

wprowadź opis zdjęcia tutaj

Najpierw napisałem module_a. Teraz chcę na to napisać testy. Chciałbym przetestować funkcję „prywatną” _private_func. Nie rozumiem, dlaczego nie napisałbym dla niego testu, jeśli później mógłbym zrefaktoryzować go do własnego modułu wewnętrznego, a następnie napisać dla niego testy.


Załóżmy, że mam moduł z następującymi funkcjami (może to być również klasa):

def public_func(a):
    b = _do_stuff(a)
    return _do_more_stuff(b)

_do_stuffi _do_more_stuffsą „prywatnymi” funkcjami modułu.

Rozumiem pomysł, że powinniśmy testować tylko interfejs publiczny, a nie szczegóły implementacji. Oto jednak:

_do_stuffi _do_more_stuffzawierają większość funkcjonalności modułu. Każdy z nich może być funkcją publiczną innego, „wewnętrznego” modułu. Ale nie są one jeszcze rozwinięte i wystarczająco duże, aby można je było wyodrębnić do osobnych plików.

Testowanie tych funkcji wydaje się słuszne, ponieważ są ważnymi jednostkami funkcjonalności. Gdyby były one w różnych modułach jako funkcje publiczne, przetestowalibyśmy je. Dlaczego więc nie przetestować ich, gdy nie są jeszcze (lub nigdy) wyodrębnione do innego pliku?

Aviv Cohn
źródło
2
„Prywatne metody są korzystne dla testów jednostkowych ...” „… ... trzymanie się prywatnej metody przynosi mi użyteczne, niezawodne ulepszenie w testach jednostkowych. Natomiast osłabienie ograniczeń dostępu„ dla testowalności ”daje mi tylko niejasne, trudne do zrozumienia kawałek kodu testowego, który jest dodatkowo narażony na ryzyko złamania przez jakiekolwiek drobne refaktoryzacje; szczerze mówiąc, to, co dostaję, wygląda podejrzanie jak dług techniczny ”
gnat,
3
„Czy powinienem przetestować funkcje prywatne?” Nie. Nigdy, nigdy, nigdy.
David Arno,
2
@DavidArno Dlaczego? Jaka jest alternatywa dla testowania elementów wewnętrznych? Tylko testy integracyjne? Lub upublicznić więcej rzeczy? (chociaż z mojego doświadczenia
testuję
1
Jeśli jest na tyle ważne, że istnieje potrzeba napisania testów, powinien już znajdować się w osobnym module. Jeśli nie, przetestuj jego zachowanie za pomocą publicznego interfejsu API.
Vincent Savard,

Odpowiedzi:

14

Potrzeba testowania nie jest tym samym, co potrzeba bycia publicznym.

Nietrywialny kod wymaga testowania niezależnie od narażenia. Zachowanie niepubliczne nie musi istnieć, nie mówiąc już o testowaniu.

Te sprzeczne widoki mogą prowadzić do upublicznienia każdej funkcji lub odmowy przypisania kodu do funkcji, chyba że będzie ona publiczna.

To nie jest odpowiedź. Bądź gotów tworzyć prywatne funkcje pomocnicze. Przetestuj je za pomocą publicznego interfejsu, który wykorzystuje go w jak największym stopniu.

Jeśli testowanie przez interfejs publiczny nie wykonuje dostatecznie funkcji prywatnej, funkcja prywatna próbuje na to pozwolić.

Sprawdzanie poprawności może pomóc zawęzić zakres funkcji prywatnych. Jeśli nie możesz przejść wartości null przez interfejs publiczny, nadal możesz zgłosić wyjątek, jeśli taki zostanie.

Dlaczego powinieneś? Po co testować to, czego nigdy nie zobaczysz? Ponieważ rzeczy się zmieniają. Może być teraz prywatny, ale później publiczny. Kod wywołujący może ulec zmianie. Kod, który wyraźnie odrzuca wartość null, zapewnia prawidłowe użycie i oczekiwany stan.

Oczywiście zero może być w porządku. To tylko przykład tutaj. Ale jeśli czegoś oczekujesz, warto wyjaśnić to oczekiwanie.

To może nie był rodzaj testu, o którym myślałeś, ale mam nadzieję, że będziesz gotowy stworzyć prywatne funkcje pomocnicze, gdy będzie to właściwe.

Chęć testowania jest dobra, ale nie powinna być siłą napędową w projektowaniu publicznego interfejsu API. Zaprojektuj publiczny interfejs API, aby był łatwy w użyciu. Prawdopodobnie nie będzie, jeśli każda funkcja jest publiczna. Interfejs API powinien być czymś, co ludzie mogą zrozumieć, jak korzystać bez zanurzania się w kodzie. Nie zostawiaj takich ludzi zastanawiających się, do czego służy ta dziwna funkcja pomocnika.

Ukrywanie funkcji pomocnika publicznego w module wewnętrznym jest próbą respektowania potrzeby czystego interfejsu API podczas wystawiania pomocników do testowania. Nie powiem, że to źle. Być może robisz pierwszy krok w kierunku innej warstwy architektonicznej. Ale proszę, opanuj sztukę testowania funkcji prywatnych pomocników za pomocą funkcji publicznych, które ich używają jako pierwsze. W ten sposób nie będziesz nadmiernie używać tego obejścia.

candied_orange
źródło
Wymyśliłem podejście, chciałbym usłyszeć twoją opinię: kiedykolwiek jestem w sytuacji, w której chcę przetestować funkcję prywatną, sprawdzę, czy mogę ją wystarczająco przetestować za pomocą jednej z funkcji publicznych (w tym wszystkie przypadki na krawędziach itp.). Jeśli mogę, nie napiszę specjalnie testu dla tej funkcji, a jedynie testuję ją, testując funkcje publiczne, które z niej korzystają. Jeśli jednak uważam, że funkcja nie może być wystarczająco przetestowana przez interfejs publiczny i zasługuje na własny test, wyodrębnię ją do wewnętrznego modułu, w którym jest publiczna, i napiszę testy dla niej. Co myślisz?
Aviv Cohn,
Powiem, że pytam o to samo inni faceci, którzy tutaj odpowiedzieli :) Chciałbym usłyszeć opinie wszystkich.
Aviv Cohn,
Znowu nie powiem ci nie. Obawiam się, że nie powiedziałeś nic o pilnowaniu, jak wpływa to na użyteczność. Różnica między publicznym a prywatnym nie ma charakteru strukturalnego. To użycie. Jeśli różnica między publicznym a prywatnym to drzwi frontowe i tylne, to Twoim zadaniem jest zbudowanie szopy na podwórku. W porządku. Dopóki ludzie się tam nie zgubią.
candied_orange
1
Uznano za „Jeśli testowanie przez interfejs publiczny nie wykonuje wystarczająco funkcji prywatnej, funkcja prywatna próbuje na to pozwolić”.
Kris Welsh
7

Krótka odpowiedź: nie

Dłuższa odpowiedź: tak, ale za pośrednictwem publicznego „API” twojej klasy

Cała idea prywatnych członków klasy polega na tym, że reprezentują one funkcjonalność, która powinna być niewidoczna poza „jednostką” kodu, bez względu na to, jak duża ma być ta jednostka. W kodzie obiektowym jednostka często kończy się klasą.

Powinieneś tak zaprojektować swoją klasę, aby możliwe było wywołanie całej prywatnej funkcjonalności poprzez różne kombinacje stanu wejściowego. Jeśli okaże się, że nie ma stosunkowo prostego sposobu, aby to zrobić, prawdopodobnie jest to wskazówka, że ​​twój projekt wymaga bliższej uwagi.


Po wyjaśnieniu pytania jest to tylko kwestia semantyki. Jeśli dany kod może działać jako oddzielna samodzielna jednostka i jest testowany tak, jakby był kodem publicznym, nie widzę żadnej korzyści z przeniesienia go do samodzielnego modułu. Obecnie służy to jedynie wprowadzeniu w błąd przyszłych programistów (w tym ciebie, za 6 miesięcy), co do tego, dlaczego pozornie publiczny kod jest ukryty w innym module.

Richzilla
źródło
Cześć, dziękuję za odpowiedź :) Proszę ponownie przeczytać pytanie, które zredagowałem w celu wyjaśnienia.
Aviv Cohn,
Wymyśliłem podejście, chciałbym usłyszeć twoją opinię: kiedykolwiek jestem w sytuacji, w której chcę przetestować funkcję prywatną, sprawdzę, czy mogę ją wystarczająco przetestować za pomocą jednej z funkcji publicznych (w tym wszystkie przypadki na krawędziach itp.). Jeśli mogę, nie napiszę specjalnie testu dla tej funkcji, a jedynie testuję ją, testując funkcje publiczne, które z niej korzystają. Jeśli jednak uważam, że funkcja nie może być wystarczająco przetestowana przez interfejs publiczny i zasługuje na własny test, wyodrębnię ją do wewnętrznego modułu, w którym jest publiczna, i napiszę testy dla niej. Co myślisz?
Aviv Cohn,
Powiem, że pytam o to samo inni faceci, którzy tutaj odpowiedzieli :) Chciałbym usłyszeć opinie wszystkich.
Aviv Cohn,
5

Chodzi o to, że funkcje prywatne są ukrytymi szczegółami implementacji, które można dowolnie zmieniać bez zmiany publicznego interfejsu API. Twój przykładowy kod:

def public_func(a):
    b = _do_stuff(a)
    return _do_more_stuff(b)

Jeśli masz serię testów, które tylko używają public_func, to jeśli przepisujesz to na:

def public_func(a):
    b = _do_all_the_new_stuff(a)
    return _do_all_the_old_stuff(b)

wtedy, dopóki wynik zwrotu dla określonej wartości apozostanie taki sam, wszystkie testy będą dobre. Jeśli wynik zwrotu ulegnie zmianie, test zakończy się niepowodzeniem, co podkreśla fakt, że coś się zepsuło.

To wszystko dobrze: statyczny publiczny interfejs API; dobrze zamknięte wewnętrzne funkcjonowanie; i solidne testy.

Jednakże, jeśli napisałeś testy dla _do_stufflub _do_more_stuffnastępnie dokonałeś powyższej zmiany, miałbyś teraz kilka zepsutych testów, nie dlatego, że zmieniono funkcjonalność, ale dlatego, że zmieniła się implementacja tej funkcjonalności. Testy te wymagałyby przepisania, aby pracować z nowymi funkcjami, ale po ich uruchomieniu można jedynie wiedzieć, że testy te działały z nowymi funkcjami. Utracilibyście oryginalne testy, więc nie wiedzielibyście, czy zachowanie public_funcsię zmieniło, więc testy byłyby w zasadzie bezużyteczne.

To zła rzecz: zmieniający się interfejs API; odsłonięte elementy wewnętrzne ściśle połączone z testami; i kruche testy, które zmieniają się, gdy tylko wprowadzane są zmiany w implementacji.

Więc nie, nie testuj funkcji prywatnych. Zawsze.

David Arno
źródło
Cześć David, dziękuję za odpowiedź. Przeczytaj ponownie moje pytanie, które zredagowałem w celu wyjaśnienia.
Aviv Cohn,
Meh Nie ma nic złego w testowaniu funkcji wewnętrznych. Ja osobiście nie piszę żadnych trywialnych funkcji, chyba że będę w stanie opisać je kilkoma testami jednostkowymi, więc zakaz testowania funkcji wewnętrznych praktycznie uniemożliwiłby mi pisanie funkcji wewnętrznych, pozbawiając mnie bardzo przydatnej techniki. Interfejsy API mogą i mogą się zmieniać; kiedy to zrobią, musisz zmienić testy. Refaktoryzacja funkcji wewnętrznej (i jej testu) nie przerywa testów funkcji zewnętrznej; o to właśnie chodzi w tych testach. Zła rada ogólnie.
Robert Harvey,
Naprawdę argumentujesz, że nie powinieneś mieć prywatnych funkcji.
Robert Harvey,
1
@AvivCohn Są albo wystarczająco duże, aby uzasadnić testowanie, w którym to przypadku są wystarczająco duże, aby uzyskać własny plik; lub są na tyle małe, że nie trzeba ich testować osobno. Więc co to jest?
Doval,
3
@RobertHarvey Nie, sprawia, że ​​argument „dzieli duże klasy na luźno powiązane komponenty, jeśli to konieczne”. Jeśli tak naprawdę nie są publiczne, to prawdopodobnie jest to dobry przypadek użycia dla widoczności pakietu.
Doval,