Pytanie Czy można udawać / fałszować, aby brakujący zamek spowodował niepowodzenie testu?


Piszę cienkie opakowanie wokół słownika, które ma być wątkowane. Jako takie, niektóre zamki są wymagane, a większość logiki polega na zapewnieniu, że rzeczy są odpowiednio zamknięte i dostępne w sposób bezpieczny dla wątków.

Teraz próbuję to przetestować. Jedną z wielkich rzeczy, które chciałbym przetestować w jednostce, jest zachowanie blokady, aby upewnić się, że jest prawidłowe. Jednak nigdy nie widziałem, aby to zrobić nigdzie, więc nie jestem pewien, jak to zrobić. Ponadto, wiem, że mógłbym użyć kilku wątków do rzucania rzeczy na ścianę, ale przy takim typie testu nie ma gwarancji, że zawiedzie, kiedy się pomyli. To zależy od systemu operacyjnego zdefiniowanego w harmonogramie wątków.

Jakie są sposoby, aby upewnić się, że moje zachowanie związane z blokowaniem jest prawidłowe przy testach jednostkowych?


12
2018-02-26 18:57


pochodzenie


+1 w koncepcji pytania; ciekawy dlaczego nie używasz a ConcurrentDictionary w tym konkretnym przypadku. - Tim Medora
@TimMedora przenośna biblioteka klas skierowana do Windows Phone :( - Earlz
Zrozumiany. Z mojego doświadczenia wynika, że ​​bombardowanie przypuszczalnej klasy bezpiecznej dla wątków tysiącami operacji (wątek krzyżowy, zróżnicowana sekwencja) szybko ujawni problemy, ale oczywiście to nie jest deterministyczne. Możesz dodać kilka wewnętrznych haków testowych, aby pomóc wymusić odpowiedni scenariusz. - Tim Medora
@TimMedora można to zrobić deterministycznie w teście: iteracja w jednym wątku pomiędzy sygnalizacją innego wątku w celu zmodyfikowania kolekcji, a następnie sprawdzenie, czy modyfikacja nie nastąpiła i nie było żadnych wyjątków. - Kimi
@ Kimi - Myślę, że byłby to ważny test w jednym scenariuszu (iteracja), ponieważ możesz sygnalizować wątek # 2 przed iterowaniem następnego elementu. Nie wiem, czy ten sam paradygmat byłby deterministyczny we wszystkich scenariuszach (takich jak dwie równoczesne operacje Add ()). - Tim Medora


Odpowiedzi:


Nie sądzę Dictionary sam możesz uzyskać wiarygodne testy - Twoim celem jest wykonanie 2 wywołań równolegle, co nie stanie się niezawodnie.

Możesz spróbować mieć niestandardowy słownik (np. Wywodzić go ze zwykłego) i dodawać wywołania zwrotne dla wszystkich metod Get / Add. Niż będziesz w stanie opóźnić połączenia w 2 wątkach w razie potrzeby ... Będziesz potrzebować oddzielnej synchronizacji między 2 wątkami, aby twój kod testowy działał tak, jak chcesz, a nie martwy przez cały czas.


1
2018-02-26 19:17



W końcu to zrobiłem. Omówiłem szczegółowo, jak to zrobiłem poniżej - Earlz


Blokowanie to tylko szczegół implementacji. Powinieneś wykpić sam stan wyścigu i sprawdzić, czy dane nie utraciły integralności w teście.

Byłoby to korzystne, jeśli zdecydujesz się zmienić implementację słownika i użyć innych prymitywów synchronizacji.

Testowanie blokowania pokaże, że wywołujesz instrukcję blokującą, gdy jej oczekujesz. Podczas testowania z wyśrubowanym stanem wyścigu można odkryć miejsca, których nie spodziewałeś się zsynchronizować.


3
2018-02-26 19:03



Zasadniczo polega to na łamaniu słownika, jeśli jest on dostępny jednocześnie - Earlz
Nie powinno pozostać w prawidłowym stanie, co zostanie udowodnione w teście. - Kimi
Cóż, to polega na szczegółach implementacji Słownika. Właściwie muszę przetestować, że słownik nigdy nie jest dostępny w sposób równoczesny, ponieważ cała sprawa w zasadzie nie jest bezpieczna dla moich celów - Earlz
Słownik może i powinien być czytelny jednocześnie, tylko odczyt / zapis powinien być zsynchronizowany. Standardowy słownik ujawni równoczesne problemy z dostępem do odczytu / zapisu, których próbujesz uniknąć, które będą widoczne w teście. - Kimi


Zasadniczo skończyłem robić to, co powiedział @Alexei. Mówiąc dokładniej, właśnie to zrobiłem:

  1. Zrobić PausingDictionary który po prostu ma wywołanie zwrotne dla każdej metody, ale poza tym po prostu przechodzi do zwykłego słownika
  2. Streszczenie mojego kodu (przy użyciu DI, itp.), Dzięki czemu mogę używać PausingDictionary zamiast zwykłego Dictionary podczas testowania
  3. Dodano dwa ConcurrentDictionaries w moim teście jednostki. Jeden o nazwie "Accessed" i jeden o nazwie "GoAhead". Kluczem do tego jest połączenie "action"+Thread.GetHashCode().ToString() (gdzie działanie jest inne dla każdego wywołania zwrotnego)
  4. Zainicjowałem wszystko na false i dodałem kilka metod rozszerzenia, aby nieco ułatwić sobie pracę
  5. Skonfiguruj wywołania zwrotne słownika, aby ustawić Dostęp dla wątku na true, ale wtedy będzie czekać w oddzwanianiu, dopóki GoAhead nie będzie prawdą
  6. Uruchomiono dwa wątki z testu urządzenia. Jeden wątek miałby dostęp do słownika, ale dlatego GoAhead jest fałszywe dla tego wątku, to siedziałoby tam. Drugi wątek będzie również próbował uzyskać dostęp do słownika
  7. Miałem takie twierdzenie Accessed dla tego wątku jest fałsz, ponieważ mój kod powinien go zablokować.

Jest w tym trochę więcej. Musiałbym też wyśmiać ILista, ale nie sądzę, żebym to zrobił. Te testy jednostkowe, choć cenne, zdecydowanie nie są najłatwiejsze do napisania na świecie. Oprócz kodu instalacyjnego i implementacji fałszywego interfejsu i takich, każdy test kończy się około 25 liniami niepoprawnego kodu. Blokowanie jest trudne. Udowodnienie skuteczności blokady jest jeszcze trudniejsze. Co zadziwiające, ten rodzaj wzorca pozwala na przetestowanie prawie każdego scenariusza. Ale jest bardzo gadatliwy i nie pozwala na ładne testy

Pomimo tego, że trudno jest napisać testy, działa to doskonale. Kiedy usuwam blokadę stale się nie udaje i po dodaniu blokady, konsekwentnie przechodzi.

Edytować:

Myślę, że ta metoda "kontrolowania przeplotu" wątków umożliwiłaby również przetestowanie bezpieczeństwa wątków, biorąc pod uwagę, że napisałeś test dla każdego możliwego przeplotu. Z pewnym kodem byłoby to niemożliwe, ale chcę tylko powiedzieć, że w żaden sposób nie ogranicza się do kodu blokującego. Można zrobić to w taki sam sposób, aby konsekwentnie powielać awarię wątku, taką jak foo.Contains(x) i wtedy var tmp=foo[x]


2
2018-02-27 15:29



+1: bardzo ładne napisy! - Alexei Levenkov