Czy statyczność jest ogólnie „zła” dla testów jednostkowych, a jeśli tak, to dlaczego Resharper to zaleca? [Zamknięte]

85

Odkryłem, że istnieją tylko 3 sposoby testowania jednostkowego zależności (makiety / kodu pośredniczącego), które są statyczne w języku C # .NET:

Biorąc pod uwagę, że dwa z nich nie są darmowe, a jeden nie trafił w wersji 1.0, drwiny ze statycznych rzeczy nie są zbyt łatwe.

Czy to czyni metody statyczne i takie „złe” (w sensie testowania jednostkowego)? A jeśli tak, to dlaczego resharper chce, żebym zrobił coś, co może być statyczne, statyczne? (Zakładając, że resharper nie jest również „zły”).

Wyjaśnienie: Mówię o scenariuszu, w którym chcesz przetestować metodę, a metoda ta wywołuje metodę statyczną w innej jednostce / klasie. Według większości definicji testowania jednostkowego, jeśli pozwalasz testowanej metodzie wywoływać metodę statyczną w innej jednostce / klasie, to nie jesteś testem jednostkowym, to testujesz integrację . (Przydatne, ale nie test jednostkowy.)

Vaccano
źródło
3
TheLQ: Możesz. Uważam, że mówi o tym, że nie jest w stanie przetestować metod statycznych, ponieważ przez większość czasu ma to wpływ na zmienne statyczne. W ten sposób zmienia się stan po teście i pomiędzy testami.
26
Osobiście uważam, że zbyt daleko posuwasz się do definicji „jednostki”. „Jednostka” powinna być „najmniejszą jednostką, która ma sens do testowania w izolacji”. To może być metoda, może być coś więcej. Jeśli metoda statyczna nie ma stanu i jest dobrze przetestowana, to przy drugim wywołaniu testowym drugiej jednostki nie stanowi problemu (IMO).
mlk
10
„Osobiście uważam, że zbyt daleko posuwasz się do definicji„ jednostki ”.” Nie, po prostu idzie ze standardowym użyciem, a ty tworzysz własną definicję.
7
„dlaczego resharper chce, żebym zrobił coś, co może być statyczne, statyczne?” Resharper nie chce, żebyś coś zrobił. To tylko uświadamia, że ​​modyfikacja jest możliwa, a może pożądana z POV analizy kodu. Resharper nie zastępuje własnego osądu!
Adam Naylor
4
@ acidzombie24. Zwykłe metody mogą również modyfikować stan statyczny, aby były tak samo „złe” jak metody statyczne. Fakt, że mogą również modyfikować stan przy użyciu krótszego cyklu życia, czyni je jeszcze bardziej niebezpiecznymi. (Nie jestem zwolennikiem metod statycznych, ale kwestia modyfikacji stanu jest ciosem w metody regularne, tym bardziej)
mike30

Odpowiedzi:

105

Patrząc na inne odpowiedzi tutaj, myślę, że może być pewne zamieszanie między metodami statycznymi, które utrzymują stan statyczny lub powodują skutki uboczne (co wydaje mi się naprawdę złym pomysłem), a metodami statycznymi, które jedynie zwracają wartość.

Metody statyczne, które nie utrzymują żadnego stanu i nie powodują żadnych skutków ubocznych, powinny być łatwo testowane jednostkowo. W rzeczywistości uważam takie metody za „funkcjonalność biedaka” programowania funkcjonalnego; przekazujesz metodę obiekt lub wartość i zwraca obiekt lub wartość. Nic więcej. Nie rozumiem, w jaki sposób takie metody wpłynęłyby w ogóle na testy jednostkowe.

