Czy należy przetestować wartości wyliczenia za pomocą testów jednostkowych?

15

Jeśli masz wyliczenie tylko z wartościami (nie ma metod, które można wykonać w Javie), a wyliczenie to jest częścią biznesowej definicji systemu, czy należy napisać dla niego testy jednostkowe?

Myślałem, że powinny być napisane, nawet jeśli mogłyby wydawać się proste i zbędne. Uważam, że to, co dotyczy specyfikacji biznesowej, powinno być wyraźnie napisane w teście, czy to z jednostką / integracją / interfejsem użytkownika / itp. testy lub przy użyciu systemu typów języka jako metody testowania. Ponieważ wartości, które musi mieć wyliczenie (np. W Javie), z punktu widzenia biznesu, nie mogą być testowane przy użyciu systemu typów, myślę, że powinien istnieć test jednostkowy.

To pytanie nie jest podobne do tego, ponieważ nie dotyczy tego samego problemu, co moje. W tym pytaniu jest funkcja biznesowa (savePeople), a osoba pyta o implementację wewnętrzną (forEach). Tam jest środkowa warstwa biznesowa (funkcja save people), która zawiera konstrukcję językową (forEach). Tutaj konstrukcja języka (enum) jest używana do określenia zachowania z biznesowego punktu widzenia.

W tym przypadku szczegół implementacji pokrywa się z „prawdziwą naturą” danych, to znaczy: zbiorem (w sensie matematycznym) wartości. Można prawdopodobnie użyć niezmiennego zestawu, ale te same wartości powinny nadal tam być. Jeśli używasz tablicy, to samo należy zrobić, aby przetestować logikę biznesową. Myślę, że tutaj zagadką jest to, że konstrukcja języka bardzo dobrze pokrywa się z charakterem danych. Nie jestem pewien, czy wytłumaczyłem się poprawnie

IS1_SO
źródło
19
Jak dokładnie wyglądałby test jednostkowy wyliczenia?
jonrsharpe
@jonrsharpe Zapewniłoby to, że wartości zawarte w wyliczeniu są tymi, których oczekujesz. Zrobiłbym to, iterując wartości wyliczenia, dodając je do zbioru, na przykład jako ciągi znaków. Zamów ten zestaw. Porównać zestaw z uporządkowaną listą wartości zapisanych ręcznie w teście. Powinny pasować.
IS1_SO
1
@jonrsharpe, lubię myśleć o testach jednostkowych również jako „definicjach” lub „wymaganiach” zapisanych w kodzie. Test jednostkowy wyliczenia byłby tak prosty, jak sprawdzenie liczby elementów w wyliczeniu i ich wartości. Zwłaszcza w języku C #, gdzie wyliczenia nie są klasami, ale mogą być bezpośrednio mapowane na liczby całkowite, gwarantując ich wartości, a nie programowanie przypadkowe może okazać się przydatne do celów serializacji.
Machado
2
IMHO nie jest o wiele bardziej przydatne niż testowanie, czy 2 + 2 = 4 w celu sprawdzenia, czy Wszechświat ma rację. Testujesz kod za pomocą tego wyliczenia, a nie samego wyliczenia.
Agent_L,

Odpowiedzi:

39

Jeśli masz wyliczenie tylko z wartościami (nie ma metod, które można wykonać w Javie), a wyliczenie to jest częścią biznesowej definicji systemu, czy należy napisać dla niego testy jednostkowe?

Nie, to tylko stan.

Zasadniczo fakt, że używasz wyliczenia, jest szczegółem implementacji ; jest to coś, co możesz chcieć zmienić na inny projekt.

Testowanie wyliczeń pod kątem kompletności jest analogiczne do sprawdzania, czy obecne są wszystkie reprezentowalne liczby całkowite.

Testowanie zachowań obsługiwanych przez wyliczenia jest jednak dobrym pomysłem. Innymi słowy, jeśli zaczniesz od pozytywnego zestawu testów i skomentujesz dowolną pojedynczą wartość wyliczenia, to co najmniej jeden test powinien zakończyć się niepowodzeniem (błędy kompilacji są uważane za niepowodzenia).

