Jak przeprowadzasz testy jednostkowe metod prywatnych?

479

Buduję bibliotekę klas, która będzie miała kilka publicznych i prywatnych metod. Chcę mieć możliwość testowania jednostkowego metod prywatnych (głównie podczas opracowywania, ale może to również być przydatne do przyszłego refaktoryzacji).

Jaki jest właściwy sposób to zrobić?

Eric Labashosky
źródło
3
Być może czegoś mi brakuje, a może chodzi tylko o to, że to pytanie jest… cóż, pre-historicjeśli chodzi o lata internetowe, ale testowanie jednostkowe metod prywatnych jest teraz zarówno łatwe, jak i bezpośrednie, a Visual Studio produkuje w razie potrzeby niezbędne klasy akcesoriów. wstępne wypełnianie logiki testów fragmentami cholernie zbliżonymi do tego, czego można chcieć od prostych testów funkcjonalnych. Zobacz np. msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx
mjv
3
Wydaje się niemal duplikat stackoverflow.com/questions/34571/... .
Raedwald
Pytający może nie korzystać ze studia wizualnego
Dave
3
Nie testuj wewnętrznych elementów: blog.ploeh.dk/2015/09/22/unit-testing-internals
Mark Seemann

Odpowiedzi:

122

Jeśli używasz .net, powinieneś użyć InternalsVisibleToAttribute .

TcKs
źródło
86
Fuj Zostanie to wkompilowane w twoje zwolnione zespoły.
Jay
14
@Jay - czy nie można użyć #if DEBUGtego InternalsVisibleToatrybutu, aby nie dotyczył kodu wersji?
mpontillo
20
@Mike, możesz, ale wtedy możesz uruchamiać testy jednostkowe tylko dla kodu debugowania, a nie kodu wydania. Ponieważ kod wersji jest zoptymalizowany, możesz zobaczyć inne zachowanie i różne czasy. W kodzie wielowątkowym oznacza to, że testy jednostkowe nie wykrywają odpowiednio warunków wyścigu. Znacznie lepiej jest użyć odbicia za pomocą sugestii @ AmazedSaint poniżej lub użyć wbudowanego PrivateObject / PrivateType. Pozwala to zobaczyć szereg prywatny w kompilacjach wersji, przy założeniu, że twoja wiązka testowa działa z pełnym zaufaniem (co MSTest działa lokalnie)
Jay
121
czego mi brakuje? Dlaczego miałaby to być akceptowana odpowiedź, skoro w rzeczywistości nie odpowiada na konkretne pytanie dotyczące testowania metod prywatnych? InternalsVisibleTo ujawnia tylko metody oznaczone jako wewnętrzne, a nie te oznaczone jako prywatne zgodnie z żądaniem PO (i powód, dla którego tu wylądowałem). Chyba muszę kontynuować używanie PrivateObject, na co odpowiedział Seven?
Darren Lewis
6
@Jay Wiem, że to trochę późno przychodzi, ale jedną z opcji jest użycie czegoś podobnego #if RELEASE_TESTwokół InternalsVisibleTojak Mike sugeruje, i zrobić kopię konfiguracji kompilacji uwalniania, który definiuje RELEASE_TEST. Możesz przetestować swój kod wersji z optymalizacjami, ale kiedy faktycznie budujesz dla wydania, twoje testy zostaną pominięte.
Shaz
349

Jeśli chcesz przetestować metodą prywatną metodę prywatną, coś może być nie tak. Testy jednostkowe (ogólnie) mają na celu przetestowanie interfejsu klasy, co oznacza jej publiczne (i chronione) metody. Możesz oczywiście „zhakować” rozwiązanie tego problemu (nawet jeśli tylko upublicznisz metody), ale możesz też rozważyć:

  1. Jeśli metoda, którą chcesz przetestować, naprawdę jest warta przetestowania, warto przenieść ją do własnej klasy.
  2. Dodaj więcej testów do metod publicznych, które wywołują metodę prywatną, testując funkcjonalność metody prywatnej. (Jak wskazali komentatorzy, powinieneś to zrobić tylko wtedy, gdy funkcjonalność tych prywatnych metod jest naprawdę częścią publicznego interfejsu. Jeśli faktycznie wykonują funkcje ukryte przed użytkownikiem (tj. Test jednostkowy), prawdopodobnie jest to złe).
