W ostatnich latach napotkałem ten problem kilka razy, pisząc kod obsługi wątków dla kilku projektów. Podaję spóźnioną odpowiedź, ponieważ większość innych odpowiedzi, podając alternatywy, w rzeczywistości nie odpowiadają na pytanie dotyczące testowania. Moja odpowiedź jest skierowana do przypadków, w których nie ma alternatywy dla kodu wielowątkowego; Zajmuję się projektowaniem kodu pod kątem kompletności, ale także omawiam testy jednostkowe.
Pisanie testowanego wielowątkowego kodu
Pierwszą rzeczą do zrobienia jest oddzielenie kodu obsługi wątku produkcyjnego od całego kodu, który faktycznie przetwarza dane. W ten sposób przetwarzanie danych można przetestować jako kod pojedynczo wątkowy, a jedyną rzeczą, którą robi kod wielowątkowy, jest koordynacja wątków.
Drugą rzeczą do zapamiętania jest to, że błędy w kodzie wielowątkowym są probabilistyczne; błędy, które objawiają się najrzadziej, to błędy, które przekradną się do produkcji, będą trudne do odtworzenia nawet podczas produkcji, a zatem spowodują największe problemy. Z tego powodu standardowe podejście do kodowania polegające na szybkim pisaniu kodu, a następnie debugowaniu go, aż zadziała, jest złym pomysłem na kod wielowątkowy; spowoduje to kod, w którym łatwe błędy zostaną naprawione, a niebezpieczne błędy będą nadal występować.
Zamiast tego, pisząc kod wielowątkowy, musisz napisać kod z takim nastawieniem, że unikniesz pisania błędów. Jeśli poprawnie usunąłeś kod przetwarzania danych, kod obsługi wątku powinien być wystarczająco mały - najlepiej kilka wierszy, w najgorszym przypadku kilkadziesiąt wierszy - abyś miał szansę napisać go bez pisania błędu, a na pewno bez pisania wielu błędów , jeśli rozumiesz wątki, nie spiesz się i zachowaj ostrożność.
Pisanie testów jednostkowych dla kodu wielowątkowego
Po napisaniu kodu wielowątkowego tak ostrożnie, jak to możliwe, nadal warto pisać testy dla tego kodu. Głównym celem testów jest nie tyle testowanie błędów związanych z wyścigiem w zależności od czasu - niemożliwe jest powtarzalne testowanie takich warunków wyścigu - ale raczej sprawdzenie, czy twoja strategia blokowania zapobiegająca takim błędom pozwala na interakcję wielu wątków zgodnie z przeznaczeniem .
Aby poprawnie przetestować prawidłowe działanie blokujące, test musi rozpocząć wiele wątków. Aby test był powtarzalny, chcemy, aby interakcje między wątkami zachodziły w przewidywalnej kolejności. Nie chcemy zewnętrznie synchronizować wątków w teście, ponieważ to maskuje błędy, które mogą się zdarzyć podczas produkcji, w których wątki nie są synchronizowane zewnętrznie. Pozostawia to użycie opóźnień czasowych do synchronizacji wątków, co jest techniką, którą z powodzeniem stosowałem za każdym razem, gdy musiałem pisać testy wielowątkowego kodu.
Jeśli opóźnienia są zbyt krótkie, test staje się kruchy, ponieważ niewielkie różnice czasowe - powiedzmy między różnymi maszynami, na których testy mogą być uruchomione - mogą spowodować, że czas się wyłączy i test się nie powiedzie. To, co zwykle robiłem, to zaczynać od opóźnień, które powodują niepowodzenia testów, zwiększać opóźnienia, aby test przebiegał niezawodnie na mojej maszynie programistycznej, a następnie podwajać opóźnienia poza tym, aby test miał duże szanse na przekazanie na inne maszyny. Oznacza to, że test zajmie makroskopowo dużo czasu, choć z mojego doświadczenia wynika, że staranne zaprojektowanie testu może ograniczyć ten czas do nie więcej niż kilkunastu sekund. Ponieważ nie powinieneś mieć zbyt wielu miejsc wymagających kodu koordynacji wątków w swojej aplikacji, powinno to być dopuszczalne dla twojego zestawu testów.
Na koniec śledź liczbę błędów wykrytych w teście. Jeśli Twój test obejmuje 80% pokrycia kodu, można oczekiwać, że wykryje około 80% twoich błędów. Jeśli Twój test jest dobrze zaprojektowany, ale nie wykrył żadnych błędów, istnieje uzasadniona szansa, że nie masz dodatkowych błędów, które pojawią się tylko podczas produkcji. Jeśli test wykryje jeden lub dwa błędy, nadal możesz mieć szczęście. Poza tym, możesz rozważyć dokładną analizę lub nawet całkowite przepisanie kodu obsługi wątków, ponieważ jest prawdopodobne, że kod nadal zawiera ukryte błędy, które będą bardzo trudne do znalezienia, dopóki kod nie będzie produkowany, i bardzo trudne do naprawienia.