Testowanie prywatnych metod

Często podczas pisania testów/specyfikacji mamy ochotę na przetestowanie danej metody prywatnej. Wiele osób testuje tylko publiczny interfejs klasy ale zdarzają się przypadki kiedy chciałoby się przetestować kilka wybranych prywatnych metod (np. kiedy publiczny interfejs jest bardzo prosty a prawdziwa praca wykonywana jest przez prywatne metody - co często z drugiej strony jest oznaką niezrównoważonego projektu klasy).

Z pomocą przychodzi Ruby ze swoimi mechanizmami refleksji i dynamicznych otwartych klas:

[code=ruby]require ‘rubygems’
require ‘dust’
require ‘test/unit’

class Ninja
private
def kill(num_victims)
“#{num_victims} victims are no longer with us.”
end
end

class Class
def publicize_methods
saved_private_instance_methods = self.private_instance_methods
self.class_eval { public *saved_private_instance_methods }
yield
self.class_eval { private *saved_private_instance_methods }
end
end

unit_tests do
test “kill returns a murder string” do
Ninja.publicize_methods do
assert_equal ‘3 victims are no longer with us.’, Ninja.new.kill(3)
end
end
end[/code]
Znalezione tutaj

Doprawdy trudno mi zrozumieć sytuację, w której chcielibyśmy używać tego rodzaju rozwiązania. Na pewno nie do testowania kodu, który pochodzi “z zewnątrz” - szczegóły implementacyjne - a to właśnie ukryte są w metodach prywatnych - mogą się zmienić, zatem ich testowanie sensu nie ma.

Jeśli zaś testujemy własny kod, to (na tyle na ile znam TDD i BDD) testy stanowią specyfikację (albo jej część). Jeśli w niej znajduje się jakaś metoda prywatna, to czemu nie uczynić z niej po prostu metody publicznej? A odnośnie sytuacji, w której mamy tylko kilka “prostych” metod publicznych - skoro istotnie implementacja korzysta z wielu metod prywatnych, to lepszym sposobem testowania takiej klasy byłoby chyba przygotowanie różnorodnych testów dla tych metod publicznych.

Wydaje mi się, że zaproponowane rozwiązanie jest raczej nierozsądnym użyciem dynamicznych własności Rubiego - tzn. idzie na przekór fundamentom programowania zorientowanego obiektowo.

Nie zgodzę się z Tobą apohllo. Testy unitowe jak najbardziej mogą wykorzystywać wiedzę jak działa dana metoda (co jest w środku - zdaje się to się ładnie nazywa white box). Dlatego nie ma przeszkód by testować także metody prywatne.

@radarek

Nie jestem specjalistą od testów, ani od programowania obiektowego, ale testowanie metod prywatnych budzi mój sprzeciw :slight_smile: Jeszcze raz zapytam - po co jakaś metoda jest oznaczana jako prywatna? Bo zawiera szczegóły implementacyjne, które są najbardziej podatne na zmiany, ponadto może przyjmować założenia, których nie można przekazać jako parametrów jej wywołania (np. wartości określonych zmiennych instancyjnych, wywołanie w ramach transakcji, sekcji krytycznej, przy dostępności określonego zasobu, etc.)

To, że testy jednostkowe można pisać zaglądając do implementacji to inna sprawa. Ja rozumiem to raczej w kontekście wyspecyfikowania różnych przypadków testowych, tak aby dostać powiedzmy 100% pokrycia. Zresztą zwróć uwagę na to co sam napisałeś:

Pokreślenie moje. Wykorzystywać wiedzę != testować metody prywatne.

Zarówno w TDD i w BDD zaleca się pisanie testów przed implementacją. Mają one nam pomóc napisać tylko to, co jest niezbędne do uzyskania pożądanego zachowania. Nie może być tutaj mowy o metodach prywatnych, bo nie jest to żadne zachowanie widoczne z zewnątrz. A kiedy pojawia się błąd, dla którego chcemy napisać test, to też widoczny jest on “na zewnątrz” - pojawia się jako rezultat określonego wywołania publicznego.

Te wywody opieram na podstawach OOP, TDD i BDD - jeśli w praktyce ktoś testuje metody prywatne i dzięki temu udaje mu się unikać błędów, to chwała mu za to. Tylko niech nie twierdzi, że działa w zgodzie z tymi metodologiami, a to z nich zaczerpnięte są takie pojęcia jak prywatność, czy testy jednostkowe.