VoiceOfUnreason
źródło
5
Ale w tym przypadku szczegół implementacji pokrywa się z „prawdziwą naturą” danych, to znaczy: zbiorem (w sensie matematycznym) wartości. Można prawdopodobnie użyć niezmiennego zestawu, ale te same wartości powinny nadal tam być. Jeśli używasz tablicy, to samo należy zrobić, aby przetestować logikę biznesową. Myślę, że tutaj zagadką jest to, że konstrukcja języka bardzo dobrze pokrywa się z charakterem danych. Nie jestem pewien, czy wytłumaczyłem się poprawnie.
IS1_SO
4
@ IS1_SO - do tego stopnia, że ​​VOU powinien zakończyć się niepowodzeniem jednego testu: czy to? W takim przypadku nie trzeba specjalnie testować Enum. Czyż nie Może to znak, że można modelować swój kod prościej i stworzyć abstrakcję nad „prawdziwej natury” danych - np niezależnie od kart w talii, czy naprawdę trzeba mieć reprezentację [ Hearts, Spades, Diamonds, Clubs], jeśli Czy kiedykolwiek karty, jeśli karta jest czerwona / czarna?
anotherdave
1
@ IS1_SO pozwala powiedzieć, że masz wyliczenie kodów błędów i chcesz zgłosić null_ptrbłąd. Teraz ma kod błędu przez wyliczanie. Kod sprawdzający:null_ptr błędu również wyszukuje kod za pomocą wyliczenia. Może więc mieć wartość 5(np.). Teraz musisz dodać kolejny kod błędu. Wyliczenie zostało zmienione (powiedzmy, że dodajemy nowe na górze wyliczenia) Wartość null_ptrwynosi teraz 6. Czy to problem? teraz zwracasz kod błędu 6i testujesz 6. Tak długo, jak wszystko jest logicznie spójne, nic ci nie jest, mimo że ta zmiana przełamuje twój test teoretyczny.
Baldrickk
17

Nie testujesz deklaracji enum . Możesz sprawdzić, czy wejście / wyjście funkcji ma oczekiwane wartości wyliczenia. Przykład:

enum Parity {
    Even,
    Odd
}

Parity GetParity(int x) { ... }

Państwo nie pisać testy sprawdzające następnie ENUM Paritydefiniuje nazwy Eveni Odd. Taki test byłby bezcelowy, ponieważ wystarczyłoby powtórzyć to, co zostało już określone w kodzie. Powiedzenie tego samego dwa razy nie poprawi tego.

Ci zrobić testy weryfikujące zapisu GetParitygłosu powróci Evendo 0, Odddla 1 i tak dalej. Jest to cenne, ponieważ nie powtarzasz kodu, weryfikujesz zachowanie kodu, niezależnie od implementacji. Gdyby kod w środku GetParityzostał całkowicie przepisany, testy byłyby nadal ważne. Rzeczywiście, główne zalety testów jednostkowych polegają na tym, że dają one swobodę bezpiecznego przepisywania i ponownego kodowania kodu, zapewniając, że kod nadal działa zgodnie z oczekiwaniami.

Ale jeśli masz test, który zapewnia, że deklaracja wyliczenia określa oczekiwane nazwy, to każda zmiana, którą wprowadzisz w wyliczeniu w przyszłości, będzie wymagać również zmiany testu. Oznacza to, że nie jest to tylko dwa razy więcej pracy, ale oznacza to również utratę wszelkich korzyści z testu jednostkowego. Jeśli musisz zmienić kod i przetestować w tym samym czasie , nie ma zabezpieczenia przed wprowadzaniem błędów.

