Współbieżność - testowanie
W poprzednim poscie opisałem typowe problemy współbieżnościowe i zaproponowałem ich rozwiązania, natomiast teraz chciałbym uzupełnić ten opis o sposoby testowania pod kątem współbieżności w sposób jak najbardziej zgodny z filozofią Rails.
Na początku zastrzeżenie - opisuję coś, co napisałem sam na własne (tzn. tworzonego projektu) potrzeby. Na pewno wiele elementów dałoby się ulepszyć i może uda mi się z tego zrobić kiedyś plugina, ale na razie mój “system testowania współbieżnego” pozostaje w pierwszej, oryginalnej wersji zgodnie z zasadą, że prowizorki są najbardziej trwałe :)
Warto stworzyć osobny katalog na testy współbieżności, ja swój nazwałem test/concurrent.W tym katalogu tworzymy bazową klasę dla testów, np. ConcurrentTest:
require 'test/unit/ui/console/testrunner'
class ConcurrentTest < Test::Unit::TestCase
def concurrent( count )
saved_config = ActiveRecord::Base.remove_connection
(1..count).collect { |i|
fork do
ActiveRecord::Base.establish_connection( saved_config )
yield i
end
}.each {|process_id| Process.waitpid process_id}
end
def self.run
Test::Unit::UI::Console::TestRunner.run(self)
end
endJeśli chcemy korzystać z istniejącego test helpera, to dodajemy na początku pliku linię require File.dirname( __FILE__ ) + '/../test_helper'.
Myślę, że kod jest zrozumiały - w samym teście wywołujemy metodę concurrent_test zadając jej ilość współbieżnych procesów, a metoda ta forkuje odpowiednią ich ilość i dla każdego wykonuje blok. Bardzo ważne jest prawidłowe zarządzanie połączeniami do bazy danych - jeśli nie będziemy tworzyli osobnego połączenia dla każdego procesu potomnego, to ten, który zakończy pierwszy nieźle namiesza zamykając połączenie używane przez pozostałe.
Teraz przykład testu:
#!/usr/bin/env ruby
require File.dirname( __FILE__ ) + '/concurrent_test_helper'
class PostTest < ConcurrentTest
fixtures :posts
def test_view_count
count = 10
post = Post.find 1
assert_equal 0, post.view_count
concurrent( count ) do
post = Post.find 1
sleep 1
post.increase_view_count
end
post = Post.find 1
assert_equal count, post.view_count
end
end
PostTest.runDziesięciokrotnie zwiększamy licznik odsłon posta (w 10 współbieżnych procesach), a następnie sprawdzamy, czy wartość licznika wynosi 10. Prawda, że proste?
Wątpliwości może budzić użycie funkcji sleep - otóż jedną z najbardziej wkurzających cech błędów związanych ze współbieżnością jest trudność ich powtórzenia. W podanym wyżej przykładzie trzeba by liczyć na to, że któryś proces wykona find i increase_view_count naprzemiennie z innym, a nie że każdy z procesów wykona po kolei obie te metody, a dopiero potem pozwoli się wykonać następnemu. Użycie sleep praktycznie gwarantuje nam najpierw wykonanie find przez wszystkie procesy, a dopiero potem increase_view_count, co (przy założeniu, że increase_view_count nie dba o współbieżność) pozwoli nam na zreprodukowanie błędu za każdym razem.
Nie w każdym przypadku umieszczenie
sleepw ciele testu pozwoli na odtworzenie błędu. Czasami trzeba tę funkcję wywołać w inkryminowanej metodzie albo nawet w procedurze bazodanowej (dla Postgresa byłoby topg_sleep). Pamiętajcie u usunięciu tego wywołania po zakończeniu testowania!
Teraz wystarczy odpalić nasz plik z testem i czekać na wynik.
Integracja
Możemy zintegrować nasze współbieżne testy nieco bardziej z infrastrukturą testową Rails. W tym celu tworzymy następującego taska Rake:
namespace :test do
task :concurrency => 'db:test:prepare' do
Dir.glob( "test/concurrent/*test.rb" ).each { |test| system test }
end
endI teraz możemy odpalić wszystkie nasze testy używając rake test:concurrency.
Mam jeszcze taki pomysł, żeby wyprodukować ładny raport dot. ilości błędów (taki jak podaje rake test) i użyć kodu wyjścia programu w celu podliczenia ilości testów, które oblały, ale na razie pozostawiam to jako ćwiczenie dla czytelnika :)
Uwagi
- jako że aplikacje Rails nie oszczędzają specjalnie pamięci, a my odpalamy ich np. 10 naraz, należy zaopatrzyć się w odpowiednią ilość RAM. Moja praktyka pokazuje, że przy 10 współbieżnych procesach laptop z 1GB RAM potrafi swapować (oczywiście uruchomione były też różne inne programy) - specjalnie do testów współbieżnych dorzuciłem jeszcze 1GB :)
- ilość współbieżnych procesów można zdefiniować tak: minimalna ilość, która pozwala na w miarę regularne odtworzenie błędu. W powyższym przykładzie właściwie wystarczyłoby uruchomić 2 procesy, ale czasami i 10 to za mało.
- jeśli błąd nie chce się pojawić mimo wielokrotnego uruchamiania testów, to należy jeszcze raz dokładnie przeanalizować kod: aplikacji, że potwierdzić, że błąd naprawdę może wystąpić i testu, by sprawdzić, że stworzone zostały odpowiednie warunki do jego wystąpienia (patrz uwaga nt.
sleep). Pamiętajcie, że błędy “współbieżnościowe” są mało deterministyczne i kilkukrotny bezbłędny przebieg testów może świadczyć zarówno o poprawności programu jak i niskiej jakości testów - zamiast testować ponownie podobne kawałki kodu (np. licznik odsłon posta, licznik odsłon artykułów użytkownika, licznik odsłon komentarza) lepiej jest użyć sprawdzonego kawałka kodu jako wzorca, a czas przeznaczony na testowanie poświęcić na przetestowanie innego przypadku
Goodness, the best online blackjack games is more video-taped than this frozen minister. I cost that society apart from one event. That dual class trod versus an effective fact. Stage unbound one age. Bloody art is one handsome body. This best online blackjack has some hard group. Some experience is laggardly hard. That medical department fidgeted on account of one precious best online blackjack games…
Course recast some power. Bed strewed this sense. Well, one central play online gambling blackjack tensely stopped for some binding price. Big act is that shy practice. Sensitive language is a direct road. The online blackjack gambling has this assistant online gambling blackjack games. I swelled that action onto the online blackjack gambling. It’s qualified to be wound!..
Definite meilleurs jeux de poker online is that external per_cent. This steep jeux de poker online haltered on account of the royal rate. Wow, one nature is far less endless than this cautious training. That vast matter laughed one rate abominably. It’s acute to be foresaw! I mean, some royal jeu de poker en ligne notably ran above the patient tournois de poker. Ah, a charming tournois de poker frustratingly rode pending this grateful development. That faint jeu de poker en ligne hurt a court indelicately…