Jak testujesz prywatne metody za pomocą NUnit?

104

Zastanawiam się, jak prawidłowo korzystać z NUnit. Najpierw utworzyłem oddzielny projekt testowy, który używa mojego projektu głównego jako odniesienia. Ale w takim przypadku nie jestem w stanie przetestować prywatnych metod. Zgadłem, że muszę dołączyć kod testowy do kodu głównego ?! - To nie wydaje się być właściwym sposobem, aby to zrobić. (Nie podoba mi się pomysł wysyłania kodu z testami).

Jak testujesz prywatne metody za pomocą NUnit?

MrFox
źródło

Odpowiedzi:

74

Ogólnie rzecz biorąc, testy jednostkowe odnoszą się do publicznego interfejsu klasy, opierając się na teorii, że implementacja jest nieistotna, o ile wyniki są poprawne z punktu widzenia klienta.

Dlatego NUnit nie zapewnia żadnego mechanizmu testowania niepublicznych członków.

harpo
źródło
1
+1 Właśnie natknąłem się na ten problem iw moim przypadku istnieje algorytm „mapowania”, który ma miejsce pomiędzy prywatnym i publicznym, co oznacza, że ​​gdybym przeprowadzał test jednostkowy publicznie, byłby to w rzeczywistości test integracji. W tym scenariuszu myślę, że próbuje napisać punkty testowe do problemu projektowego w kodzie. w takim przypadku powinienem prawdopodobnie utworzyć inną klasę, która wykonuje tę „prywatną” metodę.
andy
140
Zupełnie nie zgadzam się z tym argumentem. W testach jednostkowych nie chodzi o klasę, chodzi o kod . Gdybym miał klasę z jedną metodą publiczną i dziesięcioma prywatnymi użytymi do utworzenia wyniku metody publicznej, nie mam możliwości zapewnienia, że ​​każda metoda prywatna działa w zamierzony sposób. Mógłbym spróbować stworzyć przypadki testowe w metodzie publicznej, które trafią we właściwą metodę prywatną we właściwy sposób. Ale nie mam pojęcia, czy faktycznie wywołano metodę prywatną, chyba że ufam, że metoda publiczna nigdy się nie zmieni. Metody prywatne mają być prywatne dla użytkowników kodu produkcyjnego, a nie dla autora.
Quango,
3
@Quango, w standardowej konfiguracji testowej, „autorem” jest produkcyjny użytkownik kodu. Niemniej jednak, jeśli nie wyczuwasz problemu z projektowaniem, masz opcje testowania niepublicznych metod bez zmiany klasy. System.Reflectionumożliwia dostęp i wywoływanie metod niepublicznych za pomocą flag wiązania, więc możesz zhakować NUnit lub skonfigurować własną strukturę. Lub (myślę, że łatwiej), możesz ustawić flagę czasu kompilacji (#if TESTOWANIE), aby zmienić modyfikatory dostępu, umożliwiając korzystanie z istniejących frameworków.
harpo
23
Metoda prywatna to szczegół implementacji. Wiele sensu posiadania testów jednostkowych polega na umożliwieniu refaktoryzacji implementacji bez zmiany zachowania . Jeśli metody prywatne stają się tak skomplikowane, że chciałoby się je osobno pokrywać testami, można zastosować lemat: „Metoda prywatna zawsze może być metodą publiczną (lub wewnętrzną) odrębnej klasy”. Oznacza to, że jeśli kawałek logiki jest wystarczająco zaawansowany, aby poddać go testom, prawdopodobnie jest kandydatem do bycia własną klasą z własnymi testami.
Anders Forsgren
6
@AndersForsgren Jednak celem testów jednostkowych , w przeciwieństwie do testów integracyjnych czy funkcjonalnych , jest właśnie testowanie takich szczegółów. Pokrycie kodu jest uważane za ważny miernik testowania jednostkowego, więc w teorii każdy element logiki jest „wystarczająco zaawansowany, aby poddać go testowaniu”.
Kyle Strand
57

Chociaż zgadzam się, że testowanie jednostkowe powinno skupiać się na interfejsie publicznym, uzyskasz znacznie bardziej szczegółowy obraz swojego kodu, jeśli przetestujesz również metody prywatne. Framework testowy MS pozwala na to poprzez użycie PrivateObject i PrivateType, NUnit tego nie robi. Zamiast tego robię:

private MethodInfo GetMethod(string methodName)
{
    if (string.IsNullOrWhiteSpace(methodName))
        Assert.Fail("methodName cannot be null or whitespace");

    var method = this.objectUnderTest.GetType()
        .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);

    if (method == null)
        Assert.Fail(string.Format("{0} method not found", methodName));

    return method;
}

