Programowanie kontraktowe a test jednostkowy

13

Jestem nieco defensywnym programistą i wielkim fanem kontraktów Microsofts Code.

Teraz nie zawsze mogę używać C # iw większości języków jedynym narzędziem, jakie mam, jest twierdzenie. Więc zwykle kończę na takim kodzie:

class
{       
    function()
    {   
         checkInvariants();
         assert(/* requirement */);

         try
         {
             /* implementation */
         }
         catch(...)
         {
              assert(/* exceptional ensures */);                  
         }
         finally
         {
              assert(/* ensures */);
              checkInvariants();
         }
    }

    void checkInvariants()
    {
         assert(/* invariant */);
    }
}

Jednak ten paradygmat (lub jakkolwiek byś go nazwał) prowadzi do zaśmiecenia kodu.

Zacząłem się zastanawiać, czy to naprawdę jest warte wysiłku i czy odpowiedni test jednostkowy już to obejmie?

ronag
źródło
6
Chociaż testy jednostkowe pozwalają przenieść twierdzenia poza kod aplikacji (a tym samym uniknąć bałaganu), należy wziąć pod uwagę, że nie mogą sprawdzić wszystkiego, co może się zdarzyć w prawdziwym systemie produkcyjnym. Tak więc umowy na kod IMO mają pewne zalety, szczególnie w przypadku kodu krytycznego, w którym poprawność jest szczególnie ważna.
Giorgio
Czy to w zasadzie czas programowania, łatwość konserwacji, czytelność w porównaniu do lepszego pokrycia kodu?
ronag
1
Najczęściej używam asercji w kodzie, aby sprawdzić poprawność parametrów (na przykład na zeru), aw teście jednostkowym dodatkowo sprawdzam te twierdzenia.
artjom

Odpowiedzi:

14

Nie sądzę, że powinieneś myśleć o tym jak o „kontra”.
Jak wspomniano w komentarzach @Giorgio, umowy na kod mają sprawdzać niezmienniki (w środowisku produkcyjnym), a testy jednostkowe mają na celu upewnienie się, że kod działa zgodnie z oczekiwaniami, gdy te warunki zostaną spełnione.

duros
źródło
2
Myślę, że ważne jest również sprawdzenie, czy kod działa (np. Zgłasza wyjątek), gdy warunki nie są spełnione.
svick,
6

Kontrakty pomagają ci w co najmniej jednej rzeczy, której nie mają testy jednostkowe. Podczas opracowywania publicznego interfejsu API nie można testować jednostkowo, w jaki sposób ludzie używają Twojego kodu. Możesz jednak nadal definiować umowy dla swoich metod.

Osobiście byłbym tak rygorystyczny w odniesieniu do umów tylko w przypadku publicznego interfejsu API modułu. W wielu innych przypadkach prawdopodobnie nie jest to warte wysiłku (i zamiast tego możesz użyć testów jednostkowych), ale to tylko moja opinia.

To nie znaczy, że odradzam myślenie o umowach w takich przypadkach. Zawsze o nich myślę. Po prostu nie uważam za konieczne, aby zawsze jawnie je kodować.

Honza Brabec
źródło
1

Jak już wspomniano, umowy i testy jednostkowe mają inny cel.

Kontrakty dotyczą programowania obronnego, aby upewnić się, że spełnione są warunki wstępne, kod jest wywoływany z właściwymi parametrami itp.

Testy jednostkowe w celu upewnienia się, że kod działa, w różnych scenariuszach. Są jak „specyfikacje z zębami”.

Aserty są dobre, dzięki czemu kod jest niezawodny. Jeśli jednak obawiasz się, że dodaje on dużo kodu, możesz również chcieć dodać warunkowe punkty przerwania w niektórych miejscach podczas debugowania i zmniejszyć liczbę Assert.

Sajad Deyargaroo
źródło
0

Wszystko, co masz w wywołaniach checkVariants (), można wykonać na podstawie testów, ile wysiłku może w rzeczywistości zależeć od wielu rzeczy (zależności zewnętrzne, poziomy sprzężenia itp.), Ale wyczyściłby kod z jednego punktu widzenia. Nie jestem pewien, jak sprawdzalna byłaby baza kodu opracowana względem twierdzeń bez pewnego refaktoryzacji.

Zgadzam się z @duros, nie należy ich traktować jako podejść wyłącznych lub konkurencyjnych. W rzeczywistości w środowisku TDD można nawet argumentować, że „wymagania” będą wymagały testów :).

Twierdzi jednak, że NIE poprawiaj kodu, chyba że faktycznie zrobisz coś, aby naprawić nieudane sprawdzenie, po prostu przestają one ulegać uszkodzeniu lub podobnym, zwykle przez przerwanie przetwarzania przy pierwszych oznakach problemów.

Testowane / dobrze przetestowane rozwiązanie zazwyczaj już myślało i / lub odkryło wiele źródeł / przyczyn złych danych wejściowych i wyjściowych podczas opracowywania komponentów wchodzących w interakcję i zajmowało się nimi bliżej źródła problemu.

Jeśli twoje źródło jest zewnętrzne i nie masz nad nim kontroli, to aby uniknąć zaśmiecania kodu podczas rozwiązywania problemów z innymi kodami, możesz rozważyć wdrożenie jakiegoś elementu do czyszczenia / asercji danych między źródłem a twoim komponentem i umieść tam swoje kontrole .

Ciekawe też, jakich języków używasz, które nie mają jakiegoś rodzaju xUnit lub innej biblioteki testowej, którą ktoś opracował. Myślałem, że jest coś na wszystko w dzisiejszych czasach?

Chris Lee
źródło
0

Oprócz testów jednostkowych i kontraktów z kodem, po prostu pomyślałem, że zwrócę uwagę na inny aspekt, którym jest wartość definiowania interfejsów, tak aby wyeliminować lub ograniczyć możliwość niepoprawnego wywoływania kodu w pierwszej kolejności.

Nie zawsze jest to łatwe lub możliwe, ale zdecydowanie warto zadać sobie pytanie: „Czy mogę uczynić ten kod bardziej niezawodnym?”

Anders Hejlsberg, twórca C #, powiedział, że jednym z największych błędów w C # nie było uwzględnienie typów zerowalnych. Jest to jeden z głównych powodów, dla których istnieje tak bardzo potrzebny bałagan kodu ochronnego.

Mimo to refaktoryzacja, aby mieć tylko niezbędną i wystarczającą ilość kodu ochronnego, czyni z mojego doświadczenia bardziej użyteczny i łatwy w utrzymaniu kod.

Zgadzam się z @duros na pozostałej części.

James World
źródło
0

Wykonaj obie te czynności, ale zastosuj statyczne metody pomocnicze, aby wyjaśnić swoje intencje. Oto, co Google zrobiło dla Javy, sprawdź code.google.com/p/guava-libraries/wiki/PreconditionsExplained

Alexander Torstling
źródło