Jeroen Heijmans
źródło
38
Opcja 2 sprawia, że ​​testy jednostkowe muszą mieć wiedzę na temat podstawowej implementacji funkcji. Nie lubię tego robić. Ogólnie myślę, że testy jednostkowe powinny testować funkcję bez zakładania niczego o implementacji.
Herms
15
Wady testowania implementacji polegają na tym, że testy będą kruche, jeśli zostaną wprowadzone jakiekolwiek zmiany w implementacji. Jest to niepożądane, ponieważ refaktoryzacja jest równie ważna jak pisanie testów w TDD.
JtR
30
Cóż, testy powinny się przerwać, jeśli zmienisz implementację. TDD oznaczałoby najpierw zmianę testów.
śleske,
39
@sleske - Nie do końca się zgadzam. Jeśli funkcjonalność się nie zmieniła, nie ma powodu, by test miał się zepsuć, ponieważ testy powinny być testowaniem zachowania / stanu, a nie implementacji. Właśnie to miał na myśli jtr, sprawiając, że twoje testy były kruche. W idealnym świecie powinieneś być w stanie refaktoryzować swój kod i nadal mieć zaliczone testy, sprawdzając, czy refaktoryzacja nie zmieniła funkcjonalności twojego systemu.
Alconja
26
Przepraszam za naiwność (jak dotąd mało doświadczenia w testowaniu), ale czy nie jest pomysł testowania jednostkowego w celu przetestowania każdego modułu kodu na własną rękę? Naprawdę nie rozumiem, dlaczego prywatne metody powinny być wyłączone z tego pomysłu.
OMill,
118

Testowanie metod prywatnych może nie być przydatne. Jednak czasami lubię też nazywać metody prywatne metodami testowymi. Przez większość czasu, aby zapobiec duplikacji kodu do generowania danych testowych ...

Microsoft udostępnia w tym celu dwa mechanizmy:

Akcesoria

  • Przejdź do kodu źródłowego definicji klasy
  • Kliknij nazwę klasy prawym przyciskiem myszy
  • Wybierz „Utwórz prywatny akcesor”
  • Wybierz projekt, w którym ma zostać utworzony akcesor => Otrzymasz nową klasę o nazwie foo_accessor. Ta klasa będzie generowana dynamicznie podczas kompilacji i upublicznia wszystkich członków publicznie dostępnych.

Jednak mechanizm ten jest czasem nieco trudny do rozwiązania, jeśli chodzi o zmiany interfejsu oryginalnej klasy. Tak więc przez większość czasu unikam tego.

Klasa PrivateObject Innym sposobem jest użycie Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject

// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );

// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );

// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );
Siedem
źródło
2
Jak wywołujesz prywatne metody statyczne?
StuperUser
18
Prywatne akcesoria są przestarzałe w Visual Studio 2012 .
Ryan Gates
3
Metoda akcesora do testowania metod prywatnych jest przestarzała od wersji VS 2011. blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…
Sanjit Misra
1
Czytając dokumenty znalezione na stronie Microsoftu tu nie widzę żadnej wzmianki klasy PrivateObject jest przestarzała. Używam MSVS 2013 i działa zgodnie z oczekiwaniami.
stackunderflow
3
@RyanGates Pierwsze rozwiązanie w witrynie, do której się odwołujesz, stwierdza, że ​​rozwiązaniem dostępu do prywatnych członków jest „Użyj klasy PrivateObject, aby uzyskać dostęp do wewnętrznych i prywatnych interfejsów API w kodzie. Można to znaleźć w Microsoft.VisualStudio.QualityTools.UnitTestFramework. dll assembly. ”
stackunderflow
78

Nie zgadzam się z filozofią „powinieneś być zainteresowany jedynie testowaniem interfejsu zewnętrznego”. To trochę tak, jakby powiedzieć, że warsztat samochodowy powinien mieć tylko testy, aby sprawdzić, czy koła się obracają. Tak, ostatecznie interesuje mnie zachowanie zewnętrzne, ale podoba mi się to, że moje własne, wewnętrzne testy są bardziej szczegółowe i do rzeczy. Tak, jeśli dokonam refaktoryzacji, być może będę musiał zmienić niektóre testy, ale chyba że jest to ogromny refaktor, będę musiał zmienić tylko kilka, a fakt, że inne (niezmienione) testy wewnętrzne nadal działają, jest doskonałym wskaźnikiem, że refaktoryzacja zakończyła się powodzeniem.

Możesz spróbować objąć wszystkie przypadki wewnętrzne przy użyciu tylko interfejsu publicznego i teoretycznie możliwe jest przetestowanie każdej metody wewnętrznej (lub przynajmniej każdej, która ma znaczenie) całkowicie za pomocą interfejsu publicznego, ale może być konieczne stanie się na głowie, aby osiągnąć to i połączenie między przypadkami testowymi uruchamianymi przez interfejs publiczny a wewnętrzną częścią rozwiązania, które mają przetestować, może być trudne lub niemożliwe do rozpoznania. Po wskazaniu, indywidualne testy, które gwarantują, że wewnętrzna maszyna działa poprawnie, są warte drobnych zmian testowych, które pojawiają się przy refaktoryzacji - przynajmniej takie było moje doświadczenie. Jeśli musisz wprowadzić ogromne zmiany w swoich testach dla każdego refaktoryzacji, to może nie ma to sensu, ale w takim przypadku może powinieneś całkowicie przemyśleć swój projekt.