Robert Harvey
źródło
44
Metoda statyczna jest testowalna jednostkowo, ale co z metodami wywołującymi metodę statyczną? Jeśli osoby dzwoniące należą do innej klasy, mają zależność, którą należy odłączyć od siebie, aby wykonać test jednostkowy.
Vaccano
22
@Vaccano: Ale jeśli napiszesz metody statyczne, które nie utrzymują stanu i nie wywołują skutków ubocznych, i tak są mniej więcej funkcjonalnie równoważne z kodem pośredniczącym.
Robert Harvey
20
Być może proste. Ale bardziej złożone mogą zacząć zgłaszać wyjątki i mieć wyniki, których nie należy się spodziewać (i powinny zostać złapane w teście jednostkowym dla metody statycznej, a nie w teście jednostkowym dla wywołującego metodę statyczną), a przynajmniej to ja doprowadzono do uwierzenia w literaturę, którą przeczytałem.
Vaccano,
20
@Vaccano: Metoda statyczna, która ma wyniki lub losowe wyjątki, ma skutki uboczne.
Tikhon Jelvis,
10
@TikhonJelvis Robert został mówić o wyjściach; a „losowe” wyjątki nie powinny być efektem ubocznym, są zasadniczo formą wyjścia. Chodzi o to, że za każdym razem, gdy testujesz metodę wywołującą metodę statyczną, owijasz tę metodę i wszystkie kombinacje jej potencjalnego wyniku, i nie możesz przetestować tej metody w izolacji.
Nicole,
26

Wygląda na to, że mylisz dane statyczne i metody statyczne . Resharper, jeśli dobrze pamiętam, zaleca tworzenie privatemetod w klasie statycznych, jeśli można je tak uczynić - uważam, że daje to niewielką korzyść w zakresie wydajności. To nie polecam dokonanie „wszystko, co może być” statyczne!

Nie ma nic złego w metodach statycznych i są one łatwe do przetestowania (o ile nie zmieniają żadnych danych statycznych). Na przykład pomyśl o bibliotece Maths, która jest dobrym kandydatem do klasy statycznej z metodami statycznymi. Jeśli masz (wymyśloną) metodę taką:

public static long Square(int x)
{
    return x * x;
}

to jest doskonale testowalne i nie ma skutków ubocznych. Po prostu sprawdzasz, że kiedy wpadasz, powiedzmy 20, wrócisz 400. Nie ma problemu.

Dan Diplo
źródło
4
Co dzieje się, gdy masz inną klasę, która wywołuje tę metodę statyczną? W tym przypadku wydaje się to proste, ale jest to zależność, której nie można odizolować bez jednego z trzech wymienionych powyżej narzędzi. Jeśli go nie wyodrębnisz, to nie będziesz „testował jednostki”, „
testujesz
3
Nic się nie dzieje. Dlaczego miałoby to .NET Framework jest pełen metod statycznych. Czy mówisz, że nie możesz ich użyć w żadnej metodzie, którą chcesz przetestować w jednostce?
Dan Diplo,
3
Cóż, jeśli Twój kod jest na poziomie produkcyjnym / jakości .NET Framework, to na pewno. Ale głównym celem testów jednostkowych jest testowanie jednostki w izolacji. Jeśli wywołujesz także metody w innych jednostkach (statycznych lub innych), testujesz teraz swoją jednostkę i jej zależności. Nie twierdzę, że nie jest to przydatny test, ale w większości definicji nie jest to „test jednostkowy”. (Ponieważ testujesz teraz testowany moduł i jednostkę, która ma metodę statyczną).
Vaccano,
10
Przypuszczalnie ty (lub inni) przetestowałeś już metodę statyczną i wykazałeś, że działa ona (przynajmniej zgodnie z oczekiwaniami) przed napisaniem wokół niej zbyt dużej ilości kodu. Jeśli coś się psuje w części, którą następnie testujesz, właśnie tam powinieneś szukać najpierw, a nie w materiałach, które już testowałeś.
cHao
6
@Vaccano Więc jak Microsoft testuje .NET Framework, co? Wiele klas we frameworku odwołuje się do metod statycznych w innych klasach (np. System.Math), Nie wspominając o obfitości statycznych metod fabrycznych itp. Ponadto nigdy nie będziesz w stanie używać żadnych metod rozszerzania itp. Faktem jest, że takie proste funkcje są takie podstawowe dla języków współczesnych. Możesz je przetestować w izolacji (ponieważ na ogół będą deterministyczne), a następnie bez obaw używać ich w swoich klasach. To nie jest problem!
Dan Diplo,
18

Jeśli prawdziwe pytanie brzmi „Jak przetestować ten kod?”:

public class MyClass
{
   public void MethodToTest()
   {
       //... do something
       MyStaticClass.StaticMethod();
       //...more
   }
}

Następnie po prostu refaktoryzuj kod i wstaw jak zwykle wywołanie klasy statycznej w następujący sposób:

public class MyClass
{
   private readonly IExecutor _externalExecutor;
   public MyClass(_IExecutor executor)
   {
       _exeternalExecutor = executor;
   }

   public void MethodToTest()
   {
       //... do something
       _exetrnalExecutor.DoWork();
       //...more
   }
}

public class MyStaticClassExecutor : IExecutor
{
    public void DoWork()
    {
        MyStaticClass.StaticMethod();
    }
}
Słoneczny
źródło
9
Na szczęście mamy również pytania dotyczące czytelności kodu i KISS również na SO :)
gbjbaanb 15.11.11
czy delegat nie byłby łatwiejszy i zrobiłby to samo?
jk.
@jk może być, ale wtedy trudno używać kontenera IoC.
Słoneczny
15

Statyka niekoniecznie jest zła, ale może ograniczyć twoje możliwości, jeśli chodzi o testowanie jednostkowe z podróbkami / próbkami / skrótami.

Istnieją dwa ogólne podejścia do kpin.

Pierwszy (tradycyjny - wdrożony przez RhinoMocks, Moq, NMock2; w tym obozie również znajdują się ręczne makiety i odcinki) opiera się na szwach testowych i wstrzykiwaniu zależności. Załóżmy, że testujesz jednostkowo jakiś kod statyczny i ma on zależności. W tak skonstruowanym kodzie często zdarza się, że statyka tworzy własne zależności, odwracając inwersję zależności . Wkrótce odkryjesz, że nie możesz wstrzykiwać fałszywych interfejsów do testowanego kodu zaprojektowanego w ten sposób.

Drugi (próbować czegokolwiek - wdrożony przez TypeMock, JustMock i Moles) opiera się na API profilowania .NET . Może przechwycić dowolną instrukcję CIL i zastąpić fragment kodu fałszywym. Pozwala to TypeMock i innym produktom w tym obozie wyśmiewać wszystko: statykę, zapieczętowane klasy, prywatne metody - rzeczy, które nie zostały zaprojektowane do testowania.

Trwa debata między dwiema szkołami myślenia. Mówi się, że przestrzegaj zasad SOLID i projektuj testowalność (często obejmuje to łatwość w statywie). Drugi mówi: kup TypeMock i nie martw się.

azheglov
źródło
14

Sprawdź to: „Metody statyczne to śmierć dla testowalności” . Krótkie streszczenie argumentu:

Aby przeprowadzić test jednostkowy, musisz wziąć mały fragment kodu, ponownie powiązać jego zależności i przetestować go w izolacji. Jest to trudne w przypadku metod statycznych, nie tylko w przypadku, gdy uzyskują dostęp do stanu globalnego, ale nawet jeśli wywołują inne metody statyczne.

Rafał Dowgird
źródło
32
Nie utrzymuję globalnego stanu ani nie wywołuję skutków ubocznych w moich metodach statycznych, więc ten argument wydaje mi się nieistotny. Artykuł, do którego się odsyłasz, stanowi śliski argument nachylenia, który nie ma podstaw, jeśli metody statyczne ograniczają się do prostego kodu proceduralnego, działając w sposób „ogólny” (jak funkcje matematyczne).
Robert Harvey
4
@Robert Harvey - w jaki sposób przeprowadzasz test jednostkowy metody wykorzystującej metodę statyczną z innej klasy (tj. Ma ona zależność od metody statycznej). Jeśli po prostu pozwolicie to nazwać, to nie jesteście „testami jednostkowymi”, jesteście „testami integracyjnymi”
Vaccano
10
Nie czytaj tylko artykułu na blogu, przeczytaj wiele komentarzy, które się z nim nie zgadzają. Blog to tylko opinia, a nie fakt.
Dan Diplo,
8
@Vaccano: Metoda statyczna bez skutków ubocznych lub stanu zewnętrznego jest funkcjonalnie równoważna wartości. Wiedząc o tym, nie różni się niczym od testowania jednostkowego metody, która tworzy liczbę całkowitą. Jest to jedna z kluczowych informacji, które daje nam programowanie funkcjonalne.
Steven Evers
3
Z wyjątkiem pracy systemów wbudowanych lub aplikacji, których błędy mogą potencjalnie powodować incydenty międzynarodowe, IMO, kiedy „testowalność” napędza architekturę, musiałeś ją obrócić, zanim w pierwszej kolejności zastosowałeś metodykę testowania na każdym rogu. Jestem też zmęczony wersją XP wszystkiego, co dominuje w każdej rozmowie. XP nie jest autorytetem, to branża. Oto oryginalna, sensowna definicja testowania jednostkowego: python.net/crew/tbryan/UnitTestTalk/slide2.html
Erik Reppen
5