[quote=apohllo]@radarek

Nie jestem specjalistą od testów, ani od programowania obiektowego, ale testowanie metod prywatnych budzi mój sprzeciw :)[/quote]
Ja też nie jestem specjalistą :). Postaram się odpowiedzieć w taki sposób jak ja pojmuję OOP, TDD (BDD to tak na prawdę TDD++).

Masz zupełną rację. Ale przedstawiasz stanowisko kogoś kto używa naszej biblioteki (API). Metody prywatne mają na celu ukrywanie szczegółów implementacyjnych - tu również się zgadzam. Jednak ukrywamy kod nie przed samym sobą, a przed tymi, którzy będą go używać “z zewnątrz”. My, jako autorzy biblioteki chcemy przetestować ją. Mamy więc prawo od metod prywatnych także oczekiwać spełnienia pewnych warunków, a zatem mamy prawo je przetestować.

[quote=apohllo]To, że testy jednostkowe można pisać zaglądając do implementacji to inna sprawa. Ja rozumiem to raczej w kontekście wyspecyfikowania różnych przypadków testowych, tak aby dostać powiedzmy 100% pokrycia. Zresztą zwróć uwagę na to co sam napisałeś:

Pokreślenie moje. Wykorzystywać wiedzę != testować metody prywatne.

Zarówno w TDD i w BDD zaleca się pisanie testów przed implementacją. Mają one nam pomóc napisać tylko to, co jest niezbędne do uzyskania pożądanego zachowania. Nie może być tutaj mowy o metodach prywatnych, bo nie jest to żadne zachowanie widoczne z zewnątrz. A kiedy pojawia się błąd, dla którego chcemy napisać test, to też widoczny jest on “na zewnątrz” - pojawia się jako rezultat określonego wywołania publicznego.[/quote]
Wiadomo jest, że metody prywatne pisze się po to, żeby było wołane przez publiczne (od nich przecież zaczyna się używanie obiektów). Prawdą jest, że jeśli prywatna metoda ‘zepsuje się’ dostaniesz o tym informację. Jednak czy nie lepiej byłoby dostać konkretną informację, że metoda prywatna X nie przechodzi testów, niż publiczna metoda Y nie przeszła testół a potem dopiero doszukać się, że to prywatna metoda zawiodła? Pisanie testów z jednej strony służy pisaniu interfejsu (to o czym pisałeś), ale także sprawdzaniu pewnego kontraktu, który jest zaszyty w logice metody. I tu nieważne czy metoda jest prywatna czy publiczna. Zawsze może przyjść inny programista popsuć kod, a testy wykryją to.

Szczerze mówiąc sam do końca nie wiem czy można (powinno) tak robić czy nie. Myślę, jednak, że nie jest to dużym grzechem. Nie wykluczam, że po zdobyciu większego doświadczenia zdania nie zmienię :).

No ja też prawdę mówiąc nie wiem, czy to jest grzechem :slight_smile:

Po prostu myślę sobie tak - jeśli ta prywatna metoda zawiera jakąś funkcjonalność, którą można wyodrębnić i przetestować niezależnie, to dlaczego nie mielibyśmy jej upublicznić?
W przeciwnym razie (w szczególności odnoszę się tutaj do specyficznych warunków, w jakich wywoływana jest ta metoda) trzeba sobie odpowiedzieć na pytanie: co my właściwie testujemy? Czy warunki (innymi słowy środowisko) wywołania tej metody w obu przypadkach są takie same? (tzn. w teście i w realnym użyciu).

Jako ilustrację problemu weźmy pojęcie konstruktora - jest to jedyna metoda, która musi być wywołana, przed użyciem obiektu - nie można dostać obiektu nie wywołując jego konstruktora. Zatem cała jego inicjalizacja powinna się dokonać właśnie w konstruktorze. Potem powinniśmy mieć w zasadzie możliwość wywołania dowolnej metody publicznej.

Ale, jak pisałem wcześniej, nie dotyczy to metod prywatnych - przy ich wywołaniu możemy przyjmować pewne dodatkowe założenia. I teraz kiedy piszemy test, to musimy uwzględnić wszystkie te założenia i test staje się przez to mniej zrozumiały. Znacznie łatwiej spowodować sytuację, w której popełnimy błąd w teście, na czym chyba nikomu nie zależy.