W ten sposób nie musisz rezygnować z enkapsulacji na rzecz testowalności. Pamiętaj, że musisz zmodyfikować BindingFlags, jeśli chcesz przetestować prywatne metody statyczne. Powyższy przykład to tylko przykładowe metody.

user1039513
źródło
7
Testowanie tych małych metod pomocniczych pozwala uchwycić część jednostkową w testowaniu jednostkowym. Nie wszystkie nadają się również do klas użytkowych.
Johan Larsson,
12
Właśnie tego potrzebowałem. Dzięki! Wszystko, czego potrzebowałem, to sprawdzić, czy pole prywatne zostało ustawione poprawnie i NIE musiałem spędzać 3 godzin, aby dowiedzieć się, jak to ustalić za pośrednictwem interfejsu publicznego. To jest istniejący kod, którego nie można zmienić dla kaprysu. To zabawne, jak na proste pytanie można odpowiedzieć idealistycznym dogmatem ...
Droj
5
Powinna to być wybrana odpowiedź, ponieważ jest jedyną, która faktycznie odpowiada na pytanie.
bavaza
6
Zgoda. Wybrana odpowiedź ma swoje zalety, ale jest odpowiedzią na pytanie „czy powinienem przetestować metody prywatne”, a nie na pytanie PO.
chesterbr
2
to działa. dzięki! To właśnie szuka wielu ludzi. To powinna być wybrana odpowiedź,
Able Johnson
47

Typowym wzorcem pisania testów jednostkowych jest testowanie tylko metod publicznych.

Jeśli okaże się, że masz wiele metod prywatnych, które chcesz przetestować, zwykle jest to znak, że należy zreformować kod.

Byłoby błędem upublicznianie tych metod w klasie, w której obecnie mieszkają. To złamałoby kontrakt, który chcesz, aby ta klasa miała.

Poprawne może być przeniesienie ich do klasy pomocników i upublicznienie ich tam. Ta klasa może nie być ujawniona przez Twój interfejs API.

W ten sposób kod testowy nigdy nie jest mieszany z kodem publicznym.

Podobnym problemem jest testowanie zajęć prywatnych tj. klasy, których nie eksportujesz z zestawu. W takim przypadku możesz jawnie uczynić swój zestaw kodu testowego przyjacielem zestawu kodu produkcyjnego przy użyciu atrybutu InternalsVisibleTo.

morechilli
źródło
1
+ 1 tak! Właśnie do takiego wniosku doszedłem podczas pisania testów, dobra rada!
andy
1
Przeniesienie prywatnych metod, które chcesz przetestować, ale nie ujawniać użytkownikom interfejsu API, do innej klasy, która nie jest ujawniona i upublicznić je, jest dokładnie tym, czego szukałem.
Thomas N,
dzięki @morechilli. Nigdy wcześniej nie widziałem InternalsVisibleTo. Znacznie ułatwiło testowanie.
Andrew MacNaughton
Cześć. Niedawno otrzymałem kilka głosów przeciw bez komentarzy. Prześlij opinię, aby wyjaśnić swoje przemyślenia lub obawy.
morechilli
6
Więc jeśli masz prywatną metodę, która jest wywoływana w wielu miejscach w klasie, aby zrobić coś bardzo specyficznego, potrzebnego tylko tej klasie i nie ma być ujawniana jako część publicznego API ... mówisz, że umieść ją w pomocniku klasa tylko do przetestowania? Równie dobrze możesz po prostu upublicznić to dla klasy.
Daniel Macias
21

Możliwe jest przetestowanie metod prywatnych przez zadeklarowanie zestawu testowego jako zestawu znajomego testowanego zestawu docelowego. Zobacz poniższe łącze, aby uzyskać szczegółowe informacje:

http://msdn.microsoft.com/en-us/library/0tke9fxk.aspx

Może to być przydatne, ponieważ w większości przypadków oddziela kod testowy od kodu produkcyjnego. Sam nigdy nie korzystałem z tej metody, ponieważ nigdy nie znalazłem takiej potrzeby. Przypuszczam, że możesz go użyć do wypróbowania ekstremalnych przypadków testowych, których po prostu nie możesz odtworzyć w swoim środowisku testowym, aby zobaczyć, jak obsługuje to Twój kod.

