Testowanie programów I: Podstawy

Bardzo rzadko zdarza się, że kod, który napisaliśmy działa za pierwszym uruchomieniem. Często testowanie programów i znajdowanie w nich błędów to żmudny i czasochłonny proces. Dziś pokażemy, jak sprawić, by trwał on jak najkrócej.

Zadanie

Mamy za zadanie napisać program, który sortuje podany ciąg liczb. Ponieważ rozwiązywanie tego problemu nie jest dzisiaj naszym celem, wyobraźmy sobie, że wpadliśmy na pomysł, by zaimplementować algorytm bąbelkowy i nasz kod wygląda teraz jakoś tak:

Jeśli niektóre z użytych poleceń są dla Ciebie nie jasne, koniecznie przeczytaj ten wpis.

Zanim wyślemy kod na sprawdzarkę, chcemy go przetestować. Pamiętaj o najważniejszej zasadzie przy rozwiązywaniu zadań:

Nigdy nie wysyłaj nieprzetestowanego kodu!

Jak to zrobić? Najprostszym wyjściem jest uruchomienie programu i wprowadzenie testu ręcznie. Sposób ten, o ile jest rzeczywiście prosty, o tyle w przypadku większego testu jest strasznie nieefektywny – wyobraź sobie chociażby sytuację, gdy testujesz program na ciągu zawierający 10 liczb i program zwrócił złą odpowiedź. Poprawiłeś jedną linijkę i chcesz sprawdzić, czy kod już jest poprawny. Musisz wpisywać cały ciąg od początku.

Testowanie z pliku

Istnieje dużo efektywniejszy sposób. Żeby nie wpisywać testów za każdym razem, zapiszmy go do pliku w folderze, w którym znajduje się kod. Następnie uruchomimy program z przekierowanym wejściem – z klawiatury do pliku. Nasz kod będzie myślał, że wczytuje dane z klawiatury a tak naprawdę będzie pobierał je z wcześniej przygotowanego pliku.

No dobra, ale jak to zrobić? Otwórz terminal/wiersz poleceń (ctrl+alt+T na Linuksie lub ‚cmd’ w polu Uruchom na Windowsie) i postępuj zgodnie z instrukcjami na filmiku:

OK, jeszcze raz, co tutaj się wydarzyło. W terminalu przeszliśmy do katalogu z plkiem źródłowym. Skompilowaliśmy program (Ty możesz to uczynić w programie z interfejsem graficznym, jak Dev-C++ czy Codeblocks), następnie stworzyliśmy plik tekstowy w folderze z kodem (tutaj również możesz użyć dowolnego programu) i uruchomiliśmy program, tak by wczytywał dane z pliku. Komenda wygląda tak (na Windowsie):

nazwa_programu.exe < plik_z_testem.txt

Oczywiście jeden test to za mało, by przekonać się, czy program na pewno działa poprawnie. Spróbuj wymyślić kilka innych i przetestować program w podobny sposób (zapisując test do pliku). Wróć tutaj, gdy znajdziesz test, na którym nasz kod zwraca zły wynik.

Wyświetlanie pomocniczych wartości

Już? OK. Rzeczywiście nasz program nie działa najlepiej. Oto przykładowy test, na którym program się myli:

Program zwraca:

Co teraz? Jeśli nie potrafimy znaleźć błędu patrząc tylko na kod, warto coś wypisać i sprawdzić, co nasz program robi krok po kroku. W tym przypadku wypiszmy pośrednie wyniki sortowania po każdym obrocie zewnętrznej pętli w funkcji sortuj(). Teraz nasza funkcja wygląda tak:

Zwróć uwagę na jedną rzecz: zamiast wypisywać na ekran poleceniem cout my używamy cerr. Jaka jest różnica między nimi? cerr (skrót do console-error) wypisuje tekst na tzw. standardowe wyjście błędów (standard error). Wizualnie nie zauważysz żadnych zmian, ale to polecenie ma jedną główną przewagę nad cout, z której skorzystamy – sprawdzarki kompletnie ignorują to, co wypisujesz za pomocą cerr. Zatem, gdy już znajdziesz wszystkie błędy i zdarzy ci się zapomnieć zakomentować linii wypisujących pomocniczy tekst za pomocą cerr, nie otrzymasz komunikatu Wrong Answer z tego powodu.

OK, sprawdźmy co wypisuje teraz nasz program:

Wychodzi na to, że pierwsze dwie liczby posortował poprawnie i dopiero z trzecią (2) ma problem. Dwójka powinna wylądować na początku ciągu 2 – 3 – 4 a jest w środku (trzecia linia). Dowiedzmy się dlaczego, tak się dzieje. Wypiszmy liczby, które aktualnie są porównywane. Kod funkcji wygląda teraz tak:

A program wypisuje:

Naszą uwagę przykuwa linijka:

Porównywanie dwóch tych samych liczb nigdy nie powinno się zdarzyć, skoro wszystkie liczby w naszym ciągu są różne. Szybki rzut oka na kod i widzimy, że zamiast porównywać:

My porównujemy:

Poprawiamy kod (2 linijki – jedną z ifem, drugą z wypisywaniem) i odpalamy kod raz jeszcze. Jeśli nie zamknąłeś terminala, wciśnij strzałkę do góry. Powinieneś zobaczyć poprzednio wpisane polecenie. To kolejna zaleta używania terminala.

Wszystko jest w porządku. Kod zwraca posortowany ciąg. Możesz usunąć teraz linijki z pomocniczym outputem (cerr) i spróbować wysłać program na sprawdzarkę. O, chociażby do tego problemu.

To wszystko na dziś. Warto jednak postawić sobie pytanie, czy przetestowanie kodu na kilku prostych testach jest wystarczające. Oczywiście, że nie. W ogóle nie sprawdziliśmy, jak nasz program radzi sobie w przypadku długich ciągów i czy jest dostatecznie szybki, by przejść limity czasowe. O tym porozmawiamy w drugiej części tego tutoriala, która już niebawem.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

*