Darrell Plank
źródło
20
Obawiam się, że nadal się z tobą nie zgadzam. Traktowanie każdego elementu jako czarnej skrzynki pozwala na bezproblemową zamianę / wyjęcie modułów. Jeśli masz coś FooServicedo zrobienia X, powinieneś się tylko martwić, że tak naprawdę dzieje się na Xżądanie. Jak to nie powinno mieć znaczenia. Jeśli występują problemy w klasie, których nie można rozpoznać za pośrednictwem interfejsu (mało prawdopodobne), nadal jest to poprawne FooService. Jeśli jest to problem, który jest widoczny poprzez interfejs, test na członków publicznych powinien go wykryć. Chodzi o to, że dopóki koło obraca się prawidłowo, może być używane jako koło.
Podstawowy
5
Jednym ogólnym podejściem jest to, że jeśli twoja logika wewnętrzna jest na tyle skomplikowana, że ​​uważasz, że wymaga ona testów jednostkowych, być może trzeba ją wyodrębnić do jakiegoś rodzaju klasy pomocniczej z publicznym interfejsem, który może być testowany jednostkowo. Wówczas klasa „nadrzędna” może po prostu skorzystać z tego pomocnika i każdy może zostać odpowiednio przetestowany jednostkowo.
Mir
9
@Basic: Całkowicie błędna logika w tej odpowiedzi. Klasycznym przypadkiem, gdy potrzebujesz metody prywatnej, jest to, że potrzebujesz kodu do ponownego wykorzystania metodami publicznymi. Umieszczasz ten kod w jakiejś PrivMethod. Ta metoda nie powinna być narażona na publiczne, ale wymaga przetestowania, aby upewnić się, że publiczne metody, które opierają się na PrivMethod, naprawdę mogą na niej polegać.
Dima,
6
@Dima Z pewnością więc, jeśli jest problem PrivMethod, test, na PubMethodktóry połączenia PrivMethodpowinny go ujawnić? Co się stanie, gdy zmienisz swój SimpleSmtpServicew GmailService? Nagle twoje prywatne testy wskazują na kod, który już nie istnieje lub może działa inaczej i nie powiedzie się, nawet jeśli aplikacja może działać idealnie zgodnie z przeznaczeniem. Jeśli istnieje skomplikowane przetwarzanie, które dotyczyłoby obu nadawców e-maili, być może powinno być w takim, EmailProcessorktóre może być używane przez oba i testowane osobno?
Podstawowy
2
@miltonb Możemy patrzeć na to z różnych stylów programowania. WRT elementy wewnętrzne, nie mam tendencji do testowania ich przez jednostki. Jeśli występuje problem (zidentyfikowany w testach interfejsu), albo łatwo go wyśledzić, dołączając debugger, albo klasa jest zbyt złożona i powinna zostać podzielona (z testowanym interfejsem publicznym nowej jednostki klasy) IMHO
Basic
51

W rzadkich przypadkach chciałem przetestować funkcje prywatne, zwykle zmodyfikowałem je, aby były chronione, i napisałem podklasę z funkcją publicznego pakowania.

Klasa:

...

protected void APrivateFunction()
{
    ...
}

...

Podklasa do testowania:

...

[Test]
public void TestAPrivateFunction()
{
    APrivateFunction();
    //or whatever testing code you want here
}

...
Jason Jackson
źródło
1
Możesz nawet umieścić tę klasę potomną w pliku testu jednostkowego zamiast zaśmiecać prawdziwą klasę. +1 za przebiegłość.
Tim Abell,
W miarę możliwości zawsze umieszczam cały kod związany z testem w projekcie testów jednostkowych. To był tylko kod psuedo.
Jason Jackson,
2
Ta funkcja nie jest prywatna, jest chroniona, a wynik netto ... sprawiłeś, że Twój kod jest mniej bezpieczny / narażony na prywatne funkcje typów potomnych
War
22

Myślę, że bardziej fundamentalnym pytaniem powinno być to, dlaczego próbujesz w pierwszej kolejności przetestować metodę prywatną. Jest to zapach kodu, który próbujesz przetestować metodę prywatną za pomocą publicznego interfejsu tej klasy, podczas gdy ta metoda jest prywatna z jakiegoś powodu, ponieważ jest szczegółem implementacji. Należy się tylko przejmować zachowaniem interfejsu publicznego, a nie jego implementacją pod przykryciem.

Jeśli chcę przetestować zachowanie metody prywatnej, używając wspólnych refaktoryzacji, mogę wyodrębnić jej kod do innej klasy (być może z widocznością na poziomie pakietu, więc upewnij się, że nie jest to część publicznego API). Następnie mogę przetestować jego zachowanie w izolacji.

Produkt refaktoryzacji oznacza, że ​​metoda prywatna jest teraz oddzielną klasą, która stała się współpracownikiem oryginalnej klasy. Jego zachowanie zostanie dobrze poznane dzięki własnym testom jednostkowym.