Prosta prawda rzadko uznawana jest taka, że ​​jeśli klasa zawiera zależną od kompilatora zależność od innej klasy, nie można jej przetestować w oderwaniu od tej klasy. Możesz sfałszować coś, co wygląda jak test, i pojawi się w raporcie, jakby to był test.

Ale nie będzie miał kluczowych właściwości definiujących test; porażki, gdy coś jest nie tak, przemijanie, gdy mają rację.

Dotyczy to wszelkich wywołań statycznych, wywołań konstruktora oraz wszelkich odniesień do metod lub pól, które nie są dziedziczone z klasy podstawowej lub interfejsu . Jeśli nazwa klasy pojawia się w kodzie, jest to zależność widoczna dla kompilatora i bez niej nie można poprawnie przetestować. Każda mniejsza porcja po prostu nie jest prawidłową jednostką do przetestowania . Każda próba potraktowania go tak, jakby był, przyniesie wyniki nie bardziej znaczące niż napisanie małego narzędzia do emitowania kodu XML używanego przez środowisko testowe w celu powiedzenia „test pozytywny”.

Biorąc to pod uwagę, istnieją trzy opcje:

  1. zdefiniuj testowanie jednostkowe jako testowanie jednostki złożonej z klasy i jej zakodowanych zależności. Działa to, pod warunkiem, że unikniesz zależności cyklicznych.

  2. nigdy nie twórz zależności czasu kompilacji między klasami, za które odpowiadasz za testowanie. Działa to, pod warunkiem, że nie przeszkadza ci wynikowy styl kodu.

  3. nie testuj jednostki, zamiast tego test integracji. Który działa, pod warunkiem, że nie powoduje konfliktu z czymś innym, do czego należy użyć terminu testowanie integracji.

soru
źródło
3
Nic nie mówi, że test jednostkowy jest testem jednej klasy. To test pojedynczej jednostki. Charakterystyczne właściwości testów jednostkowych polegają na tym, że są one szybkie, powtarzalne i niezależne. Odwoływanie się Math.Pido metody nie czyni testu integracji z żadnej rozsądnej definicji.
sara
tj. opcja 1. Zgadzam się, że to najlepsze podejście, ale warto wiedzieć, że inni ludzie używają terminów inaczej (rozsądnie lub nie).
soru
4

Nie ma na to dwóch sposobów. Sugestie ReSharpera i kilka przydatnych funkcji języka C # nie byłyby używane tak często, gdybyś pisał izolowane testy jednostek atomowych dla całego kodu.

Na przykład, jeśli masz metodę statyczną i musisz ją zlikwidować, nie możesz tego zrobić, chyba że użyjesz struktury izolacji opartej na profilu. Obejściem kompatybilnym z połączeniem jest zmiana początku metody używania notacji lambda. Na przykład:

PRZED:

    public static DBConnection ConnectToDB( string dbName, string connectionInfo ) {
    }

PO:

    public static Func<string, string, DBConnection> ConnectToDB (dbName, connectionInfo ) {
    };

Oba są kompatybilne z połączeniami. Dzwoniący nie muszą się zmieniać. Ciało funkcji pozostaje takie samo.

Następnie w kodzie testu jednostkowego możesz w ten sposób wyciąć to wywołanie (zakładając, że należy ono do klasy o nazwie Baza danych):

        Database.ConnectToDB = (dbName, connectionInfo) => { return null|whatever; }