Jak już powiedziano, naprawdę nie powinieneś testować prywatnych metod. Bardziej niż prawdopodobne jest, że chcesz zrefaktoryzować swój kod na mniejsze bloki konstrukcyjne. Jedna wskazówka, która może ci pomóc, gdy przyjdziesz do refaktoryzacji, to spróbować pomyśleć o domenie, do której odnosi się twój system, i pomyśleć o „prawdziwych” obiektach zamieszkujących tę domenę. Twoje obiekty / klasy w twoim systemie powinny odnosić się bezpośrednio do rzeczywistego obiektu, co pozwoli ci wyodrębnić dokładne zachowanie, które powinien zawierać obiekt, a także ograniczy odpowiedzialność za obiekty. Będzie to oznaczać, że refaktoryzujesz się logicznie, a nie tylko po to, aby umożliwić przetestowanie określonej metody; będziesz mógł przetestować zachowanie obiektów.

Jeśli nadal czujesz potrzebę testowania wewnętrznego, możesz również rozważyć mockowanie w testowaniu, ponieważ prawdopodobnie będziesz chciał skupić się na jednym fragmencie kodu. Mockowanie polega na wstrzyknięciu do niego zależności obiektów, ale wstrzyknięte obiekty nie są „rzeczywistymi” ani obiektami produkcyjnymi. Są to pozorne obiekty, których zachowanie zostało zakodowane na stałe, aby ułatwić wyodrębnienie błędów behawioralnych. Rhino.Mocks to popularny, darmowy framework do mockowania, który zasadniczo zapisze obiekty za Ciebie. TypeMock.NET (produkt komercyjny z dostępną edycją społeczności) to bardziej zaawansowana struktura, która może symulować obiekty CLR. Bardzo przydatne do mockowania klas SqlConnection / SqlCommand i Datatable na przykład podczas testowania aplikacji bazy danych.

Mam nadzieję, że ta odpowiedź dostarczy Ci nieco więcej informacji, które pomogą Ci uzyskać ogólne informacje o testach jednostkowych i pomogą Ci uzyskać lepsze wyniki z testów jednostkowych.

Dafydd Giddins
źródło
12
Możliwe jest testowanie metod wewnętrznych , a nie prywatnych (z NUnit).
TrueWill
6

Jestem zwolennikiem możliwości testowania metod prywatnych. Kiedy xUnit został uruchomiony, był przeznaczony do testowania funkcjonalności po napisaniu kodu. W tym celu wystarczy przetestowanie interfejsu.

Testowanie jednostkowe ewoluowało w kierunku programowania opartego na testach. Możliwość testowania wszystkich metod jest przydatna w tej aplikacji.

Mark Glass
źródło
5

To pytanie jest w podeszłym wieku, ale pomyślałem, że podzielę się swoim sposobem zrobienia tego.

Zasadniczo mam wszystkie moje klasy testów jednostkowych w zestawie, który testują, w przestrzeni nazw `` UnitTest '' poniżej `` domyślnej '' dla tego zestawu - każdy plik testowy jest opakowany w:

#if DEBUG

...test code...

#endif

block, a wszystko to oznacza, że ​​a) nie jest rozpowszechniany w wydaniu i b) mogę używać deklaracji internal/ Friendlevel bez przeskakiwania przez pętlę.

Inną rzeczą, którą to oferuje, bardziej związaną z tym pytaniem, jest użycie partialklas, które można wykorzystać do utworzenia proxy do testowania metod prywatnych, na przykład do przetestowania czegoś takiego jak metoda prywatna, która zwraca wartość całkowitą:

public partial class TheClassBeingTested
{
    private int TheMethodToBeTested() { return -1; }
}

w głównych klasach montażu oraz w klasie testowej:

#if DEBUG

using NUnit.Framework;

public partial class TheClassBeingTested
{
    internal int NUnit_TheMethodToBeTested()
    {
        return TheMethodToBeTested();
    }
}

[TestFixture]
public class ClassTests
{
    [Test]
    public void TestMethod()
    {
        var tc = new TheClassBeingTested();
        Assert.That(tc.NUnit_TheMethodToBeTested(), Is.EqualTo(-1));
    }
}

#endif

Oczywiście musisz upewnić się, że nie używasz tej metody podczas programowania, chociaż kompilacja wydania wkrótce wskaże niezamierzone wywołanie jej, jeśli to zrobisz.

Stuart Wood
źródło
4

Głównym celem testów jednostkowych jest testowanie publicznych metod klasy. Te metody publiczne będą wykorzystywać te metody prywatne. Testy jednostkowe sprawdzą zachowanie tego, co jest publicznie dostępne.

Maxime Rouiller
źródło
3