JacquesB
źródło
Zaktualizowałem moje pytanie, starając się odpowiedzieć na tę odpowiedź, sprawdź, czy to pomoże.
IS1_SO
@ IS1_SO: OK, co mnie myli - czy dynamicznie generujesz wartości wyliczeniowe, czy co się dzieje?
JacquesB
Nie. Chodziło mi o to, że w tym przypadku konstrukcja języka wybrana do reprezentowania wartości jest wyliczeniem. Ale jak wiemy, jest to szczegół implementacji. Co się stanie, jeśli wybierzesz tablicę lub zestaw <> (w Javie) lub ciąg znaków z niektórymi tokenami separacji reprezentującymi wartości? W takim przypadku warto przetestować, czy zawarte w nim wartości są przedmiotem zainteresowania firmy. O to mi chodzi. Czy to wyjaśnienie pomaga?
IS1_SO
3
@ IS1_SO: Czy mówisz o testowaniu wystąpienia enum zwróconego z funkcji o określonej oczekiwanej wartości? Ponieważ tak, możesz to przetestować. Po prostu nie musisz testować samej deklaracji enum.
JacquesB
11

Jeśli istnieje ryzyko, że zmiana wyliczenia spowoduje uszkodzenie kodu, to na pewno wszystko z atrybutem [Flagi] w języku C # byłoby dobrym rozwiązaniem, ponieważ dodanie wartości między 2 a 4 (3) byłoby bitowe 1 i 2 zamiast dyskretny przedmiot.

To warstwa ochrony.

Powinieneś rozważyć zastosowanie enum kodeksu postępowania, który znają wszyscy programiści. Nie polegaj na tekstowych reprezentacjach wyliczenia jest częstym, ale może to kolidować z twoimi wytycznymi serializacji.

Widziałem, jak ludzie „poprawiają” wielkie litery wpisów enum, sortują je alfabetycznie lub według innych logicznych grup, z których wszystkie złamały inne części złego kodu.

Ian
źródło
5
Jeśli wartości liczbowe wyliczenia są używane w dowolnym miejscu, na przykład podczas przechowywania w bazie danych, wówczas zmiana kolejności (w tym usunięcie lub wstawienie przed ostatnią wartością) może spowodować, że istniejące rekordy staną się nieważne.
stannius
3
+1, ta odpowiedź jest niedoceniana. Jeśli twoje wyliczenia są częścią serializacji, interfejsu wejściowego z zewnętrznym słowem lub bitowalną kompozycją informacji, na pewno będą musiały zostać przetestowane pod kątem spójności w każdej wersji systemu. Przynajmniej jeśli martwisz się kompatybilnością wsteczną, co zwykle jest dobrą rzeczą.
Machado
11

Nie, test sprawdzający, czy wyliczenie zawiera wszystkie prawidłowe wartości i nic więcej zasadniczo nie powtarza deklaracji wyliczenia. Testowałbyś tylko, czy język poprawnie implementuje konstrukcję enum, co jest testem bezsensownym.

To powiedziawszy, powinieneś przetestować zachowanie, które zależy od wartości wyliczeniowych. Na przykład, jeśli używasz wartości wyliczania do szeregowania bytów do json lub cokolwiek, lub przechowujesz wartości w bazie danych, powinieneś przetestować zachowanie dla wszystkich wartości wyliczenia. W ten sposób, jeśli wyliczenie zostanie zmodyfikowane, przynajmniej jeden z testów powinien zakończyć się niepowodzeniem. W każdym razie testowałbyś zachowanie wokół twojego wyliczenia, a nie samą deklarację wyliczania.

jesm00
źródło
3

Twój kod powinien działać poprawnie niezależnie od rzeczywistych wartości wyliczenia. W takim przypadku nie są wymagane żadne testy jednostkowe.

Ale możesz mieć kod, w którym zmiana wartości wyliczeniowej spowoduje uszkodzenie. Na przykład, jeśli wartość wyliczenia jest przechowywana w pliku zewnętrznym, a po zmianie wartości wyliczenia odczytanie pliku zewnętrznego da zły wynik. W takim przypadku będziesz mieć WIELKI komentarz w pobliżu wyliczenia, ostrzegający nikogo, aby nie modyfikował żadnych wartości, i możesz bardzo dobrze napisać test jednostkowy, który sprawdza wartości liczbowe.

gnasher729
źródło
1

Ogólnie rzecz biorąc, samo sprawdzenie, czy wyliczenie ma zakodowaną listę wartości, nie ma dużej wartości, jak powiedziano w innych odpowiedziach, ponieważ wtedy wystarczy zaktualizować test i wyliczyć razem.