Po zakończeniu należy go zastąpić oryginalną wartością. Możesz to zrobić za pomocą try / wreszcie lub, w trakcie czyszczenia testu jednostkowego, tego, który jest wywoływany po każdym teście, napisz kod taki jak ten:

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Database).TypeInitializer.Invoke(null, null);
    }

który ponownie wywoła statyczny inicjalizator twojej klasy.

Lambda Funcs nie są tak bogate w wsparcie jak zwykłe metody statyczne, więc takie podejście ma następujące niepożądane skutki uboczne:

  1. Jeśli metoda statyczna była metodą rozszerzenia, najpierw musisz ją zmienić na metodę inną niż rozszerzenie. Resharper może to zrobić automatycznie.
  2. Jeśli którykolwiek z typów danych metod statycznych jest zespołem osadzonym międzyoperacyjnie, na przykład dla pakietu Office, musisz owinąć metodę, owinąć typ lub zmienić go na typ „obiekt”.
  3. Nie możesz już korzystać z narzędzia refaktoryzacji podpisów zmian Resharpera.

Powiedzmy jednak, że całkowicie unikasz statyki i zamieniasz ją na metodę instancji. Nadal nie można go wyśmiewać, chyba że metoda jest wirtualna lub zaimplementowana jako część interfejsu.

Tak więc w rzeczywistości każdy, kto sugeruje rozwiązanie problemu umieszczania metod statycznych, ma uczynić je metodami instancji, byłyby także przeciwne metodom instancji, które nie są wirtualne lub stanowią część interfejsu.

Dlaczego więc C # ma metody statyczne? Dlaczego dopuszcza metody instancji innych niż wirtualne?

Jeśli użyjesz jednej z tych „funkcji”, po prostu nie będziesz mógł stworzyć izolowanych metod.

Kiedy ich używasz?

Używaj ich do każdego kodu, którego nie spodziewasz się, że ktokolwiek zechce go usunąć. Kilka przykładów: metoda Format () klasy String, metoda WriteLine () klasy Console, metoda Cosh () klasy Math

I jeszcze jedno. Większość ludzi nie będzie się tym przejmować, ale jeśli możesz o wykonanie połączenia pośredniego, to kolejny powód, aby unikać metod instancji. Zdarzają się przypadki, gdy jest to hit wydajności. Właśnie dlatego istnieją metody inne niż wirtualne.

zumalifeguard
źródło
3
  1. Myślę, że częściowo dlatego, że metody statyczne są „szybsze” do wywołania niż metody instancji. (W cudzysłowie, ponieważ pachnie to mikrooptymalizacją) patrz http://dotnetperls.com/static-method
  2. Mówi ci, że nie potrzebuje stanu, dlatego może być wywoływany z dowolnego miejsca, usuwając narzut związany z inicjacją, jeśli to jedyna rzecz, której ktoś potrzebuje.
  3. Jeśli chcę się z tego kpić, to myślę, że ogólnie jest to praktyką, że jest zadeklarowany na interfejsie.
  4. Jeśli jest zadeklarowany w interfejsie, R # nie zasugeruje, aby był statyczny.
  5. Jeśli jest zadeklarowany jako wirtualny, R # nie zasugeruje, aby był statyczny.
  6. Statyczne utrzymywanie stanu (pól) jest czymś, co zawsze powinno być uważnie rozważane . Stan statyczny i nici mieszają się jak lit i woda.

R # nie jest jedynym narzędziem, które sprawi, że ta sugestia. Analiza FxCop / MS Code również zrobi to samo.

Ogólnie powiedziałbym, że jeśli metoda jest statyczna, ogólnie powinna być również możliwa do przetestowania. Daje to trochę do myślenia przy projektowaniu i prawdopodobnie więcej dyskusji niż teraz w moich palcach, więc cierpliwie czekam na słabe głosy i komentarze ...;)

MIA
źródło
Czy możesz zadeklarować statyczną metodę / obiekt na interfejsie? (Nie sądzę). Eter, mam na myśli, kiedy twoja metoda statyczna jest wywoływana przez testowaną metodę. Jeśli osoba dzwoniąca znajduje się w innej jednostce, należy odizolować metodę statyczną. Jest to bardzo trudne w przypadku jednego z trzech wymienionych powyżej narzędzi.
Vaccano,
1
Nie, nie można zadeklarować statycznego interfejsu. To byłoby bez znaczenia.
MIA
3