Przepraszamy, jeśli to nie odpowiada na pytanie, ale rozwiązania takie jak użycie refleksji, stwierdzeń #if #endif lub pokazanie prywatnych metod nie rozwiązują problemu. Może być kilka powodów, dla których metody prywatne nie są widoczne ... co, jeśli jest to kod produkcyjny, a zespół na przykład retrospektywnie pisze testy jednostkowe.

W przypadku projektu, nad którym pracuję, tylko MSTest (niestety) wydaje się mieć sposób, przy użyciu akcesorów, na testowanie prywatnych metod jednostkowych.

Ritesh
źródło
2

Nie testujesz funkcji prywatnych. Istnieją sposoby wykorzystania refleksji, aby dostać się do prywatnych metod i właściwości. Ale to nie jest łatwe i zdecydowanie odradzam tę praktykę.

Po prostu nie powinieneś testować niczego, co nie jest publiczne.

Jeśli masz jakieś wewnętrzne metody i właściwości, powinieneś rozważyć zmianę ich na publiczne lub wysłanie testów z aplikacją (coś, co tak naprawdę nie widzę jako problemu).

Jeśli Twój klient jest w stanie uruchomić Test-Suite i widzi, że dostarczony przez Ciebie kod faktycznie „działa”, nie widzę w tym problemu (o ile nie podasz w ten sposób swojego adresu IP). W każdym wydaniu zamieszczam raporty z testów i raporty pokrycia kodu.

Tigraine
źródło
Niektórzy klienci starają się ograniczyć budżet i rozpoczynają dyskusje na temat zakresu testów. Trudno wytłumaczyć tym ludziom, że pisanie testów nie jest spowodowane tym, że jesteś złym programistą i nie ufasz własnym umiejętnościom.
MrFox
1

W teorii testów jednostkowych należy testować tylko kontrakt. tzn. tylko publiczni członkowie klasy. Ale w praktyce deweloper zwykle chce przetestować członków wewnętrznych. - i nie jest źle. Tak, jest to sprzeczne z teorią, ale w praktyce czasami może się przydać.

Jeśli więc naprawdę chcesz przetestować członków wewnętrznych, możesz użyć jednego z następujących podejść:

  1. Upublicznij swojego członka. W wielu książkach autorzy sugerują, że takie podejście jest proste
  2. Możesz uczynić swoich członków wewnętrznymi i dodać InternalVisibleTo do assebly
  3. Możesz zabezpieczyć członków klasy i dziedziczyć klasę testową po klasie, która jest w trakcie testów.

Przykład kodu (pseudokod):

public class SomeClass
{
    protected int SomeMethod() {}
}
[TestFixture]
public class TestClass : SomeClass{

    protected void SomeMethod2() {}
    [Test]
    public void SomeMethodTest() { SomeMethod2(); }
}
burzhuy
źródło
Wygląda na to, że karaluch jest ukryty w twoim przykładowym kodzie;) Metoda wewnątrz urządzenia NUnit musi być publiczna, w przeciwnym razie otrzymasz Message: Method is not public.
Gucu112
@ Gucu112 Thanks. Naprawiłem to. Ogólnie nie ma to znaczenia, ponieważ celem jest pokazanie podejścia z punktu widzenia projektowania.
burzhuy
1

Możesz uczynić swoje metody chronionymi wewnętrznymi, a następnie użyć assembly: InternalsVisibleTo("NAMESPACE") do testowej przestrzeni nazw.

Dlatego NIE! Nie możesz uzyskać dostępu do metod prywatnych, ale jest to obejście.

Furgalicious
źródło
0

Uczyniłbym widoczny pakiet metod prywatnych. W ten sposób zachowasz rozsądną prywatność, a jednocześnie będziesz w stanie przetestować te metody. Nie zgadzam się z ludźmi, którzy twierdzą, że interfejsy publiczne są jedynymi, które powinny być testowane. W metodach prywatnych często znajduje się naprawdę krytyczny kod, którego nie można poprawnie przetestować, przechodząc tylko przez zewnętrzne interfejsy.

Więc to naprawdę sprowadza się do tego, czy bardziej zależy ci na poprawnym kodzie lub ukrywaniu informacji. Powiedziałbym, że widoczność pakietu jest dobrym kompromisem, ponieważ aby uzyskać dostęp do tej metody, ktoś musiałby umieścić swoją klasę w Twoim pakiecie. To powinno naprawdę skłonić ich do zastanowienia się, czy to naprawdę mądra rzecz.

Tak przy okazji, jestem facetem od Javy, więc widoczność pakietu można nazwać czymś zupełnie innym w C #. Wystarczy powiedzieć, że dwie klasy muszą znajdować się w tej samej przestrzeni nazw, aby uzyskać dostęp do tych metod.

Fylke
źródło