Jak Testować Metody Prywatne?
Jest kilka popularnych sposobów do testowania metod prywatnych. Można po prostu zmienić modyfikator dostępu takiej metody, wtedy od razu ta metoda będzie widoczna w teście. Jednak nie jest to zazwyczaj dobre rozwiązanie, bo skoro dana metoda ma być prywatna, to raczej nie powinno się zmieniać jej poziomu dostępności, tylko ze względu na testy (w niektórych przypadkach ma to sens, ale o tym będzie w dalszej części tego artykułu). Wprowadza to tylko bałagan do naszego kodu. Kolejne sposoby to użycie mechanizmu refleksji albo atrybutu InternalsVisibleTo. Nie będę się jednak rozwijał na temat tego, jak dokładnie zaimplementować takie testy, ponieważ w obu przypadkach, również nie będzie to dobre rozwiązanie. Jeżeli istnieje u Ciebie potrzeba testowania metody prywatnej, to jest to najprawdopodobniej oznaka, że masz złą architekturę w swojej aplikacji. Także, prawdopodobnie powinieneś zacząć od refaktoryzacji.
Czy Testować Metody Prywatne i Chronione?
Przyjrzyjmy się prostemu przykładowi.
public class Offer
{
public string Title { get; private set; }
public event EventHandler OfferChanged;
public void SetTitle(string title)
{
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentNullException(nameof(title));
Title = title;
OnOfferChanged(Guid.NewGuid());
}
protected virtual void OnOfferChanged(Guid id)
{
OfferChanged?.Invoke(this, id);
}
}
Mamy tutaj metodę SetTitle, w klasie Offer, która po prostu ustawia właściwość Title, jeżeli parametr nie jest nullem, oraz pustym stringiem, a także wyzwala zdarzenie OfferChanged. Napiszmy krótki test, upewniający nas o tym, że zdarzenie OfferChanged jest wyzwalane, gdy przekazany jest prawidłowy parametr do metody. Test może wyglądać w ten sposób:
[Test]
public void SetTitle_WhenCalled_ShouldRaiseOfferChangedEvent()
{
var offer = new Offer();
using (var monitoredSubject = offer.Monitor())
{
offer.SetTitle("1");
monitoredSubject.Should().Raise("OfferChanged");
}
}
Jeżeli do metody zostanie przekazana wartość 1, to wtedy jest wyzwalane zdarzenie OfferChanged. Teraz zastanówmy się, czy dodatkowo powinny zostać napisane testy do niepublicznej metody OnOfferChanged. Jak widzisz, jest to szczegół naszej implementacji, który został przetestowany już w testach metody publicznej SetTile. Także nie jest konieczne pisanie dodatkowych testów do metody OnOfferChanged. Jeżeli natomiast zdecydowałbyś się na pisanie testów do tej metody, to również musisz pamiętać, że metoda, która jest szczegółem implementacji, dużo częściej może zostać zmieniona. W tym momencie do metody OnOfferChanged jest przekazywany parametr id, ale równie dobrze mógłby on być pobierany z pola klasy Offer.
public class Offer
{
private Guid _id;
public string Title { get; private set; }
public event EventHandler OfferChanged;
public Offer()
{
_id = Guid.NewGuid();
}
public void SetTitle(string title)
{
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentNullException(nameof(title));
Title = title;
OnOfferChanged();
}
protected virtual void OnOfferChanged()
{
OfferChanged?.Invoke(this, _id);
}
}
W takim przypadku musiałbyś zmieniać swoje testy, ponieważ został usunięty parametr z metody testowanej. To jest akurat prosty przykład, ale jeżeli przykład byłby bardziej skomplikowany, to prawdopodobieństwo zmiany w metody prywatnej byłoby jeszcze bardziej możliwe. Wtedy te testy byłyby kruche, często wymagałyby aktualizacji, zmian, co na pewno nie jest pożądanym efektem.
PODSUMOWANIE:
Jeżeli metoda jest prywatna, to jest wywoływana w metodzie publicznej (przynajmniej powinno tak być :) ), jeżeli dobrze dobierzemy przypadki testowe, to testując metodę publiczną, przetestujemy również tę metodę prywatną, ponieważ w niej są tylko szczegóły implementacji. Nas interesuje czy dana publiczna składowa działa według założeń, a nie szczegóły jej implementacji. Jeżeli jednak okaże się, że warto byłoby przetestować również inne przypadki testowe, to może to nie powinna być metoda prywatna, może powinna to być metoda publiczna, nawet wydzielona do innej klasy. Traktowana jako osobna jednostka i wtedy jak najbardziej trzeba do niej napisać oddzielne testy. Powinniśmy testować tylko publiczne api, a metody prywatne i chronione są tylko szczegółem implementacji, dla których nie powinniśmy pisać osobnych testów jednostkowych.
Poprzedni artykuł - Test Driven Development: Korzyści ze stosowania TDD na przykładzie w .NET.
Następny artykuł - Szybkie Wprowadzenie do ASP.NET Core.