Testowanie jednostkowe metod prywatnych w C #

291

Visual Studio umożliwia testowanie jednostkowe metod prywatnych za pomocą automatycznie generowanej klasy akcesorium. Napisałem test prywatnej metody, która kompiluje się pomyślnie, ale kończy się niepowodzeniem w czasie wykonywania. Dość minimalna wersja kodu i testu to:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

Błąd środowiska wykonawczego to:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

Zgodnie z intellisense - i stąd sądzę, że kompilator - cel jest typu TypeA_Accessor. Ale w środowisku wykonawczym jest typu TypeA, a zatem dodawanie listy kończy się niepowodzeniem.

Czy jest jakiś sposób, aby zatrzymać ten błąd? Lub, być może bardziej prawdopodobne, jakie inne porady mają inne osoby (przewiduję, że „nie testuj metod prywatnych” i „nie testuj jednostkowo testów stanu obiektów”).

Junichiro
źródło
Potrzebujesz akcesorium do prywatnej klasy TypeB. Accessor TypeA_Accessor zapewnia dostęp do prywatnych i chronionych metod TypeA. Jednak TypeB nie jest metodą. To jest klasa.
Dima,
Accessor zapewnia dostęp do prywatnych / chronionych metod, członków, właściwości i zdarzeń. Nie zapewnia dostępu do prywatnych / chronionych klas w twojej klasie. A klasy prywatne / chronione (TypeB) są przeznaczone wyłącznie do użycia przez metody własności klasy (TypeA). Zasadniczo próbujesz dodać klasę prywatną (TypeB) spoza TypeA do „myList”, która jest prywatna. Ponieważ używasz akcesora, nie ma problemu z dostępem do mojej listy. Jednak nie można używać TypeB przez akcesor. Możliwym rozwiązaniem byłoby przeniesienie TypeB poza TypeA. Ale może to popsuć twój projekt.
Dima,
Poczuj, że testowanie metod prywatnych powinno zostać wykonane przez następujący stackoverflow.com/questions/250692/…
nate_weldon

Odpowiedzi:

274

Tak, nie testuj prywatnych metod .... Ideą testu jednostkowego jest przetestowanie urządzenia za pomocą jego publicznego „API”.

Jeśli zauważysz, że musisz przetestować wiele prywatnych zachowań, najprawdopodobniej masz nową „klasę” ukrytą w klasie, którą próbujesz przetestować, wypakuj ją i przetestuj za pomocą publicznego interfejsu.

Jedna rada / narzędzie do myślenia ... Istnieje pomysł, że żadna metoda nigdy nie powinna być prywatna. Oznacza to, że wszystkie metody powinny znajdować się na publicznym interfejsie obiektu .... jeśli uważasz, że musisz ustawić go jako prywatny, najprawdopodobniej żyje on na innym obiekcie.

Ta rada nie sprawdza się w praktyce, ale w większości jest dobrą radą i często zmusza ludzi do rozkładania przedmiotów na mniejsze.

Keith Nicholas
źródło
385
Nie zgadzam się. W OOD prywatne metody i właściwości są nieodłącznym sposobem, aby się nie powtarzać ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). Ideą programowania i enkapsulacji czarnej skrzynki jest ukrywanie szczegółów technicznych przed subskrybentem. Dlatego rzeczywiście konieczne jest posiadanie nietrywialnych prywatnych metod i właściwości w kodzie. A jeśli nie jest trywialny, należy go przetestować.
AxD
8
to nie jest nieodłączne, niektóre języki OO nie mają prywatnych metod, prywatne właściwości mogą zawierać obiekty z publicznymi interfejsami, które można testować.
Keith Nicholas
7
Istotą tej porady jest to, że jeśli twój obiekt robi jedną rzecz i jesteś SUCHY, to często nie ma powodu, aby mieć prywatne metody. Często prywatne metody robią coś, za co obiekt nie jest tak naprawdę odpowiedzialny, ale jest całkiem przydatne, jeśli nie jest trywialne, to na ogół jest to inny obiekt, ponieważ prawdopodobnie narusza SRP
Keith Nicholas
28
Źle. Możesz użyć metod prywatnych, aby uniknąć powielania kodu. Lub do walidacji. Lub do wielu innych celów, o których świat publiczny nie powinien wiedzieć.
Jorj
37
Kiedy zostałeś zrzucony na tak przerażająco zaprojektowaną bazę kodu OO i poproszono cię o „stopniową poprawę”, z rozczarowaniem stwierdziłem, że nie mogłem przeprowadzić pierwszych testów prywatnych metod. Tak, być może w podręcznikach tych metod nie ma, ale w prawdziwym świecie mamy użytkowników, którzy mają wymagania dotyczące produktu. Nie mogę po prostu dokonać ogromnego refaktoryzacji „Spójrz na mój czysty kod” bez przeprowadzenia testów w projekcie. Wydaje się, że to kolejny przykład zmuszania praktykujących programistów do korzystania z dróg, które naiwnie wydają się dobre, ale nie uwzględniają prawdziwego bałaganu.
dune.rocks
666

