Testy jednostkowe puste metody?

170

Jaki jest najlepszy sposób testowania jednostkowego metody, która nic nie zwraca? W szczególności w języku C #.

To, co naprawdę próbuję przetestować, to metoda, która pobiera plik dziennika i analizuje go pod kątem określonych ciągów. Ciągi są następnie wstawiane do bazy danych. Nic, czego nie zrobiono wcześniej, ale będąc BARDZO nowością w TDD, zastanawiam się, czy można to przetestować, czy jest to coś, co tak naprawdę nie jest testowane.

jdiaz
źródło
55
Nie używaj terminu „TDD”, jeśli nie to robisz. Robisz testy jednostkowe, a nie TDD. Gdybyś robił TDD, nigdy nie miałbyś pytania typu „jak przetestować metodę”. Test istniałby pierwszy, a następnie pytanie brzmiałoby: „jak sprawić, by ten test przeszedł?” Ale jeśli BYŁEŚ robiąc TDD, twój kod zostałby napisany na potrzeby testu (a nie na odwrót), a ostatecznie odpowiedziałbyś na swoje własne pytanie. Twój kod zostałby sformatowany inaczej w wyniku TDD, a ten problem nigdy by się nie pojawił. Tylko wyjaśnienie.
Suamere

Odpowiedzi:

151

Jeśli metoda nic nie zwraca, jest to jedna z poniższych

  • imperative - albo żądasz, aby obiekt zrobił coś samemu sobie .. np. zmienił stan (nie oczekując żadnego potwierdzenia .. zakłada się, że zostanie zrobiony)
  • informacyjny - po prostu powiadomienie kogoś, że coś się wydarzyło (bez oczekiwania działania lub odpowiedzi).

Metody rozkazujące - możesz sprawdzić, czy zadanie zostało faktycznie wykonane. Sprawdź, czy rzeczywiście nastąpiła zmiana stanu. na przykład

void DeductFromBalance( dAmount ) 

można sprawdzić, sprawdzając, czy saldo zaksięgowane w tej wiadomości jest rzeczywiście mniejsze niż początkowa wartość dAmount

Metody informacyjne - są rzadkie jako element interfejsu publicznego obiektu ... stąd normalnie nie są testowane jednostkowo. Jeśli jednak musisz, możesz sprawdzić, czy ma miejsce obsługa zgłoszenia. na przykład

void OnAccountDebit( dAmount )  // emails account holder with info

można przetestować, sprawdzając, czy wiadomość e-mail jest wysyłana

Opublikuj więcej szczegółów na temat swojej faktycznej metody, a ludzie będą mogli lepiej odpowiedzieć.
Aktualizacja : Twoja metoda robi 2 rzeczy. Właściwie podzieliłbym to na dwie metody, które można teraz niezależnie przetestować.

string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );

Ciąg [] można łatwo zweryfikować, podając pierwszą metodę z fikcyjnym plikiem i oczekiwanymi ciągami. Drugi jest nieco trudny. Możesz albo użyć Mocka (Google lub Search stackoverflow na frameworkach do naśladowania), aby naśladować bazę danych lub trafić do rzeczywistej bazy danych i sprawdzić, czy ciągi znaków zostały wstawione we właściwym miejscu. Sprawdź ten wątek, aby znaleźć dobre książki ... Polecam Pragmatic Unit Testing, jeśli jesteś w kryzysie.
W kodzie byłby używany jak

InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );
Gishu
źródło
1
hej gishu, dobra odpowiedź. Przykład, który podałeś ... czy to nie więcej testów integracji ...? a jeśli tak, pozostaje pytanie, jak naprawdę przetestować metody Void… może to niemożliwe?
andy
2
@andy - zależy od Twojej definicji „testów integracji”. Metody rozkazujące zwykle zmieniają stan, więc możesz zostać zweryfikowany przez test jednostkowy, który bada stan obiektu. Metody informacyjne można zweryfikować za pomocą testu jednostkowego, który podłącza pozorowanego nasłuchiwania / współpracownika, aby upewnić się, że obiekt testu wysyła właściwe powiadomienie. Myślę, że oba można rozsądnie przetestować za pomocą testów jednostkowych.
Gishu,
@andy baza danych może być mockowana / oddzielana przez interfejs akcesora, umożliwiając w ten sposób testowanie akcji przez dane przekazane do obiektu pozorowanego.
Peter Geiger
62

Przetestuj jego skutki uboczne. To zawiera:

  • Czy rzuca jakieś wyjątki? (Jeśli tak, sprawdź, czy tak. Jeśli nie, wypróbuj kilka przypadków narożnych, które mogą, jeśli nie jesteś ostrożny - argumenty zerowe są najbardziej oczywistą rzeczą).
  • Czy ładnie gra swoimi parametrami? (Jeśli są zmienne, czy powoduje to ich mutację, kiedy nie powinno i odwrotnie?)
  • Czy ma odpowiedni wpływ na stan obiektu / typu, do którego go wywołujesz?