Kiedyś miałem przypadek, że jeden moduł używał typów wyliczeń z dwóch innych modułów i mapował między nimi. (Jedna z wyliczeń miała dodatkową logikę, druga dotyczyła dostępu do bazy danych, obie miały zależności, które powinny być od siebie odizolowane).

W tym przypadku dodałem test (w module odwzorowania), który zweryfikował, że wszystkie wpisy wyliczenia w wyliczeniu źródłowym istnieją również w wyliczeniu docelowym (a zatem, że odwzorowanie zawsze będzie działać). (W niektórych przypadkach sprawdziłem też na odwrót).

W ten sposób, gdy ktoś dodał wpis do jednego z wyliczeń i zapomniał dodać odpowiedni wpis do drugiego, test zaczął się nie udać.

Paŭlo Ebermann
źródło
1

Wyliczenia są po prostu typami skończonymi o niestandardowych (miejmy nadzieję znaczących) nazwach. Wyliczenie może mieć tylko jedną wartość, np. voidKtóra zawiera tylko null(niektóre języki nazywają to uniti używają nazwy voidwyliczenia bez elementów!). Może mieć dwie wartości, takie jak boolktóra ma falsei true. Może mieć trzy, jak colourChannelz red, greeniblue . I tak dalej.

Jeśli dwa wyliczenia mają tę samą liczbę wartości, wówczas są „izomorficzne”; tzn. jeśli będziemy systematycznie zamieniać wszystkie nazwy, możemy użyć jednego zamiast drugiego, a nasz program nie będzie się zachowywał inaczej. W szczególności nasze testy nie będą się zachowywać inaczej!

Na przykład, resultzawierającego win/ lose/ drawjest izomorficzna z powyższym colourChannel, ponieważ możemy wymienić na przykład colourChannelz result, redz win, greenz losei bluez draw, i tak długo, jak to zrobić wszędzie (producentów i konsumentów, parser i serialisers, wpisy do bazy danych, pliki dziennika itp ), wówczas w naszym programie nie będzie żadnych zmian. Wszelkie „ colourChanneltesty”, które napisaliśmy, nadal będą zaliczane, nawet jeśli nie macolourChannel !

Ponadto, jeśli wyliczenie zawiera więcej niż jedną wartość, zawsze możemy zmienić te wartości, aby uzyskać nowy wyliczenie z taką samą liczbą wartości. Ponieważ liczba wartości się nie zmieniła, nowy układ jest izomorficzny w stosunku do starego, a zatem moglibyśmy zmienić wszystkie nazwy, a nasze testy nadal by się udały (pamiętaj, że nie możemy po prostu zmienić definicji; musimy nadal wyłącz wszystkie witryny użytkowania).

Oznacza to, że w odniesieniu do maszyny, wyliczenia są „nazwami rozróżnialnymi” i niczym więcej . Jedyne, co możemy zrobić z wyliczeniem, to ustalić, czy dwie wartości są takie same (np. red/ red) Czy różne (np. red/ blue). Jest to jedyna rzecz, jaką może zrobić „test jednostkowy”, np

(  red == red  ) || throw TestFailure;
(green == green) || throw TestFailure;
( blue == blue ) || throw TestFailure;
(  red != green) || throw TestFailure;
(  red != blue ) || throw TestFailure;
...

Jak mówi @ jesm00, taki test sprawdza raczej implementację języka niż program. Testy te nigdy nie są dobrym pomysłem: nawet jeśli nie ufasz implementacji języka, powinieneś przetestować ją z zewnątrz , ponieważ nie można ufać, że poprawnie uruchomi testy!

To jest teoria; co z praktyką? Głównym problemem związanym z tą charakterystyką wyliczeń jest to, że programy z „prawdziwego świata” rzadko są samodzielne: mamy starsze wersje, wdrożenia zdalne / osadzone, dane historyczne, kopie zapasowe, bazy danych na żywo itp., Więc nigdy nie możemy tak naprawdę „zmienić się” wszystkie wystąpienia nazwy bez utraty niektórych zastosowań.