Możesz użyć klasy PrivateObject

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);
Właz
źródło
25
To poprawna odpowiedź, teraz, gdy Microsoft dodał PrivateObject.
Zoey
4
Dobra odpowiedź, ale pamiętaj, że PrivateMethod musi być „chroniony” zamiast „prywatnego”.
HerbalMart
23
@HerbalMart: Być może źle cię zrozumiałem, ale jeśli sugerujesz, że PrivateObject ma dostęp tylko do członków chronionych, a nie prywatnych, jesteś w błędzie.
kmote
17
@JeffPearce W przypadku metod statycznych można użyć „PrivateType pt = new PrivateType (typeof (MyClass));”, a następnie wywołać InvokeStatic na obiekcie pt, tak jak wywołać Invoke na obiekcie prywatnym.
Steve Hibbert
12
Na wypadek, gdyby ktoś zastanawiał się nad wersją MSTest.TestFramework v1.2.1 - klasy PrivateObject i PrivateType są niedostępne w projektach ukierunkowanych na .NET Core 2.0 - występuje problem z github: github.com/Microsoft/testfx/issues/366
shiitake
98

„Nic nie jest nazywane standardową lub najlepszą praktyką, prawdopodobnie są to tylko popularne opinie”.

To samo dotyczy również tej dyskusji.

wprowadź opis zdjęcia tutaj

Wszystko zależy od tego, co uważasz za jednostkę, jeśli uważasz, że UNIT jest klasą, trafisz tylko na metodę publiczną. Jeśli uważasz, że UNIT to linie kodu, uderzanie w prywatne metody nie spowoduje, że poczujesz się winny.

Jeśli chcesz wywołać metody prywatne, możesz użyć klasy „PrivateObject” i wywołać metodę invoke. Możesz obejrzeć ten szczegółowy film na youtube ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ), który pokazuje, jak używać „PrivateObject”, a także omawia, czy testowanie metod prywatnych jest logiczne.

Shivprasad Koirala
źródło
55

Inną myślą tutaj jest rozszerzenie testów na „wewnętrzne” klasy / metody, dając więcej białych wrażeń na temat tych testów. Możesz użyć InternalsVisibleToAttribute na zestawie, aby udostępnić je osobnym modułom testującym jednostki.

W połączeniu z zapieczętowaną klasą możesz podejść do takiego enkapsulacji, że metoda testowa jest widoczna tylko z najcichszego zestawu twoich metod. Weź pod uwagę, że chroniona metoda w zamkniętej klasie jest de facto prywatna.

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

I test jednostkowy:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}
Jeff
źródło
Nie jestem pewien, czy rozumiem. InternalsVisibleToAttribute udostępnia metody i atrybuty oznaczone jako „wewnętrzne”, ale moje pola i metody są „prywatne”. Sugerujesz, żebym zmienił coś z prywatnego na wewnętrzny? Myślę, że źle zrozumiałem.
Junichiro,
2
Tak, właśnie to sugeruję. To trochę „hacky”, ale przynajmniej nie są „publiczni”.
Jeff
27
To wspaniała odpowiedź tylko dlatego, że nie mówi „nie testuj metod prywatnych”, ale tak, jest dość „hacky”. Chciałbym, żeby było jakieś rozwiązanie. IMO źle mówi „prywatne metody nie powinny być testowane”, ponieważ tak to widzę: jest to równoważne z „prywatnymi metodami nie powinny być poprawne”.
MasterMastic
5
ya ken, myliłem się również przez tych, którzy twierdzą, że prywatne metody nie powinny być testowane w teście jednostkowym. Wyjściowe publiczne interfejsy API są danymi wyjściowymi, ale czasami niewłaściwa implementacja daje również prawidłowe dane wyjściowe. Lub implementacja spowodowała pewne złe skutki uboczne, np. Utrzymywanie zasobów, które nie są konieczne, odwoływanie się do obiektów zapobiegających gromadzeniu ich przez gc ... itp. Chyba że dostarczą innego testu, który może obejmować metody prywatne niż test jednostkowy, inaczej uważam, że nie mogą utrzymać w 100% przetestowanego kodu.
mr.Pony
Zgadzam się z MasterMastic. To powinna być zaakceptowana odpowiedź.
XDS,
27