Oczywiście istnieje limit tego, ile możesz przetestować. Na przykład generalnie nie można testować z każdym możliwym wejściem. Testuj pragmatycznie - na tyle, aby mieć pewność, że Twój kod jest odpowiednio zaprojektowany i poprawnie zaimplementowany oraz na tyle, aby działać jako dodatkowa dokumentacja dla tego, czego może oczekiwać rozmówca.

Jon Skeet
źródło
31

Jak zawsze: sprawdź, co ma robić metoda!

Czy powinien gdzieś zmienić stan globalny (uuh, zapach kodu!)?

Czy powinien wywołać interfejs?

Czy powinien zgłosić wyjątek, gdy zostanie wywołany z niewłaściwymi parametrami?

Czy powinien zgłaszać żaden wyjątek, gdy zostanie wywołany z odpowiednimi parametrami?

Czy powinno ...?

David Schmitt
źródło
11

Void zwracane typy / podprogramy to stare wiadomości. Nie wykonałem zwrotu typu Void (chyba, że ​​byłem wyjątkowo leniwy) od 8 lat (od czasu tej odpowiedzi, a więc trochę przed tym pytaniem).

Zamiast metody takiej jak:

public void SendEmailToCustomer()

Utwórz metodę zgodną z paradygmatem int.TryParse () firmy Microsoft:

public bool TrySendEmailToCustomer()

Być może nie ma żadnych informacji, które Twoja metoda musiałaby zwrócić do użycia na dłuższą metę, ale zwracanie stanu metody po jej wykonaniu jest bardzo przydatne dla wywołującego.

Ponadto bool nie jest jedynym typem stanu. Wiele razy poprzednio utworzona podprocedura mogła w rzeczywistości zwrócić trzy lub więcej różnych stanów (dobry, normalny, zły itp.). W takich przypadkach po prostu użyjesz

public StateEnum TrySendEmailToCustomer()

Jednak podczas gdy Try-Paradigm w pewnym stopniu odpowiada na to pytanie, jak przetestować zwrot z pustej przestrzeni, istnieją również inne kwestie. Na przykład, podczas / po cyklu „TDD”, będziesz „refaktoryzować” i zauważysz, że robisz dwie rzeczy swoją metodą… łamiąc w ten sposób „zasadę pojedynczej odpowiedzialności”. Więc najpierw należy się tym zająć. Po drugie, mogłeś zidentyfikować zależność ... dotykasz „Trwałe” dane.

Jeśli zajmujesz się dostępem do danych w metodzie, o której mowa, musisz dokonać refaktoryzacji w architekturę n-warstwową lub n-warstwową. Ale możemy założyć, że kiedy mówisz „Ciągi znaków są następnie wstawiane do bazy danych”, w rzeczywistości masz na myśli wywołanie warstwy logiki biznesowej lub coś w tym rodzaju. Tak, założymy, że.

Kiedy tworzony jest obiekt, rozumiesz teraz, że obiekt ma zależności. To wtedy musisz zdecydować, czy zamierzasz wykonać Dependency Injection na obiekcie, czy na metodzie. Oznacza to, że Twój Konstruktor lub metoda, o której mowa, potrzebuje nowego parametru:

public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)

Teraz, gdy możesz zaakceptować interfejs obiektu warstwy biznesowej / danych, możesz wyszydzać go podczas testów jednostkowych i nie mieć żadnych zależności ani obawiać się „przypadkowych” testów integracji.

Więc w swoim aktywnym kodzie przekazujesz PRAWDZIWY IBusinessDataEtcobiekt. Ale w testach jednostkowych przekazujesz IBusinessDataEtcobiekt MOCK . W tym makiecie można dołączyć właściwości niezwiązane z interfejsem, takie jak int XMethodWasCalledCountlub coś, czego stan (y) są aktualizowane, gdy wywoływane są metody interfejsu.

Więc twój test jednostkowy przejdzie przez twoją metodę (y) -w pytaniu, wykona jakąkolwiek logikę, którą ma i wywoła jedną lub dwie lub wybrany zestaw metod w twoim IBusinessDataEtcobiekcie. Kiedy wykonujesz swoje asercje na koniec testu jednostkowego, masz teraz kilka rzeczy do przetestowania.

  1. Stan „procedury podrzędnej”, która jest teraz metodą Try-Paradigm.
  2. Stan twojego IBusinessDataEtcobiektu Mock .

Aby uzyskać więcej informacji na temat pomysłów na iniekcję zależności na poziomie konstrukcji ... w odniesieniu do testów jednostkowych ... zapoznaj się z wzorcami projektowymi programu Builder. Dodaje jeszcze jeden interfejs i klasę dla każdego aktualnego interfejsu / klasy, ale są one bardzo małe i zapewniają OGROMNY wzrost funkcjonalności dla lepszego testowania jednostek.