Następnie mogę kpić z jego zachowania, gdy próbuję przetestować oryginalną klasę, dzięki czemu mogę skoncentrować się na przetestowaniu zachowania publicznego interfejsu tej klasy, zamiast testować kombinatoryczną eksplozję publicznego interfejsu i zachowanie wszystkich jego prywatnych metod .

Widzę to analogicznie do prowadzenia samochodu. Kiedy prowadzę samochód, nie jeżdżę z podniesioną maską, więc widzę, że silnik działa. Polegam na interfejsie, który zapewnia samochód, a mianowicie na obrotomierzu i prędkościomierzu, aby wiedzieć, że silnik działa. Polegam na tym, że samochód faktycznie porusza się po naciśnięciu pedału gazu. Jeśli chcę przetestować silnik, mogę to sprawdzić osobno. :RE

Oczywiście bezpośrednie testowanie metod prywatnych może być ostatecznością, jeśli masz starszą aplikację, ale wolałbym, aby starszy kod został ponownie przekształcony, aby umożliwić lepsze testowanie. Michael Feathers napisał świetną książkę na ten właśnie temat. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052

Big Kahuna
źródło
16
Całkowicie błędna logika w tej odpowiedzi. Klasycznym przypadkiem, gdy potrzebujesz metody prywatnej, jest to, że potrzebujesz kodu do ponownego wykorzystania metodami publicznymi. Umieszczasz ten kod w jakiejś PrivMethod. Ta metoda nie powinna być narażona na publiczne, ale wymaga przetestowania, aby upewnić się, że publiczne metody, które opierają się na PrivMethod, naprawdę mogą na niej polegać.
Dima,
Ma to sens podczas początkowego rozwoju, ale czy chcesz testy dla metod prywatnych w standardowym kolorze regresji? Jeśli tak, jeśli implementacja ulegnie zmianie, może uszkodzić pakiet testowy. OTOH, jeśli twoje testy regresji skupiają się tylko na zewnętrznie widocznych metodach publicznych, to jeśli metoda prywatna zepsuje się później, pakiet regresji powinien nadal wykryć błąd. W razie potrzeby możesz odkurzyć stary prywatny test, jeśli to konieczne.
Alex Blakemore,
9
Nie zgadzam się, powinieneś testować tylko interfejs publiczny, w przeciwnym razie dlaczego potrzebujesz prywatnych metod. W takim przypadku ustaw je wszystkie jako publiczne i przetestuj je wszystkie. Jeśli testujesz metody prywatne, łamiesz zasadzkę. Jeśli chcesz przetestować metodę prywatną i jest ona używana w wielu metodach publicznych, powinna zostać przeniesiona do własnej klasy i przetestowana w izolacji. Wszystkie metody publiczne powinny następnie przekazać tę nową klasę. W ten sposób nadal masz testy dla interfejs oryginalnej klasy. Możesz sprawdzić, czy zachowanie się nie zmieniło, i masz osobne testy dla delegowanej metody prywatnej.
Big Kahuna,
@Big Kahuna - Jeśli uważasz, że nie ma potrzeby, aby testować prywatne metody jednostkowe, nigdy nie pracowałeś z wystarczająco dużym / złożonym projektem. Wiele razy funkcja publiczna, taka jak walidacja specyficzna dla klienta, kończy się na 20 wierszach wywołujących bardzo proste metody prywatne, aby kod był bardziej czytelny, ale nadal musisz przetestować każdą metodę prywatną. Testowanie 20 razy funkcji publicznej bardzo utrudni debiut w przypadku niepowodzenia testów jednostkowych.
Pedro.The.Kid
1
Pracuję dla firmy FTSE 100. Myślę, że widziałem kilka złożonych projektów w swoim czasie, dziękuję. Jeśli musisz przetestować na tym poziomie, każda metoda prywatna jako osobny współpracownik powinna zostać przetestowana w oderwaniu, ponieważ oznacza to, że mają indywidualne zachowanie, które wymaga przetestowania. Test głównego obiektu mediatora staje się testem interakcji. Wystarczy przetestować, czy wywoływana jest właściwa strategia. Twój scenariusz brzmi, jakby klasa nie przestrzegała SRP. Nie ma ani jednego powodu do zmiany, ale naruszenie 20 => SRP. Przeczytaj książkę GOOS lub wujka Boba. YMWV
Big Kahuna,
17

Typy prywatne, wewnętrzne i prywatni członkowie są z jakiegoś powodu i często nie chcesz z nimi zadzierać bezpośrednio. A jeśli to zrobisz, są szanse, że później zepsujesz, ponieważ nie ma gwarancji, że faceci, którzy utworzyli te zespoły, zachowają prywatne / wewnętrzne implementacje jako takie.

Ale czasami, gdy robię jakieś włamania / eksploracje zestawień skompilowanych lub zestawów stron trzecich, sam chciałem zainicjować klasę prywatną lub klasę z konstruktorem prywatnym lub wewnętrznym. Lub czasami, gdy mam do czynienia ze wstępnie skompilowanymi starszymi bibliotekami, których nie mogę zmienić - w końcu piszę testy przeciwko prywatnej metodzie.