Te przemyślenia przyszły mi do głowy, kiedy przeglądałem narzędzia do testów opartych o BDD :wink: W RSpec jest zdaje się coś takiego jak Matcher, który pozwala nam definiować własne, czytelne nazwy metod. Wszystko pięknie, do momentu, w którym rozważymy błąd w naszej implementacji Matchera. Czy mamy go też testować (testować własne narzędzia do testowania, to już obłęd)?

Na tyle, na ile rozumiem sens pisania testów, to powinny one być najprostsze jak to tylko możliwe, aby zminimalizować ryzyko popełnienia błędu w samym teście. A testowanie metod prywatnych, czy opisany mechanizm RSpeca wydają mi się iść w zupełnie odwrotnym kierunku.

Podkreślam jednak, że nie jestem specjalistą i chętnie wysłuchałbym opinii testera z dużym doświadczeniem :slight_smile:

Wydaje mi się, że w dużej mierze trzeba sobie wyrobić własne zdanie. Po prostu pisząc testy sami dojdziemy do wniosku, że taki a taki sposób testowania jest lepszny niż inny itp. Sami widzimy, że zdania się podzielone ;). Aczkolwiek chętnie przeczytałbym książkę o TDD (BDD). Czy jest coś sensownego po polsku? Po angielsku w sumie też mogłoby być, ale tutaj zapewne będzie więcej teorii i rozważań niż kodu więc wygodniej czytałoby się w ojczystym języku.

A nie black box ?

A przepraszam, faktycznie to jest white box. Black box to odwrotna sytuacja, kiedy ta nasza czarna skczynka jest niedostępna dla nas i musimy obserwować ją tylko z zewnątrz.

Testujemy zachowania, więc testy tworzymy dla metod publicznych. Pisząc test-first metody prywatne powstaną w ramach refaktorowania metod publicznych. W ten sposób cały kod, łącznie z metodami prywatnymi, będzie przetestowany. Nie ma potrzeby testować oddzielnie metod prywatnych.

Problem testować/nie testować metody prywatne pojawia się, gdy piszemy najpierw implementacje, a później przypominamy sobie o testach. Zamiast opisanej na początku refleksji proponuje refaktoryzacje.

A przede wszystkim polecam praktykować test-first. Wtedy wszystko stanie się jasne :slight_smile:

Troszkę archeologii, ale chciałbym spytać jak po 3 latach zapatrujecie się na tę sprawę - na testowanie prywatnych metod? Czy coś nowego możecie powiedzieć na ten temat?

W tej materii ja się zgadzam z Avdim: http://avdi.org/devblog/2008/10/21/testing-private-methods

+1 jeżeli chodzi o teorię.

W praktyce zdarza mi się czasami testować prywatne metody, nie zawsze jest tak różowo z rozpakowywaniem czegoś do klasy. Ale warto dążyć do podejścia, które przedstawił Avdi.

Jeżeli dałeś radę wrzucić coś do prywatnej metody to dlaczego nie miałbyś móc przerzucić tego kodu do innej klasy? Jedyna różnica, że musisz ten nowy obiekt jakoś zainicjalizować jeżeli w pierwotnej klasie prywatna metoda korzystała z jakichś zależności. Może problem tkwi w tym, że sama prywatna metoda już za dużo rzeczy robi i nie bardzo jest jak określić jasno jej odpowiedzialność?

Myślę, że to byłby dobry topic na bitwę na wroc_love.rb

Technicznie nie ma żadnego problemu. Chodzi mi o to, że czasami nie widzę w tym sensu.

Prywatne metody podlegają zmianom i mogą zawierać błędy/bugi, dlatego trzeba je testować (Unitami). Są prywatne po to, żeby m.in. nie robić bałaganu w publicznym interfejsie klasy i ich użycie potrafi doskonale zwiększyć przejrzystość kodu.

+1. Ja testuję czasem metody prywatne i trick podany przez hosiawaka wydaje się całkiem fajny.

Tak na marginesie chciałbym zauważyć, że TDD (i BDD) nie ma tu wiele do rzeczy - liczy się z jakim kodem skończysz. Nie mylcie TDD z „pisaniem testów”.

+1. Ja testuję czasem metody prywatne i trick podany przez hosiawaka wydaje się całkiem fajny.[/quote]
Co do tricku, to ja wolę proste:

@drogus A czasem:

obj.send(:some_private_method)

Nie przestał działać z ruby1.9?