Wyliczenia są po prostu typami skończonymi o niestandardowych (miejmy nadzieję znaczących) nazwach. Wyliczenie może mieć tylko jedną wartość, np. void
Która zawiera tylko null
(niektóre języki nazywają to unit
i używają nazwy void
wyliczenia bez elementów!). Może mieć dwie wartości, takie jak bool
która ma false
i true
. Może mieć trzy, jak colourChannel
z red
, green
iblue
. 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, result
zawierającego win
/ lose
/ draw
jest izomorficzna z powyższym colourChannel
, ponieważ możemy wymienić na przykład colourChannel
z result
, red
z win
, green
z lose
i blue
z 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 „ colourChannel
testy”, 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
red
i testujemy tylko red
, mamy 100% pokrycia. Kontroler właściwości (spróbuje) wygenerować kontrprzykłady do naszych twierdzeń, takich jak generowanie wartości green
i 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.