Testing, testing, 1, 2, 3.
Przypomniałem sobie, co też chciałem napisać przed tygodniem. Miałem ten temat poruszyć już kilka miesięcy temu, ale obecna robota dostarczyła mi kolejnych przykładów, więc można powiedzieć, że rzecz sama za mną łazi i domaga się opisania.
Na potrzeby tego wpisu zdefiniuję system produkcyjny jako coś, co albo (a) musi działać cały czas, bo jak nie działa, to wtedy szef się na nas drze, albo (b) musi działać cały czas, bo jak nie działa cały czas, to później spędzamy dużo czasu na przywracaniu systemu do takiego stanu, jak gdyby przerwy w działaniu nie było (albo (c) -- obie te sytuacje na raz).
Niezależnie od tego jak dobrze zaprojektujemy swój system i jak czysty będzie nasz kod, niestety nigdy nie będziemy się w stanie uwolnić od pewnego przykrego skutku ubocznego źródła naszej kreatywności -- pomyłek. Jakkolwiek mózg ludzki jest zdolny do różnych cudownych rzeczy, to ceną za to jest brak precyzji. Prawidłowo działający komputer się nie myli -- robi dokładnie to, co ma w programie. A człowiek może trafić w klawisze w złej kolejności, zrobić jakąś dziwną literówkę, czy wręcz myśleć o jednej rzeczy, a napisać drugą. A te wredne buczące i piszczące maszyny nigdy nie przymykają oczu na nasze niedoskonałości. Ja przed commitowaniem zmian do systemów, których nie powinienem popsuć, staram się przejrzeć diffa i wyłapać różne dziwne błędy, ale skoro za pierwszym razem uznałem kod za poprawny, to nie mam żadnej gwarancji, że patrząc na niego drugi raz dostrzegę błąd. Co jak co, ale w tym momencie muszę przyznać, że pair programming wcale nie jest tak głupi, jak mogłoby się zdawać.
No dobra, skoro już wiemy, że robimy dziwne pomyłki, to co dalej? W przypadku jakiśtam programików możemy sobie pozwolić na testowanie na żywym systemie. W przypadku systemów produkcyjnych (definicja jak powyżej) też możemy sobie pozwolić na testowanie na żywym systemie, ale musimy być przygotowani na konsekwencje (czyli że im dłużej zajmie nam wykrycie i naprawienie błędu, tym głośniej szef będzie na nas wrzeszczał i tym dłużej będziemy nad tym siedzieli i przywracali wszystko do porządku). Generalnie rozwiązanie jest jedno -- należy testować zmiany przed ich wprowadzeniem.
Różnymi unit testami, test driven development i takimi tam się zajmował nie będę, bo się najzwyczajnie w świecie na tym wszystkim nie znam, mogę co najwyżej opisać własne wnioski z własnych doświadczeń. Po pierwsze, zanim zaczniemy pisać cokolwiek, warto się zastanowić, czy to, co robimy, jest systemem produkcyjnym (definicja jak powyżej :)? Jest? No to pisząc pierwsze linijki kodu musimy mieć kilka rzeczy na uwadze. Najważniejsza -- kodujmy tak, żeby jakimś prostym przełącznikiem być w stanie przełączyć program w tryb debugowania, czyli żeby o wynikach dokonanych operacji tylko informował (nas, czy też jakiś program testując), a nie utrwalał je w systemie. Możemy być pewni, że prędzej czy później będziemy chcieli sami poprzeglądać wyniki generowane po jakiś wprowadzonych przez nas zmianach, zanim owe wyniki cokolwiek nadpiszą/popsują.
Tutaj zwracam uwagę na błąd, który popełniłem dwa razy, ale którego raczej już nie popełnię -- jeśli w celu przetestowania jakiejś swojej zmiany, ręcznie wpisujesz testowe dane do systemu, to znaczy, że właśnie popełniasz dwa błędy. Pierwszy, to wpisywanie danych do owego działającego systemu. Nawet jeśli wiesz jak to zrobić tak, żeby przeprowadzając swój mały test nic nie popsuć, to (a) i tak ryzykujesz, że o czymś zapomniałeś i (b) masz sporą szansę, że ubezpieczająć tyłek tracisz czas, bądź redukujesz dokładność przeprowadzonego testu. Już na poziomie projektowania (jakiegokolwiek projektowania, choćby tylko w głowie) systemu powinieneś był wszystko tak przewidzieć, żeby mieć gdzieś w pełni funkcjonalną kopię systemu (kopia bazy danych, działający url do roboczej wersji kodu, etc.). Zapewne z biegiem czasu każdy w tej kwestii może wyrobić sobie własny warsztat (konwencja nazewnictwa, jakiś zbiór skryptów pozwalający automatycznie i bezbłędnie synchronizować system podstawowy i roboczy, etc.), więc jakiś dokładniejszych przykładów podawał nie będę, bo to już każdy musi zdać się na własną ocenę sytuacji.
Drugim błędem jest owo ręczne wpisywanie testowych danych. Nawet w przypadku niezbyt skomplikowanego systemu, jeśli podliczysz sobie ile czasu spędziłeś powiedzmy na ręcznym wymyślaniu i wpisywaniu jakiś komend sql, to w sumie rzecz biorąc sporo czasu zaoszczędziłbyś (a i system byłby znacznie lepiej przetestowany), gdybyś za każdym razem, gdy potrzebujesz testowych danych, pisał sobie prosty skrypt owe dane generujący. Tutaj wręcz bajecznie przydaje się znajomość języków pokroju pythona, gdzie siadasz, piszesz i za chwilę masz kawałek działającego kodu.
Idiotycznie mi ten tekst wyszedł jeśli chodzi o konstrukcję -- rozwinięcia tematu kulawe i brak pointy, ale przez ostatnie trzy dni się obijałem, a jutro mam deadline, także wybaczcie, że go nie poprawiam. I pewnie jeszcze o czymś zapomniałem, acz najwyżej dopiszę w komentarzach.
Tak nawiasem mówiąc, to ciekawym jaka będzie reakcja mojego pracodawcy, jak mu powiem, że jego gonienie mnie terminami zaskutkowało tym, że część systemu jest napisana w pythonie (fakt, że nie jest tego dużo, raptem kilkanaście kilobajtów kodu, ale jak to z pythonem bywa, bardzo 'treściwego' kodu :).

12 IX 2005 o 00:14:01
A w czym "normalnie" musisz pisać kod?
12 IX 2005 o 00:27:10
Nieokreślone. Szef zapewne oczekuje C-pochodnych bądź ewentualnie pehapa (teoretycznie można w nim normalne skrypty niewuwowe pisać).
12 IX 2005 o 16:43:25
Nie tylko teoretycznie ;)
Kiedyś napisłem program w PHP działający na GTK do obsługi kadr/płac :)
12 IX 2005 o 17:47:53
To nie macie środowiska testowego?
A błędnie wprowadzone dane to świetny przypadek testowy przecież ;-)
12 IX 2005 o 19:42:50
Jak se nie zrobiłem środowiska testowego, to se nie mam :)
A sam system jest do użytku wewnętrznego, więc testowanie na okoliczność 'co też user może wymyślić, czego myśmy nie przewidzieli' byłoby stratą czasu :)