Tak narodził się AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - jest to klasa szybkiego która ułatwi zadanie dzięki dynamicznym funkcjom i refleksji C # 4.0.

Możesz tworzyć typy wewnętrzne / prywatne, takie jak

    //Note that the wrapper is dynamic
    dynamic wrapper = AccessPrivateWrapper.FromType
        (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");

    //Access the private members
    wrapper.PrivateMethodInPrivateClass();
zdumiony święty
źródło
12

Możesz przetestować prywatną metodę na dwa sposoby

  1. możesz utworzyć instancję PrivateObjectklasy, składnia jest następująca

    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
  2. Możesz użyć odbicia.

    PrivateClass obj = new PrivateClass(); // Class containing private obj
    Type t = typeof(PrivateClass); 
    var x = t.InvokeMember("PrivateFunc", 
        BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |  
            BindingFlags.Instance, null, obj, new object[] { 5 });
Nieznany
źródło
Dobra odpowiedź, ale dla nr 1 twoja składnia jest nieprawidłowa. Musisz zadeklarować wystąpienie PrivateClasspierwszego i użyć go. stackoverflow.com/questions/9122708/…
SharpC
10

Użyłem również metody InternalsVisibleToAttribute. Warto również wspomnieć, że jeśli czujesz się niekomfortowo, wprowadzając wewnętrzne metody prywatne, aby to osiągnąć, być może nie powinny one być przedmiotem bezpośrednich testów jednostkowych.

W końcu testujesz zachowanie swojej klasy, a nie jej konkretną implementację - możesz zmienić drugą bez zmiany poprzedniej, a twoje testy powinny zostać zaliczone.

philsquared
źródło
Podoba mi się kwestia testowania zachowania, a nie implementacji. Jeśli powiązasz testy jednostkowe z implementacją (metody prywatne), wówczas testy staną się kruche i będą musiały ulec zmianie, gdy zmieni się implementacja.
Bob Horn
9

Istnieją 2 rodzaje metod prywatnych. Statyczne metody prywatne i niepasujące metody prywatne (metody instancji). Poniższe 2 artykuły wyjaśniają, w jaki sposób testować jednostkowe metody prywatne na przykładach.

  1. Testowanie jednostkowe statycznych metod prywatnych
  2. Testy jednostkowe Niestatyczne metody prywatne
Venkat
źródło
Podaj kilka przykładów, nie tylko podaj link
vinculis,
Wygląda brzydko. Brak inteligencji. Złe rozwiązanie od stwardnienia rozsianego. Jestem w szoku !
alerya
Najłatwiejszy sposób na przetestowanie prywatnych metod statycznych
Grant
8

MS Test ma wbudowaną ciekawą funkcję, która udostępnia prywatnych członków i metody w projekcie, tworząc plik o nazwie VSCodeGenAccessors

[System.Diagnostics.DebuggerStepThrough()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
    internal class BaseAccessor
    {

        protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;

        protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
        {
            m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
        }

        protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
            :
                this(null, type)
        {
        }

        internal virtual object Target
        {
            get
            {
                return m_privateObject.Target;
            }
        }

        public override string ToString()
        {
            return this.Target.ToString();
        }

        public override bool Equals(object obj)
        {
            if (typeof(BaseAccessor).IsInstanceOfType(obj))
            {
                obj = ((BaseAccessor)(obj)).Target;
            }
            return this.Target.Equals(obj);
        }

        public override int GetHashCode()
        {
            return this.Target.GetHashCode();
        }
    }

Z klasami wywodzącymi się z BaseAccessor

Jak na przykład

[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{

    protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));

    internal SomeClassAccessor(global::Namespace.Someclass target)
        : base(target, m_privateType)
    {
    }

    internal static string STATIC_STRING
    {
        get
        {
            string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
            return ret;
        }
        set
        {
            m_privateType.SetStaticField("STATIC_STRING", value);
        }
    }

    internal int memberVar    {
        get
        {
            int ret = ((int)(m_privateObject.GetField("memberVar")));
            return ret;
        }
        set
        {
            m_privateObject.SetField("memberVar", value);
        }
    }

    internal int PrivateMethodName(int paramName)
    {
        object[] args = new object[] {
            paramName};
        int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
                typeof(int)}, args)));
        return ret;
    }
Marcus King
źródło
8
Pliki gen'd istnieją tylko w VS2005. W 2008 roku są generowane za kulisami. I są obrzydliwością. A powiązane zadanie Cienia jest niestabilne na serwerze kompilacji.
Ruben Bartelink
Również akcesoria były przestarzałe w VS2012-2013.
Zephan Schroeder
5

Na CodeProject znajduje się artykuł, który krótko omawia zalety i wady testowania metod prywatnych. Następnie udostępnia kod refleksyjny umożliwiający dostęp do metod prywatnych (podobny do kodu podanego powyżej przez Marcusa). Jedyny problem, jaki znalazłem w próbce, to to, że kod nie uwzględnia przeciążonych metod.