Jednym ze sposobów testowania metod prywatnych jest refleksja. Dotyczy to także NUnit i XUnit:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);
Jack Davidson
źródło
call methods statyczne i niestatyczne ?
Kiquenet,
2
Minusem metod opartych na odbiciu jest to, że mają tendencję do pękania, gdy zmieniasz nazwy metod za pomocą R #. Może to nie być dużym problemem w małych projektach, ale w dużych bazach kodu dość kłopotliwe jest, że testy jednostkowe psują się w taki sposób, a następnie trzeba je obejść i szybko naprawić. W tym sensie moje pieniądze idą na odpowiedź Jeffa.
XDS,
2
@XDS Zbyt zła nazwaof () nie działa, aby uzyskać nazwę metody prywatnej spoza jej klasy.
Gabriel Morin
12

Eee ... Przyszedł tutaj z dokładnie tym samym problemem: przetestuj prostą , ale kluczową metodę prywatną . Po przeczytaniu tego wątku wydaje się, że „chcę wywiercić ten prosty otwór w tym prostym kawałku metalu i chcę się upewnić, że jakość odpowiada specyfikacjom”, a potem pojawia się „OK, to nie jest łatwe. Po pierwsze, nie ma do tego odpowiedniego narzędzia, ale możesz zbudować obserwatorium fal grawitacyjnych w swoim ogrodzie. Przeczytaj mój artykuł na stronie http://foobar.brigther-than-einstein.org/ Po pierwsze, oczywiście muszę wziąć udział w niektórych zaawansowanych kursach fizyki kwantowej, wtedy potrzebujesz ton ultra-cool nitrogenium, a następnie, oczywiście, moja książka dostępna w Amazon „...

Innymi słowy...

Nie, najpierw najważniejsze.

Każda metoda, prywatna, wewnętrzna, chroniona, publiczna musi być testowalna. Musi istnieć sposób na wdrożenie takich testów bez takich ceregieli, jak tutaj przedstawiono.

Dlaczego? Właśnie ze względu na wzmianki o architekturze poczynione do tej pory przez niektórych autorów. Być może proste powtórzenie zasad oprogramowania może wyjaśnić pewne nieporozumienia.

W tym przypadku zwykłymi podejrzanymi są: OCP, SRP i, jak zawsze, KIS.

Ale chwileczkę. Pomysł, aby udostępnić wszystko publicznie, jest mniej polityczny i stanowi rodzaj postawy. Ale. Jeśli chodzi o kod, nawet w ówczesnej społeczności Open Source, to nie jest dogmat. Zamiast tego „ukrywanie” czegoś jest dobrą praktyką, aby ułatwić zapoznanie się z pewnym interfejsem API. Ukryłbyś na przykład bardzo podstawowe obliczenia nowego bloku konstrukcyjnego termometru cyfrowego - nie po to, aby ukryć matematykę za rzeczywistą zmierzoną krzywą dla ciekawskich czytników kodów, ale aby zapobiec uzależnieniu kodu od niektórych, być może nagle ważni użytkownicy, którzy nie mogliby się oprzeć użyciu twojego wcześniej prywatnego, wewnętrznego, chronionego kodu do realizacji własnych pomysłów.

O czym mówię

prywatny podwójny TranslateMeasurementIntoLinear (podwójny aktualny pomiar);

Łatwo jest ogłosić Wiek Wodnika lub to, co nazywa się obecnie, ale jeśli mój kawałek czujnika zmieni się z 1.0 na 2.0, implementacja Tłumacz ... może zmienić się z prostego równania liniowego, które jest łatwo zrozumiałe i „ponownie” użyteczny dla wszystkich, do dość wyrafinowanych obliczeń, które wykorzystują analizę lub cokolwiek innego, więc złamałbym kod innej osoby. Dlaczego? Ponieważ nie rozumieli samych zasad kodowania oprogramowania, nawet KIS.