Suamere
źródło
Pierwsza część tej wspaniałej odpowiedzi służy jako niesamowita ogólna rada dla wszystkich początkujących / średnio zaawansowanych programistów.
pimbrouwers
czy nie powinno to być coś takiego public void sendEmailToCustomer() throws UndeliveredMailException?
A.Emad
1
@ A.Emad Dobre pytanie, ale nie. Kontrolowanie przepływu kodu przez wyrzucanie wyjątków jest od dawna znaną złą praktyką. Chociaż w voidmetodach, zwłaszcza w językach zorientowanych obiektowo, była to jedyna prawdziwa opcja. Najstarszą alternatywą Microsoftu jest Try-Paradigm, o którym mówię, oraz paradygmaty w stylu funkcjonalnym, takie jak Monads / Maybes. W ten sposób polecenia (w CQS) mogą nadal zwracać cenne informacje o statusie zamiast polegać na rzucaniu, co jest podobne do GOTO(co wiemy, że jest złe). rzucanie (i goto) jest powolne, trudne do debugowania i nie jest dobrą praktyką.
Suamere
Dziękuję za wyjaśnienie tego. Czy generowanie wyjątków nawet w językach takich jak Java i C ++ jest złą praktyką?
A.Emad
9

Spróbuj tego:

[TestMethod]
public void TestSomething()
{
    try
    {
        YourMethodCall();
        Assert.IsTrue(true);
    }
    catch {
        Assert.IsTrue(false);
    }
}
Nathan Alard
źródło
1
Nie powinno to być konieczne, ale można to zrobić jako takie
Nathan Alard
Witamy w StackOverflow! Rozważ dodanie wyjaśnienia do swojego kodu. Dziękuję Ci.
Aurasphere
2
Ma ExpectedAttributena celu uczynienie tego testu jaśniejszym.
Martin Liversage
8

Możesz nawet spróbować w ten sposób:

[TestMethod]
public void ReadFiles()
{
    try
    {
        Read();
        return; // indicates success
    }
    catch (Exception ex)
    {
        Assert.Fail(ex.Message);
    }
}
Reyan Chougle
źródło
1
Przypuszczam, że to najprostszy sposób.
Navin Pandit
5

będzie miało jakiś wpływ na obiekt ... Zapytanie o wynik efektu. Jeśli nie ma widocznego efektu, nie warto go testować jednostkowo!

Keith Nicholas
źródło
4

Przypuszczalnie metoda coś robi i po prostu nie wraca?

Zakładając, że tak jest, to:

  1. Jeśli modyfikuje stan swojego obiektu właściciela, powinieneś sprawdzić, czy stan zmienił się poprawnie.
  2. Jeśli przyjmuje jakiś obiekt jako parametr i modyfikuje ten obiekt, to powinieneś sprawdzić, czy obiekt jest poprawnie zmodyfikowany.
  3. Jeśli zgłasza wyjątki w określonych przypadkach, sprawdź, czy te wyjątki są poprawnie zgłaszane.
  4. Jeśli jego zachowanie różni się w zależności od stanu jego własnego obiektu lub innego obiektu, ustaw stan i sprawdź, czy metoda ma poprawną I przez jedną z trzech powyższych metod testowych).

Jeśli dasz nam znać, co robi ta metoda, mogę być bardziej szczegółowy.

David Arno
źródło
3

Użyj Rhino Mocks, aby określić, jakich wywołań, akcji i wyjątków można się spodziewać. Zakładając, że możesz kpić lub ignorować części swojej metody. Trudno to wiedzieć bez znajomości szczegółów dotyczących metody, a nawet kontekstu.

gołąb
źródło
1
Odpowiedzią na to, jak przeprowadzić testy jednostkowe, nigdy nie powinno być zdobycie narzędzia innej firmy, które zrobi to za Ciebie. Chociaż, gdy dana osoba wie, jak przeprowadzić testy jednostkowe, użycie narzędzia innej firmy, aby to ułatwić, jest dopuszczalne.
Suamere
2

Zależy od tego, co robi. Jeśli ma parametry, przekaż makiety, o które możesz zapytać później, czy zostały wywołane z odpowiednim zestawem parametrów.

André
źródło
Zgoda - weryfikacja zachowania makiet testujących metodę byłaby jednokierunkowa.
Jeff Schumacher
0

Niezależnie od instancji, której używasz do wywołania metody void, możesz po prostu użyć,Verfiy

Na przykład:

W moim przypadku _Logjest to instancja i LogMessagemetoda do przetestowania:

try
{
    this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
}
Catch 
{
    Assert.IsFalse(ex is Moq.MockException);
}

Czy Verifyzgłasza wyjątek z powodu niepowodzenia metody, której test się nie powiedzie?

Shreya Kesharkar
źródło