Czytałem o tym snafu: Błąd programowania kosztuje Citigroup 7 milionów dolarów po legalnych transakcjach pomylonych z danymi testowymi przez 15 lat .
Kiedy system został wprowadzony w połowie lat 90., kod programu odfiltrował wszelkie transakcje, którym nadano trzycyfrowe kody od 089 do 100 i wykorzystał te prefiksy do celów testowych.
Ale w 1998 r. Firma zaczęła używać alfanumerycznych kodów branżowych, rozszerzając swoją działalność. Wśród nich były kody 10B, 10C i tak dalej, które system traktował jako znajdujące się w wykluczonym zakresie, a zatem ich transakcje zostały usunięte z raportów wysłanych do SEC.
(Myślę, że to pokazuje, że użycie nieprecyzyjnego wskaźnika danych jest ... nieoptymalne. Lepiej byłoby wypełnić i użyć Branch.IsLive
właściwości semantycznie jawnej .)
Poza tym moją pierwszą reakcją było „Testy jednostkowe by tu pomogły”… ale czy tak?
Niedawno przeczytałem, dlaczego większość testów jednostkowych jest marnotrawstwem z zainteresowaniem, więc moje pytanie brzmi: jak wyglądałyby testy jednostkowe, które zawiodłyby po wprowadzeniu alfanumerycznych kodów branżowych?
źródło
Odpowiedzi:
Czy naprawdę pytasz: „czy pomogłyby tutaj testy jednostkowe?”, Czy pytasz: „czy pomógłby w tym jakikolwiek test?”.
Najbardziej oczywistą formą testowania, która by pomogła, jest wstępne stwierdzenie w samym kodzie, że identyfikator gałęzi składa się tylko z cyfr (zakładając, że jest to założenie, na którym koder opierał się podczas pisania kodu).
Mogło to nie powieść się w jakimś teście integracji, a gdy tylko zostaną wprowadzone nowe identyfikatory gałęzi alfanumerycznej, asercja wysadzi w powietrze. Ale to nie jest test jednostkowy.
Alternatywnie może istnieć test integracji procedury, która generuje raport SEC. Ten test zapewnia, że każdy rzeczywisty identyfikator oddziału zgłasza swoje transakcje (i dlatego wymaga rzeczywistych danych wejściowych, listy wszystkich używanych identyfikatorów oddziału). Więc to też nie jest test jednostkowy.
Nie widzę żadnej definicji ani dokumentacji zaangażowanych interfejsów, ale może się zdarzyć, że testy jednostkowe prawdopodobnie nie wykryły błędu, ponieważ jednostka nie była wadliwa . Jeśli jednostka może założyć, że identyfikatory gałęzi składają się tylko z cyfr, a programiści nigdy nie zdecydowali, co powinien zrobić kod, gdyby tego nie zrobił, to nie powinninapisz test jednostkowy, aby wymusić określone zachowanie w przypadku niecyfrowych identyfikatorów, ponieważ test odrzuciłby hipotetyczną poprawną implementację jednostki, która poprawnie obsługiwała alfanumeryczne identyfikatory gałęzi, i zwykle nie chcesz pisać testu jednostkowego, który uniemożliwia prawidłowy przyszłe wdrożenia i rozszerzenia. A może jeden dokument napisany 40 lat temu domyślnie zdefiniowany (poprzez pewien zakres leksykograficzny w surowym EBCDIC, zamiast bardziej przyjaznej dla człowieka reguły zestawiania), że 10B jest identyfikatorem testu, ponieważ w rzeczywistości mieści się w przedziale od 089 do 100. Ale potem 15 lat temu ktoś postanowił użyć go jako prawdziwego identyfikatora, więc „usterka” nie leży w jednostce, która poprawnie implementuje oryginalną definicję: leży w procesie, który nie zauważył, że 10B jest zdefiniowany jako identyfikator testu i dlatego nie powinien być przypisany do gałęzi. To samo stanie się w ASCII, jeśli zdefiniujesz 089 - 100 jako zakres testowy, a następnie wprowadzisz identyfikator 10 $ lub 1.0. Po prostu zdarza się, że w EBCDIC cyfry pojawiają się po literach.
Jeden test jednostkowy (lub prawdopodobnie test funkcjonalny), który jest możliwymógł uratować dzień, jest testem jednostki, która generuje lub sprawdza nowe identyfikatory gałęzi. Test ten potwierdziłby, że identyfikatory muszą zawierać tylko cyfry i zostałby napisany, aby umożliwić użytkownikom identyfikatorów gałęzi przyjęcie tego samego. A może jest gdzieś jednostka, która importuje prawdziwe identyfikatory gałęzi, ale nigdy nie widzi tych testowych, i która mogłaby być testowana jednostkowo, aby upewnić się, że odrzuca wszystkie identyfikatory testowe (jeśli identyfikatory to tylko trzy znaki, możemy je wszystkie policzyć i porównać zachowanie walidator do filtru testowego, aby upewnić się, że pasują, co dotyczy zwykłego sprzeciwu wobec testów punktowych). Gdy ktoś zmienił reguły, test jednostkowy nie powiódłby się, ponieważ jest sprzeczny z nowo wymaganym zachowaniem.
Ponieważ test został przeprowadzony z dobrego powodu, punkt, w którym należy go usunąć ze względu na zmienione wymagania biznesowe, staje się okazją dla kogoś, kto dostanie to zadanie, „znajdź każde miejsce w kodzie, które zależy od zachowania, które chcemy zmiana". Oczywiście jest to trudne, a zatem niewiarygodne, więc w żadnym wypadku nie gwarantuje uratowania dnia. Ale jeśli uchwycisz swoje założenia w testach jednostek, których zakładasz właściwości, to dałeś sobie szansę, więc wysiłek nie zostanie całkowicie zmarnowany.
Zgadzam się oczywiście, że gdyby jednostka nie została zdefiniowana z „zabawnym” wejściem, nie byłoby nic do przetestowania. Fiddly podziały przestrzeni nazw mogą być trudne do prawidłowego przetestowania, ponieważ trudność nie polega na implementacji twojej śmiesznej definicji, polega na upewnieniu się, że wszyscy rozumieją i szanują twoją zabawną definicję. To nie jest lokalna właściwość jednej jednostki kodu. Ponadto zmiana niektórych typów danych z „ciągu cyfr” na „ciąg znaków alfanumerycznych” przypomina tworzenie Unicode w programie opartym na ASCII: nie będzie to łatwe, jeśli kod będzie silnie sprzężony z oryginalną definicją i kiedy typ danych ma fundamentalne znaczenie dla tego, co robi program, a następnie często jest silnie sprzężony.
Jeśli testy jednostkowe czasami kończą się niepowodzeniem (na przykład podczas refaktoryzacji), a robiąc to, dostarczają przydatnych informacji (na przykład Twoja zmiana jest błędna), to wysiłek nie został zmarnowany. To, czego nie robią, to sprawdzenie, czy system działa. Więc jeśli piszesz testy jednostkowe zamiast testów funkcjonalnych i integracyjnych, być może wykorzystujesz swój czas nieoptymalnie.
źródło
Testy jednostkowe mogły wykryć, że kody gałęzi 10B i 10C zostały nieprawidłowo sklasyfikowane jako „gałęzie testujące”, ale wydaje mi się mało prawdopodobne, aby testy dla tej klasyfikacji gałęzi były wystarczająco obszerne, aby wykryć ten błąd.
Z drugiej strony kontrole na miejscu wygenerowanych raportów mogły ujawnić, że rozgałęzionych 10B i 10C konsekwentnie brakowało w raportach znacznie wcześniej niż 15 lat, kiedy błąd mógł pozostać obecny.
Wreszcie jest to dobra ilustracja, dlaczego mieszanie danych testowych z rzeczywistymi danymi produkcyjnymi w jednej bazie danych jest złym pomysłem. Gdyby korzystali z osobnej bazy danych zawierającej dane testowe, nie byłoby potrzeby odfiltrowywania ich z oficjalnych raportów i niemożliwe byłoby odfiltrowanie zbyt dużej ilości danych.
źródło
Oprogramowanie musiało obsługiwać pewne reguły biznesowe. Gdyby były testy jednostkowe, testy jednostkowe sprawdziłyby, czy oprogramowanie poprawnie obsługuje reguły biznesowe.
Reguły biznesowe uległy zmianie.
Najwyraźniej nikt nie zdawał sobie sprawy, że reguły biznesowe uległy zmianie i nikt nie zmienił oprogramowania, aby zastosować nowe reguły biznesowe. Gdyby były testy jednostkowe, te testy jednostkowe musiałyby zostać zmienione, ale nikt by tego nie zrobił, ponieważ nikt nie zdawał sobie sprawy, że zmieniły się reguły biznesowe.
Więc nie, testy jednostkowe by tego nie złapały.
Wyjątkiem byłoby, gdyby testy jednostkowe i oprogramowanie zostały utworzone przez niezależne zespoły, a zespół wykonujący testy jednostkowe zmienił testy, aby zastosować nowe reguły biznesowe. Wówczas testy jednostkowe zakończyłyby się niepowodzeniem, co, miejmy nadzieję, spowodowałoby zmianę oprogramowania.
Oczywiście w tym samym przypadku, gdyby zmieniono tylko oprogramowanie, a nie testy jednostkowe, testy jednostkowe również się nie udały. Ilekroć test jednostkowy kończy się niepowodzeniem, nie oznacza to, że oprogramowanie jest złe, oznacza to, że oprogramowanie lub test jednostkowy (czasami oba) są nieprawidłowe.
źródło
Nie. To jeden z dużych problemów z testowaniem jednostkowym: wprawiają cię w fałszywe poczucie bezpieczeństwa.
Jeśli wszystkie testy przejdą pomyślnie, nie oznacza to, że system działa poprawnie; oznacza to, że wszystkie twoje testy przeszły pomyślnie . Oznacza to, że części twojego projektu, o których świadomie myślałeś i napisałeś testy, działają tak, jak ci się wydaje, co naprawdę nie jest aż tak wielkim problemem: na te rzeczy zwracałeś szczególną uwagę , więc jest bardzo prawdopodobne, że i tak masz rację! Ale nie robi nic, aby złapać przypadki, o których nigdy nie pomyślałeś, takie jak ten, ponieważ nigdy nie pomyślałeś o napisaniu dla nich testu. (Gdyby tak było, byłbyś świadomy, że to oznaczało konieczność zmiany kodu i musiałbyś je zmienić).
źródło
Nie, niekoniecznie.
Pierwotnym wymogiem było użycie numerycznych kodów branżowych, dlatego zostałby przeprowadzony test jednostkowy dla komponentu, który akceptuje różne kody i odrzuca jakikolwiek 10B. System zostałby uznany za działający (który był).
Następnie wymaganie zmieniłoby się, a kody zaktualizowane, ale oznaczałoby to, że kod testu jednostkowego, który dostarczył złe dane (czyli teraz dobre dane) musiałby zostać zmodyfikowany.
Teraz zakładamy, że ludzie zarządzający systemem wiedzieliby, że tak jest, i zmieniliby test jednostkowy, aby obsługiwać nowe kody ... ale gdyby wiedzieli, że tak się dzieje, mogliby również zmienić kod, który obsługiwał te kody i tak kody… i tego nie zrobili. Test jednostkowy, który pierwotnie odrzucił kod 10B, z radością powiedziałby „wszystko jest w porządku” po uruchomieniu, gdybyś nie wiedział, jak zaktualizować ten test.
Testy jednostkowe są dobre do oryginalnego opracowania, ale nie do testowania systemu, zwłaszcza nie 15 lat po dawnym zapomnieniu o wymaganiach.
W takiej sytuacji potrzebują kompleksowego testu integracji. Taki, w którym możesz przekazać dane, które mają działać, i sprawdzić, czy to działa. Ktoś zauważyłby, że ich nowe dane wejściowe nie wygenerowały raportu, a następnie przeprowadziłby dalsze dochodzenie.
źródło
Testowanie typu (proces testowania niezmienników przy użyciu losowo generowanych prawidłowych danych, na przykład w bibliotece testowej Haskell QuickCheck i różnych portach / alternatywach zainspirowanych przez nią w innych językach) może wychwycić ten problem, testowanie jednostkowe prawie na pewno by się nie udało .
Wynika to z faktu, że kiedy zasady ważności kodów oddziałów zostały zaktualizowane, jest mało prawdopodobne, aby ktokolwiek pomyślał o przetestowaniu tych konkretnych zakresów, aby upewnić się, że działają one poprawnie.
Jeśli jednak testowanie typu było w użyciu, ktoś powinien w momencie wdrożenia oryginalnego systemu napisać parę właściwości, jedną dla sprawdzenia, czy określone kody dla gałęzi testowych zostały potraktowane jako dane testowe, a drugą dla sprawdzenia, czy nie ma innych kodów były ... kiedy definicja typu danych dla kodu oddziału została zaktualizowana (co byłoby wymagane w celu umożliwienia testowania, czy którakolwiek ze zmian kodu oddziału z cyfrowego na numeryczny działała), test ten rozpocząłby testowanie wartości w nowy zakres i najprawdopodobniej zidentyfikowałby usterkę.
Oczywiście QuickCheck został opracowany po raz pierwszy w 1999 roku, więc było już za późno, aby złapać ten problem.
źródło
Naprawdę wątpię, by testy jednostkowe miały wpływ na ten problem. To brzmi jak jedna z sytuacji tunelowych, ponieważ zmieniono funkcjonalność w celu obsługi nowych kodów oddziałów, ale nie zostało to przeprowadzone we wszystkich obszarach systemu.
Używamy testów jednostkowych, aby zaprojektować klasę. Ponowne uruchomienie testu jednostkowego jest wymagane tylko w przypadku zmiany projektu. Jeśli konkretna jednostka nie ulegnie zmianie, wówczas niezmienione testy jednostkowe zwrócą takie same wyniki jak poprzednio. Testy jednostkowe nie pokażą ci wpływu zmian na inne jednostki (jeśli nie, nie piszesz testów jednostkowych).
Możesz rozsądnie wykryć ten problem tylko poprzez:
Bardziej niepokojące jest brak wystarczających kompleksowych testów. Nie można polegać na testach jednostkowych jako teście TYLKO lub GŁÓWNY w przypadku zmian w systemie. Wygląda na to, że wymagało to tylko sporządzenia raportu o nowo obsługiwanych formatach kodu oddziału.
źródło
Zapewnienie wbudowane w środowisko wykonawcze mogło pomóc; na przykład:
bool isTestOnly(string branchCode) { ... }
Zobacz też:
źródło
Wyzwanie polega na tym, by szybko zawieść .
Nie mamy kodu ani nie mamy wielu przykładów prefiksów, które są lub nie są prefiksami gałęzi testowych zgodnie z kodem. Wszystko, co mamy, to:
Fakt, że kod dopuszcza liczby i ciągi, jest więcej niż trochę dziwny. Oczywiście 10B i 10C można uznać za liczby szesnastkowe, ale jeśli wszystkie prefiksy są traktowane jako liczby szesnastkowe, 10B i 10C nie mieszczą się w zakresie testowym i będą traktowane jako rzeczywiste rozgałęzienia.
Prawdopodobnie oznacza to, że prefiks jest przechowywany jako ciąg, ale w niektórych przypadkach jest traktowany jako liczba. Oto najprostszy kod, jaki mogę wymyślić, który replikuje to zachowanie (używając C # w celach ilustracyjnych):
W języku angielskim, jeśli ciąg jest liczbą i zawiera się między 89 a 100, jest to test. Jeśli to nie jest liczba, to jest test. W przeciwnym razie nie jest to test.
Jeśli kod jest zgodny z tym wzorcem, żaden test jednostkowy nie wychwyciłby tego w momencie wdrażania kodu. Oto kilka przykładowych testów jednostkowych:
Test jednostkowy pokazuje, że „10B” należy traktować jako gałąź testową. Użytkownik @ gnasher729 powyżej mówi, że reguły biznesowe uległy zmianie i właśnie to pokazuje ostatnie stwierdzenie powyżej. W pewnym momencie to twierdzenie powinno się przełączyć na
isFalse
, ale tak się nie stało. Testy jednostkowe uruchamiane są w fazie projektowania i kompilacji, ale potem nie są wykonywane.Jaka jest lekcja tutaj? Kod musi w jakiś sposób zasygnalizować, że otrzymał nieoczekiwany sygnał wejściowy. Oto alternatywny sposób napisania tego kodu, który podkreśla, że oczekuje on, że prefiks będzie liczbą:
Dla tych, którzy nie znają C #, zwracana wartość wskazuje, czy kod był w stanie przeanalizować prefiks z podanego ciągu. Jeśli zwracana wartość jest prawdą, kod wywołujący może użyć zmiennej isTest out, aby sprawdzić, czy prefiks gałęzi jest prefiksem testowym. Jeśli zwracana wartość to false, kod wywołujący powinien zgłosić, że podany prefiks nie jest oczekiwany, a zmienna isTest out nie ma znaczenia i należy ją zignorować.
Jeśli nie masz nic przeciwko wyjątkom, możesz to zrobić w zamian:
Ta alternatywa jest prostsza. W takim przypadku kod wywołujący musi wychwycić wyjątek. W obu przypadkach kod powinien mieć jakiś sposób zgłaszania dzwoniącemu, że nie spodziewał się strPrefiksu, którego nie można przekonwertować na liczbę całkowitą. W ten sposób kod szybko zawiedzie, a bank może szybko znaleźć problem bez drobnego zażenowania SEC.
źródło
Tak wiele odpowiedzi i nawet nie jeden cytat Dijkstry:
Dlatego to zależy. Jeśli kod został przetestowany poprawnie, najprawdopodobniej ten błąd nie istniałby.
źródło
Myślę, że tutaj test jednostkowy zapewniłby, że problem nigdy nie wystąpi.
Zastanów się, napisałeś
bool IsTestData(string branchCode)
funkcję.Pierwszy test jednostkowy, który napiszesz, powinien być pusty i pusty. Następnie dla łańcuchów o niepoprawnej długości, a następnie dla łańcuchów niecałkowitych.
Aby wszystkie testy zakończyły się pomyślnie, należy dodać funkcję sprawdzania parametrów do funkcji.
Nawet jeśli wtedy testujesz tylko dla „dobrych” danych 001 -> 999, nie myśląc o możliwości 10A, sprawdzanie parametrów zmusi cię do przepisania funkcji, gdy zaczniesz używać alfanumerycznych, aby uniknąć wyjątków, które wyrzuci
źródło
IsValidBranchCode
użyłaby jakiejś funkcji do wykonania tej kontroli? A ta funkcja prawdopodobnie zostałaby zmieniona bez potrzeby modyfikacjiIsTestData
? Więc jeśli testujesz tylko „dobre dane”, test nie pomógłby. Test przypadków skrajnych musiałby zawierać teraz aktualny kod oddziału (a nie tylko niektóre nadal niepoprawne), aby zacząć się nie udać.