Widzę, że po długim czasie nikt jeszcze nie stwierdził naprawdę prostego faktu. Jeśli resharper mówi mi, że mogę uczynić metodę statyczną, oznacza to dla mnie ogromną rzecz, słyszę jego głos mówiący: „hej, ty, te logiki nie są OBOWIĄZKOWĄ klasą do obsługi, więc powinna pozostać na zewnątrz w jakiejś klasie pomocniczej czy coś ".

g1ga
źródło
2
Nie zgadzam się. Przez większość czasu, gdy Resharper mówi, że mogę zrobić coś statycznego, to trochę kodu, który był wspólny dla dwóch lub więcej metod w klasie i dlatego wyodrębniłem go według własnej procedury. Przeniesienie tego do pomocnika byłoby bezsensowną złożonością.
Loren Pechtel
2
Widzę twój punkt widzenia tylko wtedy, gdy domena jest bardzo prosta i nie nadaje się do przyszłych modyfikacji. Inaczej, to, co nazywacie „bezsensowną złożonością”, to dla mnie dobry i czytelny dla człowieka projekt. Posiadanie klasy pomocniczej z prostym i jasnym powodem istnienia jest w pewnym sensie „mantrą” zasad SoC i zasady pojedynczej odpowiedzialności. Ponadto, biorąc pod uwagę, że ta nowa klasa staje się zależnością dla klasy głównej, musi ujawnić niektórych członków publicznych, i jako naturalna konsekwencja staje się testowalna w izolacji i łatwa do wyszydzenia, gdy działa jako zależność.
g1ga
1
Nie dotyczy pytania.
Igby Largeman
2

Jeśli metoda statyczna jest wywoływana z innej metody , nie można zapobiec ani zastąpić takiego wywołania. Oznacza to, że te dwie metody tworzą jedną Jednostkę; Test jednostkowy dowolnego rodzaju sprawdza je oba.

A jeśli ta statyczna metoda komunikuje się z Internetem, łączy bazy danych, wyświetla wyskakujące okienka GUI lub w inny sposób przekształca test Jednostki w kompletny bałagan, robi to bez żadnego łatwego obejścia. Metoda wywołująca taką metodę statyczną nie jest testowalna bez refaktoryzacji, nawet jeśli zawiera wiele kodu czysto obliczeniowego, który bardzo skorzystałby na testowaniu jednostkowym.

h22
źródło
0

Wierzę, że Resharper daje ci wskazówki i stosuje wytyczne kodowania, z którymi został skonfigurowany. Kiedy użyłem Resharpera i powiedział mi, że metoda powinna być statyczna, to na pewno będzie to metoda prywatna, która nie działa na żadne zmienne instancji.

Jeśli chodzi o testablity, ten scenariusz nie powinien stanowić problemu, ponieważ i tak nie powinieneś testować metod prywatnych.

Jeśli chodzi o testowalność metod statycznych, które są publiczne, to testy jednostkowe stają się trudne, gdy metody statyczne dotykają stanu statycznego. Osobiście ograniczyłbym to do minimum i używałbym metod statycznych jako czystych funkcji, o ile to możliwe, tam gdzie wszelkie zależności są przekazywane do metody, którą można kontrolować za pomocą urządzenia testowego. Jest to jednak decyzja projektowa.

aqwert
źródło
Mam na myśli, kiedy masz testowaną metodę (niestatyczną), która wywołuje metodę statyczną w innej klasie. Zależność tę można odizolować tylko za pomocą trzech wymienionych powyżej narzędzi. Jeśli go nie izolujesz, oznacza to, że testujesz integrację, a nie testowanie jednostkowe.
Vaccano
Dostęp do zmiennych instancji z natury oznacza, że ​​nie może być statyczny. Jedynym stanem, jaki może mieć statyczna metoda, są zmienne klas, a jedynym powodem, który powinien się wydarzyć, jest to, że masz do czynienia z singletonem, a zatem nie masz wyboru.
Loren Pechtel