Krótko mówiąc: potrzebujemy prostego sposobu na przetestowanie prywatnych metod - bez zbędnych ceregieli.

Po pierwsze: Szczęśliwego nowego roku wszystkim!

Po drugie: przećwicz lekcje architekta.

Po trzecie: „publicznym” modyfikatorem jest religia, a nie rozwiązanie.

CP70
źródło
5

Inną opcją, o której nie wspomniano, jest po prostu utworzenie klasy testu jednostkowego jako elementu potomnego testowanego obiektu. Przykład NUnit:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

Umożliwiłoby to łatwe testowanie prywatnych i chronionych (ale nie odziedziczonych prywatnych) metod, a także pozwoliłoby oddzielić wszystkie testy od rzeczywistego kodu, aby nie wdrażać zestawów testowych w środowisku produkcyjnym. Przełączenie metod prywatnych na chronione byłoby dopuszczalne w wielu odziedziczonych obiektach i jest to dość prosta zmiana.

JEDNAK...

Chociaż jest to interesujące podejście do rozwiązania problemu testowania ukrytych metod, nie jestem pewien, czy zaleciłbym, aby we wszystkich przypadkach było to właściwe rozwiązanie problemu. Wydaje się trochę dziwne wewnętrzne testowanie obiektu i podejrzewam, że mogą istnieć pewne scenariusze, że takie podejście cię wysadzi. (Na przykład niezmienne obiekty mogą utrudnić niektóre testy).

Wspominając o tym podejściu, sugeruję, że jest to raczej sugestia burzy mózgów niż uzasadnione rozwiązanie. Dodaj szczyptę soli.

EDYCJA: Uważam, że to naprawdę zabawne, że ludzie głosują na tę odpowiedź, ponieważ wyraźnie opisuję to jako zły pomysł. Czy to oznacza, że ​​ludzie się ze mną zgadzają? Jestem taki skołowany.....

Roger Hill
źródło
To kreatywne rozwiązanie, ale trochę hacky.
shinzou,
3

Z książki Skuteczna praca ze starszym kodem :

„Jeśli musimy przetestować metodę prywatną, powinniśmy ją upublicznić. Jeśli upublicznienie nam to przeszkadza, w większości przypadków oznacza to, że nasza klasa robi zbyt wiele i powinniśmy to naprawić”.

Według autora sposobem na jego naprawienie jest utworzenie nowej klasy i dodanie metody as public.

Autor wyjaśnia dalej:

„Dobry projekt jest testowalny, a projekt, który nie jest testowalny, jest zły”.

Tak więc, w tych granicach, jedyną prawdziwą opcją jest stworzenie metody publicw bieżącej lub nowej klasie.

Kai Hartmann
źródło
2

TL; DR: Wyodrębnij metodę prywatną do innej klasy, przetestuj na tej klasie; czytaj więcej o zasadzie SRP (zasada pojedynczej odpowiedzialności)

Wydaje się, że potrzebujesz wyciągu z privatemetody do innej klasy; w tym powinno być public. Zamiast próbować testować privatemetodę, powinieneś przetestować publicmetodę tej innej klasy.

Mamy następujący scenariusz:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Musimy przetestować logikę _someLogic; ale wydaje się, że Class Aodgrywają większą rolę niż to konieczne (narusza zasadę SRP); po prostu podziel na dwie klasy

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

W ten sposób someLogicmożna przetestować na A2; w A1 po prostu stwórz fałszywy A2, a następnie wstrzyknij do konstruktora, aby sprawdzić, czy A2 jest wywoływany do funkcji o nazwie someLogic.

o0omycomputero0o
źródło
0

W VS 2005/2008 możesz użyć prywatnego akcesora do przetestowania prywatnego członka, ale ten sposób zniknął w późniejszej wersji VS

Allen
źródło
1
Dobra odpowiedź w 2008 r., A może na początku 2010 r. Teraz zapoznaj się z alternatywami PrivateObject i Reflection (patrz kilka odpowiedzi powyżej). VS2010 miał błąd (y) w akcesoriach, MS wycofał je w VS2012. O ile nie będziesz zmuszony pozostać w wersji VS2010 lub starszej (narzędzia do kompilacji> 18 lat), oszczędzaj czas, unikając prywatnych dostawców. :-).
Zephan Schroeder