Artykuł znajdziesz tutaj:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

Pedro
źródło
4

Zadeklaruj je internal, a następnie użyj, InternalsVisibleToAttributeaby pozwolić zespołowi testowemu urządzenia je zobaczyć.

James Curran
źródło
11
Nie lubię używać InternalsVisibleTo, ponieważ z jakiegoś powodu ustawiłem metodę jako prywatną.
swilliams
4

Nie używam dyrektyw kompilatora, ponieważ szybko zaśmiecają wszystko. Jednym ze sposobów na złagodzenie tego, jeśli naprawdę ich potrzebujesz, jest umieszczenie ich w częściowej klasie, a twoja kompilacja zignoruje ten plik .cs podczas tworzenia wersji produkcyjnej.

Swilliams
źródło
Dołącz testowe akcesoria do wersji produkcyjnej (w celu przetestowania optymalizacji kompilatora itp.), Ale wyklucz je z wersji. Ale dzielę włosy i i tak to głosowałem, ponieważ uważam, że dobrym pomysłem jest umieszczenie tych rzeczy w jednym miejscu. Dzięki za pomysł.
CAD bloke
4

Nie powinieneś testować prywatnych metod swojego kodu. Powinieneś testować „interfejs publiczny” lub API, publiczne rzeczy swoich klas. Interfejs API to wszystkie metody publiczne, które udostępniasz zewnętrznym rozmówcom.

Powodem jest to, że gdy zaczniesz testować prywatne metody i elementy wewnętrzne swojej klasy, łączysz implementację swojej klasy (rzeczy prywatne) z testami. Oznacza to, że kiedy zdecydujesz się zmienić szczegóły implementacji, będziesz również musiał zmienić swoje testy.

Z tego powodu należy unikać używania InternalsVisibleToAtrribute.

Oto świetna rozmowa Iana Coopera na ten temat: Ian Cooper: TDD, gdzie wszystko poszło nie tak

cda01
źródło
3

Czasami dobrze jest przetestować prywatne deklaracje. Zasadniczo kompilator ma tylko jedną metodę publiczną: Compile (string outputFileName, params string [] sourceSFileNames). Jestem pewien, że rozumiesz, że trudno byłoby przetestować taką metodę bez testowania każdej „ukrytej” deklaracji!

Dlatego stworzyliśmy Visual T #: aby ułatwić testy. Jest to darmowy język programowania .NET (zgodny z C # v2.0).

Dodaliśmy operatora „.-”. Zachowuje się jak „”. operator, z wyjątkiem tego, że możesz również uzyskać dostęp do dowolnej ukrytej deklaracji z testów bez zmiany czegokolwiek w testowanym projekcie.

Zajrzyj na naszą stronę internetową: pobierzza darmo .

Ludovic Dubois
źródło
3

Dziwię się, że nikt tego jeszcze nie powiedział, ale rozwiązaniem, które zastosowałem, jest stworzenie statycznej metody wewnątrz klasy w celu przetestowania się. Daje to dostęp do wszystkiego, co publiczne i prywatne do przetestowania.

Ponadto w języku skryptowym (z funkcjami OO, takimi jak Python, Ruby i PHP), możesz wykonać test pliku po uruchomieniu. Fajny szybki sposób upewnienia się, że twoje zmiany niczego nie zepsują. To oczywiście tworzy skalowalne rozwiązanie do testowania wszystkich twoich klas: po prostu uruchom je wszystkie. (możesz to również zrobić w innych językach z pustą nazwą główną, która zawsze uruchamia również swoje testy).

wiejski prostak
źródło
1
Chociaż jest to praktyczne, nie jest zbyt eleganckie. Może to spowodować bałagan w bazie kodu, a także nie pozwala oddzielić testów od prawdziwego kodu. Możliwość zewnętrznego testowania otwiera możliwość automatycznego testowania skryptów zamiast ręcznego pisania metod statycznych.
Darren Reid
Nie wyklucza to testowania zewnętrznego ... wystarczy wywołać metodę statyczną, jak chcesz. Baza kodu również nie jest niechlujna ... odpowiednio nazwij metodę. Używam „runTests”, ale cokolwiek podobnego działa.
rube
Masz rację, nie wyklucza to testowania zewnętrznego, jednak generuje o wiele więcej kodu, tj. Powoduje, że baza kodu jest nieporządna. Każda klasa może mieć wiele prywatnych metod do testowania, które inicjują zmienne w jednym lub kilku konstruktorach. Aby przetestować, będziesz musiał napisać co najmniej tyle metod statycznych, ile jest metod do testowania, a metody testowe mogą być duże, aby zainicjować prawidłowe wartości. Utrudniłoby to utrzymanie kodu. Jak powiedzieli inni, testowanie zachowania klasy jest lepszym podejściem, reszta powinna być na tyle mała, aby debugować.
Darren Reid
Używam tej samej liczby linii do testowania, jak każdy inny (w rzeczywistości mniej, jak będziesz czytać później). Nie musisz testować WSZYSTKICH swoich prywatnych metod. Tylko te, które wymagają testowania :) Nie musisz też testować każdego z osobna metodą. Robię to za pomocą jednego połączenia. To faktycznie utrudnia utrzymanie kodu MNIEJ, ponieważ wszystkie moje klasy mają tę samą metodę testowania jednostek parasolowych, która uruchamia wszystkie prywatne i chronione testy jednostkowe linia po linii. Cała uprząż testowa następnie wywołuje tę samą metodę na wszystkich moich klasach, a utrzymanie jest w mojej klasie - testy i wszystkie.
rube
3

