Pracuję nad komparatorem list, aby pomóc w sortowaniu nieuporządkowanej listy wyników wyszukiwania według bardzo konkretnych wymagań od naszego klienta. Wymagania wymagają algorytmu rankingu zgodnego z następującymi regułami w kolejności ważności:
- Dokładne dopasowanie do nazwy
- Wszystkie słowa wyszukiwanego hasła w nazwie lub synonim wyniku
- Niektóre słowa zapytania wyszukiwania w nazwie lub synonimie wyniku (% malejąco)
- Wszystkie słowa zapytania wyszukiwania w opisie
- Niektóre słowa zapytania wyszukiwania w opisie (% malejąco)
- Data ostatniej modyfikacji malejąco
Naturalnym wyborem projektu dla tego komparatora wydawał się ranking punktowy oparty na potęgach 2. Suma mniej ważnych reguł nigdy nie może być więcej niż pozytywnym dopasowaniem reguły o wyższym znaczeniu. Osiąga się to poprzez następujący wynik:
- 32
- 16
- 8 (Drugi wynik w rozstrzygnięciu remisu na podstawie% malejąco)
- 4
- 2 (Drugi wynik w rozstrzygnięciu remisu na podstawie% malejąco)
- 1
W duchu TDD postanowiłem zacząć od testów jednostkowych. Posiadanie przypadku testowego dla każdego unikalnego scenariusza wymagałoby co najmniej 63 unikalnych przypadków testowych bez uwzględnienia dodatkowych przypadków testowych dla logiki dodatkowego przerywacza remisu w regułach 3 i 5. Wydaje się to przesadne.
Rzeczywiste testy będą w rzeczywistości mniej. W oparciu o same reguły, niektóre reguły zapewniają, że niższe reguły będą zawsze prawdziwe (np. Gdy „Wszystkie słowa z wyszukiwanego hasła pojawią się w opisie”, to reguła „Niektóre słowa z wyszukiwanego hasła pojawią się w opisie” zawsze będzie prawdziwa). Czy nadal warto wkładać wysiłek w napisanie każdego z tych przypadków testowych? Czy jest to poziom testowania, który jest zwykle wymagany przy mówieniu o 100% pokryciu testami w TDD? Jeśli nie, to jaka byłaby możliwa do zaakceptowania alternatywna strategia testowania?
źródło
Odpowiedzi:
Twoje pytanie sugeruje, że TDD ma coś wspólnego z „pisaniem najpierw wszystkich przypadków testowych”. IMHO nie jest to „w duchu TDD”, a wręcz przeciwnie . Pamiętaj, że TDD oznacza „test napędzany rozwoju”, więc trzeba tylko te przypadki testowe, które naprawdę „drive” implementacja, nie więcej. I dopóki twoja implementacja nie jest zaprojektowana w taki sposób, że liczba bloków kodu rośnie wykładniczo z każdym nowym wymaganiem, nie będziesz również potrzebował wykładniczej liczby przypadków testowych. W twoim przykładzie cykl TDD prawdopodobnie będzie wyglądał następująco:
Następnie zacznij od drugiego wymagania:
Nadchodzi haczyk : gdy dodasz przypadki testowe dla numeru wymagania / kategorii „n”, musisz jedynie dodać testy, aby upewnić się, że wynik kategorii „n-1” jest wyższy niż wynik dla kategorii „n” . Nie będziesz musiał dodawać żadnych przypadków testowych dla każdej innej kombinacji kategorii 1, ..., n-1, ponieważ testy, które wcześniej napisałeś, upewnią się, że wyniki tych kategorii będą nadal w prawidłowej kolejności.
To da ci liczbę przypadków testowych, które rosną w przybliżeniu liniowo wraz z liczbą wymagań, a nie wykładniczo.
źródło
Zastanów się, czy nie napisać klasy, która przejrzy predefiniowaną listę warunków i pomnoży bieżący wynik przez 2 za każde pomyślne sprawdzenie.
Można to bardzo łatwo przetestować za pomocą zaledwie kilku próbnych testów.
Następnie możesz napisać klasę dla każdego warunku, a dla każdego przypadku są tylko 2 testy.
Naprawdę nie rozumiem twojego przypadku użycia, ale mam nadzieję, że ten przykład pomoże.
Zauważysz, że twoje testy warunków 2 ^ szybko sprowadzają się do 4+ (warunki 2 *). 20 jest znacznie mniej narzucające się niż 64. A jeśli dodasz kolejną później, nie musisz zmieniać ŻADNEJ z istniejących klas (zasada otwarta-zamknięta), więc nie musisz pisać 64 nowych testów, po prostu masz aby dodać kolejną klasę z 2 nowymi testami i wprowadzić ją do swojej klasy ScoreBuilder.
źródło
Musisz zdefiniować „warto”. Problem z tego rodzaju scenariuszem polega na tym, że testy będą miały coraz mniejszą użyteczność. Z pewnością pierwszy test, który napiszesz, będzie całkowicie tego wart. Może znaleźć oczywiste błędy w priorytecie, a nawet takie rzeczy jak błędy parsowania podczas próby rozbicia słów.
Drugi test będzie tego wart, ponieważ obejmuje inną ścieżkę w kodzie, prawdopodobnie sprawdzając inną relację priorytetu.
63. test prawdopodobnie nie będzie tego wart, ponieważ jest to coś, na co masz pewność 99,99%, objęte logiką Twojego kodu lub innym testem.
Rozumiem, że 100% pokrycia oznacza, że wszystkie ścieżki kodu są wykonywane. Nie oznacza to, że wykonujesz wszystkie kombinacje reguł, ale wszystkie ścieżki, którymi Twój kod może pójść w dół (jak zauważyłeś, niektóre kombinacje nie mogą istnieć w kodzie). Ale ponieważ robisz TDD, nie ma jeszcze „kodu” do sprawdzenia ścieżek. List procesu powiedziałby, że wszystkie 63+.
Osobiście uważam, że 100% zasięgu to marzenie o fajce. Poza tym jest to niepragmatyczne. Istnieją testy jednostkowe, które mają ci służyć, a nie odwrotnie. Gdy wykonujesz więcej testów, zyski maleją z korzyści (prawdopodobieństwo, że test zapobiega błędowi + pewność, że kod jest poprawny). W zależności od tego, co robi twój kod, określa, gdzie na tej przesuwnej skali przestajesz wykonywać testy. Jeśli Twój kod działa na reaktorze jądrowym, być może wszystkie ponad 63 testy są tego warte. Jeśli Twój kod organizuje twoje archiwum muzyczne, prawdopodobnie możesz uciec o wiele mniej.
źródło
Twierdziłbym, że jest to idealny przypadek dla TDD.
Masz znany zestaw kryteriów do przetestowania, z logicznym podziałem tych przypadków. Zakładając, że przetestujesz je teraz lub później, wydaje się sensowne, aby wziąć znany wynik i zbudować go wokół siebie, upewniając się, że faktycznie przestrzegasz każdej reguły niezależnie.
Ponadto możesz dowiedzieć się na bieżąco, czy dodanie nowej reguły wyszukiwania narusza istniejącą regułę. Jeśli zrobisz to wszystko pod koniec kodowania, prawdopodobnie ryzykujesz koniecznością zmiany jednego, aby naprawić jeden, co psuje inny, co psuje inny ... I podczas wdrażania zasad dowiadujesz się, czy Twój projekt jest poprawny lub wymaga ulepszenia.
źródło
Nie jestem fanem ścisłego interpretowania 100% zakresu testów jako pisania specyfikacji dla każdej metody lub testowania każdej permutacji kodu. Robienie tego fanatycznie prowadzi do testowego zaprojektowania klas, które nie zawierają poprawnie logiki biznesowej i dają testy / specyfikacje, które są generalnie pozbawione znaczenia pod względem opisu obsługiwanej logiki biznesowej. Zamiast tego skupiam się na konstruowaniu testów podobnie jak same reguły biznesowe i staram się wykonywać każdą warunkową gałąź kodu za pomocą testów z wyraźnym oczekiwaniem, że testy te są łatwo zrozumiałe dla testera jako ogólnie przypadki użycia i faktycznie opisują reguły biznesowe, które zostały wdrożone.
Mając to na uwadze, wyczerpująco przetestowałbym jednostkowo 6 czynników rankingowych, które wymieniliście oddzielnie, a następnie przeprowadziliśmy 2 lub 3 testy w stylu integracji, które zapewnią, że wyniki zostaną zwiększone do oczekiwanych ogólnych wartości rankingu. Na przykład, przypadek nr 1, Dokładne dopasowanie do nazwy, miałbym co najmniej dwa testy jednostkowe, aby sprawdzić, kiedy jest dokładny, a kiedy nie, i że dwa scenariusze zwracają oczekiwany wynik. Jeśli rozróżniana jest wielkość liter, to również próba sprawdzenia „Dokładnego dopasowania” vs. „ścisłego dopasowania” i ewentualnie innych odmian wejściowych, takich jak interpunkcja, dodatkowe spacje itp., Również zwraca oczekiwane wyniki.
Po przejrzeniu wszystkich poszczególnych czynników przyczyniających się do wyników rankingu, zasadniczo zakładam, że działają one poprawnie na poziomie integracji i skupiam się na zapewnieniu, że ich połączone czynniki prawidłowo przyczyniają się do ostatecznego oczekiwanego wyniku rankingu.
Zakładając, że przypadki # 2 / # 3 i # 4 / # 5 są uogólnione na te same metody bazowe, ale przekazując różne pola, wystarczy napisać tylko jeden zestaw testów jednostkowych dla podstawowych metod i napisać proste dodatkowe testy jednostkowe, aby przetestować określone pola (tytuł, nazwa, opis itp.) i punktacja w wyznaczonym faktoringu, co dodatkowo zmniejsza redundancję całego wysiłku związanego z testowaniem.
Przy takim podejściu opisane powyżej podejście prawdopodobnie dałoby 3 lub 4 testy jednostkowe dla przypadku nr 1, być może 10 specyfikacji dla niektórych / wszystkich uwzględnionych synonimów - plus 4 specyfikacje dotyczące poprawnej oceny przypadków nr 2 - nr 5 i 2 do 3 specyfikacji w rankingu uporządkowanym według ostatecznej daty, a następnie od 3 do 4 testów poziomu integracji, które mierzą wszystkie 6 przypadków łącznie w prawdopodobny sposób (na razie zapomnij o niejasnych przypadkowych przypadkach, chyba że wyraźnie widzisz problem w kodzie, który należy rozwiązać, aby zapewnić ten warunek jest obsługiwany) lub zapewnić, że nie zostanie naruszony / złamany przez późniejsze wersje. Daje to około 25 specyfikacji do wykonania 100% napisanego kodu (nawet jeśli nie wywołałeś bezpośrednio 100% zapisanych metod).
źródło
Nigdy nie byłem fanem 100% pokrycia testowego. Z mojego doświadczenia wynika, że jeśli coś jest wystarczająco proste do przetestowania za pomocą tylko jednego lub dwóch przypadków testowych, to jest wystarczająco proste, aby rzadko zawieść. Kiedy się nie powiedzie, zwykle są to zmiany architektoniczne, które i tak wymagałyby zmian testowych.
Biorąc to pod uwagę, w przypadku wymagań takich jak twoje zawsze przeprowadzam dokładne testy jednostkowe, nawet w projektach osobistych, w których nikt mnie nie zmusza, ponieważ są to przypadki, w których testy jednostkowe oszczędzają czas i pogorszenie. Im więcej testów jednostkowych jest wymaganych, aby coś przetestować, tym więcej czasu zaoszczędzą testy jednostkowe.
To dlatego, że możesz trzymać w głowie tak wiele rzeczy naraz. Jeśli próbujesz napisać kod, który działa dla 63 różnych kombinacji, często trudno jest naprawić jedną kombinację bez rozbijania drugiej. W końcu ręcznie testujesz inne kombinacje. Testy ręczne są znacznie wolniejsze, co sprawia, że nie chcesz ponownie uruchamiać każdej możliwej kombinacji za każdym razem, gdy wprowadzasz zmiany. To sprawia, że bardziej prawdopodobne jest, że coś przeoczysz i będziesz tracić czas na szukanie ścieżek, które nie działają we wszystkich przypadkach.
Oprócz zaoszczędzonego czasu w porównaniu z testowaniem ręcznym, obciążenie psychiczne jest znacznie mniejsze, co ułatwia skupienie się na problemie bez obawy o przypadkowe wprowadzenie regresji. Dzięki temu możesz pracować szybciej i dłużej bez wypalenia. Moim zdaniem same korzyści dla zdrowia psychicznego są warte kosztu jednostkowego testowania złożonego kodu, nawet jeśli nie zaoszczędziłoby to czasu.
źródło