Jednak takie rzeczy nie są „odpowiedzialnością” samego wyliczenia: zmiana wyliczenia może przerwać komunikację ze zdalnym systemem, ale odwrotnie możemy to naprawić taki problem, zmieniając wyliczenie!

W takich sytuacjach, enum jest czerwony śledź: co, jeśli jeden system musi to być ten sposób, a inny musi to być to sposób? Nie może to być jedno i drugie, bez względu na to, ile testów piszemy! Prawdziwym winowajcą jest tutaj interfejs wejścia / wyjścia, który powinien produkować / konsumować dobrze zdefiniowane formaty, a nie „dowolną liczbę całkowitą, jaką wybiera interpretacja”. Tak więc prawdziwym rozwiązaniem jest przetestowanie interfejsów we / wy : z testami jednostkowymi, aby sprawdzić, czy parsuje / drukuje oczekiwany format, oraz z testami integracyjnymi, aby sprawdzić, czy format jest rzeczywiście akceptowany przez drugą stronę.

Wciąż możemy się zastanawiać, czy wyliczenie jest „wystarczająco dokładnie wykonywane”, ale w tym przypadku wyliczenie to znów śledź czerwony. To, co nas tak naprawdę martwi, to sam zestaw testowy . Możemy zyskać zaufanie tutaj na kilka sposobów:

  • Pokrycie kodu może nam powiedzieć, czy różnorodność wartości wyliczeniowych pochodzących z zestawu testów wystarcza do uruchomienia różnych gałęzi kodu. Jeśli nie, możemy dodać testy, które wyzwalają odkryte gałęzie lub wygenerować szerszą różnorodność wyliczeń w istniejących testach.
  • Sprawdzanie właściwości może nam powiedzieć, czy różnorodność gałęzi w kodzie jest wystarczająca, aby obsłużyć możliwości środowiska wykonawczego. Na przykład, jeśli kod obsługuje tylko redi testujemy tylko red, mamy 100% pokrycia. Kontroler właściwości (spróbuje) wygenerować kontrprzykłady do naszych twierdzeń, takich jak generowanie wartości greeni blue, o których zapomnieliśmy przetestować.
  • Testowanie mutacji może nam powiedzieć, czy nasze twierdzenia faktycznie sprawdzają wyliczenie, a nie tylko śledzenie rozgałęzień i ignorowanie ich różnic.
Warbo
źródło
1

Nie. Testy jednostkowe służą do testowania jednostek.

W programowaniu obiektowym jednostka jest często całym interfejsem, takim jak klasa, ale może być indywidualną metodą.

https://en.wikipedia.org/wiki/Unit_testing

Zautomatyzowanym testem dla zadeklarowanego wyliczenia będzie testowanie integralności języka i platformy, na której działa, a nie logika w kodzie autorstwa autora. Nie miałoby to żadnego pożytecznego celu - dołączona dokumentacja, ponieważ kod deklarujący wyliczenie służy zarówno jako dokumentacja, jak i kod, który by to przetestował.

digimunk
źródło
0

Oczekuje się, że przetestujesz obserwowalne zachowanie twojego kodu, wpływ wywołań metod / funkcji na obserwowalny stan. Tak długo, jak kod działa prawidłowo, nie musisz testować niczego innego.

Nie musisz jawnie stwierdzać, że typ wyliczenia zawiera wpisy, których się spodziewasz, podobnie jak nie stwierdzasz wprost, że klasa faktycznie istnieje lub że ma metody i atrybuty, których oczekujesz.

W rzeczywistości, testując zachowanie, domyślnie zapewniasz, że klasy, metody i wartości uczestniczące w teście istnieją, więc nie musisz tego jawnie potwierdzać.

Pamiętaj, że do poprawnego działania kodu nie potrzebujesz sensownych nazw, to tylko wygoda dla osób czytających Twój kod. Można zrobić swoją pracę kod z wartościami enum takich jak foo, bar... jak i metod frobnicate().

Przestań krzywdzić Monikę
źródło