Chcę utworzyć tutaj czytelny przykład kodu, którego można użyć w dowolnej klasie, w której chcesz przetestować metodę prywatną.

W klasie przypadków testowych po prostu uwzględnij te metody, a następnie zastosuj je, jak wskazano.

  /**
   *
   * @var Class_name_of_class_you_want_to_test_private_methods_in
   * note: the actual class and the private variable to store the 
   * class instance in, should at least be different case so that
   * they do not get confused in the code.  Here the class name is
   * is upper case while the private instance variable is all lower
   * case
   */
  private $class_name_of_class_you_want_to_test_private_methods_in;

  /**
   * This uses reflection to be able to get private methods to test
   * @param $methodName
   * @return ReflectionMethod
   */
  protected static function getMethod($methodName) {
    $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
    $method = $class->getMethod($methodName);
    $method->setAccessible(true);
    return $method;
  }

  /**
   * Uses reflection class to call private methods and get return values.
   * @param $methodName
   * @param array $params
   * @return mixed
   *
   * usage:     $this->_callMethod('_someFunctionName', array(param1,param2,param3));
   *  {params are in
   *   order in which they appear in the function declaration}
   */
  protected function _callMethod($methodName, $params=array()) {
    $method = self::getMethod($methodName);
    return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
  }

$ this -> _ callMethod ('_ someFunctionName', tablica (param1, param2, param3));

Wystarczy wydać parametry w kolejności, w jakiej pojawiają się w oryginalnej funkcji prywatnej

Damon Hogan
źródło
3

Dla każdego, kto chce uruchamiać prywatne metody bez zgiełku i bałaganu. Działa to z dowolną strukturą testowania jednostkowego wykorzystującą tylko dobre stare odbicie.

public class ReflectionTools
{
    // If the class is non-static
    public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
    {
        Type t = objectUnderTest.GetType();
        return t.InvokeMember(method,
            BindingFlags.InvokeMethod |
            BindingFlags.NonPublic |
            BindingFlags.Instance |
            BindingFlags.Static,
            null,
            objectUnderTest,
            args);
    }
    // if the class is static
    public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
    {
        MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
        foreach(var member in members)
        {
            if (member.Name == method)
            {
                return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
            }
        }
        return null;
    }
}

Następnie w swoich rzeczywistych testach możesz zrobić coś takiego:

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    typeof(StaticClassOfMethod), 
    "PrivateMethod"), 
  "Expected Result");

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    new ClassOfMethod(), 
    "PrivateMethod"), 
  "Expected Result");
Erick Stone
źródło
2

MbUnit ma ładne opakowanie dla tego, zwanego Odbłyśnikiem.

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

Możesz także ustawić i uzyskać wartości z właściwości

dogReflector.GetProperty("Age");

W odniesieniu do „testu prywatnego” zgadzam się, że… w idealnym świecie. nie ma sensu przeprowadzać prywatnych testów jednostkowych. Ale w prawdziwym świecie możesz chcieć napisać prywatne testy zamiast refaktoryzować kod.

Carl Bergquist
źródło
4
Tylko dla informacji, Reflectorzostał zastąpiony przez potężniejszy Mirrorw Gallio / MbUnit v3.2. ( gallio.org/wiki/doku.php?id=mbunit:mirror )
Yann Trevin
2

Oto dobry artykuł na temat testowania jednostkowego metod prywatnych. Ale nie jestem pewien, co jest lepsze, aby twoja aplikacja została zaprojektowana specjalnie do testowania (to jak tworzenie testów tylko do testowania) lub użyć refleksu do testowania. Jestem pewien, że większość z nas wybierze drugi sposób.

Johnny_D
źródło
2

Moim zdaniem powinieneś przetestować tylko publiczny interfejs API swojej klasy.

Upublicznienie metody w celu jej jednostkowego przetestowania powoduje przerwanie enkapsulacji ujawniając szczegóły implementacji.

Dobry publiczny interfejs API rozwiązuje bezpośredni cel kodu klienta i rozwiązuje go całkowicie.

jpchauny
źródło
To powinna być właściwa odpowiedź IMO. Jeśli masz wiele prywatnych metod, to prawdopodobnie dlatego, że masz ukrytą klasę, którą powinieneś włamać się do własnego publicznego interfejsu.
sunefred
2

Używam PrivateObject klasę. Ale jak wspomniano wcześniej, lepiej unikać testowania metod prywatnych.

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);
vsapiha
źródło
2
CC -Dprivate=public

„CC” to kompilator wiersza poleceń w systemie, którego używam. -Dfoo=barrobi równowartość #define foo bar. Tak więc ta opcja kompilacji skutecznie zmienia wszystkie prywatne rzeczy na publiczne.

Mark Harrison
źródło
2
Co to jest? Czy dotyczy to programu Visual Studio?
Tak,
„CC” to kompilator wiersza poleceń w systemie, którego używam. „-Dfoo = bar” robi odpowiednik „#define foo bar”. Tak więc ta opcja kompilacji skutecznie zmienia wszystkie prywatne rzeczy na publiczne. ha ha!
Mark Harrison
W Visual Studio ustaw definicję w środowisku kompilacji.
Mark Harrison,
1

Oto przykład, najpierw sygnatura metody:

private string[] SplitInternal()
{
    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                        .Cast<Match>()
                        .Select(m => m.Value)
                        .Where(s => !string.IsNullOrEmpty(s))
                        .ToArray();
}

Oto test:

/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
    object[] values = new object[] { 2, "Martin" };
    XPathString xp = new XPathString(path, values);

    PrivateObject param0 = new PrivateObject(xp);
    XPathString_Accessor target = new XPathString_Accessor(param0);
    string[] expected = new string[] {
        "pair[path/to/@Key={0}]",
        "Items",
        "Item[Name={1}]",
        "Date"
    };
    string[] actual;
    actual = target.SplitInternal();
    CollectionAssert.AreEqual(expected, actual);
}
Chuck Savage
źródło
1

Sposobem na to jest posiadanie metody protectedi napisanie testowego urządzenia, które dziedziczy testowaną klasę. W ten sposób nie zmieniasz metody public, ale włączasz testowanie.

Kiriloff
źródło
Nie zgadzam się z tym, ponieważ umożliwisz również swoim klientom dziedziczenie od klasy podstawowej i korzystanie z chronionych funkcji. Przede wszystkim chciałeś temu zapobiec, czyniąc te funkcje prywatnymi lub wewnętrznymi.
Nick N.,
1

1) Jeśli masz starszy kod, jedynym sposobem na przetestowanie metod prywatnych jest refleksja.

2) Jeśli jest to nowy kod, masz następujące opcje:

  • Użyj odbicia (do skomplikowanego)
  • Napisz test jednostkowy w tej samej klasie (powoduje, że kod produkcyjny jest brzydki, ponieważ zawiera również kod testowy)
  • Refaktoryzuj i upublicznij metodę w jakiejś klasie util
  • Użyj adnotacji @VisibleForTesting i usuń prywatną

Wolę metodę adnotacji, najprostszą i najmniej skomplikowaną. Jedyną kwestią jest to, że zwiększyliśmy widoczność, która moim zdaniem nie stanowi dużego problemu. Zawsze powinniśmy kodować do interfejsu, więc jeśli mamy interfejs MyService i implementację MyServiceImpl, wówczas możemy mieć odpowiednie klasy testowe, czyli MyServiceTest (metody interfejsu testowego) i MyServiceImplTest (testuj metody prywatne). Wszyscy klienci i tak powinni korzystać z interfejsu, więc nawet jeśli widoczność metody prywatnej została zwiększona, nie powinno to mieć znaczenia.

anuj
źródło
1

Możesz również zadeklarować go jako publiczny lub wewnętrzny (z InternalsVisibleToAttribute) podczas budowania w trybie debugowania:

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

Nadyma kod, ale będzie privatew kompilacji wydania.

Alex H.
źródło
0

Możesz wygenerować metodę testową dla metody prywatnej z Visual Studio 2008. Po utworzeniu testu jednostkowego dla metody prywatnej do projektu testowego dodawany jest folder Odwołania testowe, a do tego folderu dodawany jest moduł dostępu. Accessor jest również określany w logice metody testu jednostkowego. Ten akcesor umożliwia testowi jednostkowemu wywoływanie metod prywatnych w testowanym kodzie. Po szczegóły zajrzyj

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

Sarath
źródło
0

Zauważ również, że InternalsVisibleToAtrribute ma wymaganie, aby twój zespół był silnie nazwany , co stwarza własny zestaw problemów, jeśli pracujesz w rozwiązaniu, które wcześniej nie miało tego wymagania. Używam akcesora do testowania metod prywatnych. Zobacz to pytanie jako przykład tego.

Chuck Dee
źródło
2
Nie, InternalsVisibleToAttributenie , nie wymaga się, że zespoły silnie nazwane. Obecnie używam go w projekcie, w którym tak nie jest.
Cody Gray
1
Aby to wyjaśnić: „Zarówno bieżące zgromadzenie, jak i zgromadzenie przyjaciela muszą być niepodpisane lub oba muszą być podpisane silnym nazwiskiem”. - Od MSDN
Hari