Witaj, Gościu O nas | Kontakt | Mapa
Wortal Forum PHPEdia.pl Planeta Kubek IRC Przetestuj się!

Testowanie modułów z użyciem frameworka SimpleTest

Krótkie podsumowanie naszej wiedzy o TDD

Powyższy przykład nie jest zbyt skomplikowany, ale dobrze pokazuje, na czym polega programowanie sterowane testami. Podsumujmy zatem krótko to, czego nauczyliśmy się do tej pory o TDD.

  • Projektując nową klasę powinniśmy zastanowić się, jak ma wyglądać jej interfejs i zapisać sobie jego projekt np. na kartce. Następnie piszemy test, w którym korzystamy z tego interfejsu. Dzięki temu widzimy, czy API, które stworzyliśmy jest solidne i wygodne w użytkowaniu. Jeśli okaże się, że to API wymaga poprawek, nie będziemy musieli przerabiać kodu klasy, bo go po prostu jeszcze nie ma. Oszczędza nam to naprawdę sporo czasu.
  • Kiedy mamy gotowy test i uważamy, że API jest już dopracowane, możemy zabrać się za napisanie interfejsu klasy.
  • Główna zasada TDD głosi, żeby nie pisać żadnego kodu, póki aplikacja przechodzi testy pomyślnie. Dopiero, gdy test się nie uda, możemy dokonać refaktoringu. W tym wypadku oczywiste jest, że test się nie powiedzie -- nie mamy przecież żadnego kodu. Następnie powinniśmy zaimplementować metody klasy tak, aby test się udał. Powinien to być możliwie najprostszy kawałek kodu. Po tej czynności uruchamiamy test ponownie. Jeśli wszystko się powiedzie, dodajemy nowe warunki, by sprawdzić, jak klasa reaguje na inne dane. Uruchamiamy test ponownie. Jeśli wynik będzie negatywny, przeprowadzamy refaktoring kodu, czyli modyfikujemy go tak, żeby przeszedł testy. Te kroki powtarzamy do momentu, kiedy wszystkie możliwe ewentualności będą sprawdzone. Będziemy wtedy mogli uważać naszą klasę za gotową do użytku.
  • Bardzo wyraźnie widać teraz, na czym w praktyce polega zasada YAGNI. Normalnie zaimplementowalibyśmy klasę w całości, zawierając w niej funkcjonalność, której możemy nigdy nie wykorzystać. W tym wypadku kodujemy tylko i wyłącznie to, co narzuca nam nas test i jednocześnie zachowujemy przejrzystość i maksymalną prostotę kodu.
  • Pisanie testów wbrew pozorom nie jest takie proste. Test, który napiszemy, posłuży nam przez cały cykl życia programu, dlatego warto poświęcić troszkę czasu na zaprojektowanie go. Za każdym razem, gdy wprowadzamy jakieś zmiany w kodzie, powinniśmy niezwłocznie uruchomić test, który od razu powie nam, czy czegoś nie zepsuliśmy.
  • Źle napisany test także może prowadzić do błędów, które bardzo trudno później wykryć. Powinniśmy zrobić wszystko by sprawdzić, jak skrypt radzi sobie w każdej możliwej sytuacji. Odwołując się do przykładu z Validatorem: na początku nie wzięliśmy pod uwagę faktu, że użytkownik może wpisać zbyt długi login, bądź może nie wpisać go w ogóle. W takiej sytuacji ten błąd mogliby zauważyć i wykorzystać dopiero końcowi użytkownicy naszego skryptu.

Listing 5. Zastosowanie metod setUp() i tearDown()

<?php
class FileTestCase extends UnitTestCase {
    private $file = './test.txt';
    public function setUp() {
        @unlink($this->file);
    }
    public function tearDown() {
        @unlink($this->file);
    }
    public function testCreation() {
        $this->assertFalse(file_exists($this->file));
        $file = new File($this->file);

        // upewnij się, że nie wystąpiły żadne błędy php:
        $this->assertNoErrors();
        $this->assertTrue(file_exists($this->file));
    }
    public function testReadWrite() {
        $test_msg = 'test msg';
        $file = new File($this->file);
        $file->write($test_msg);
        unset($file);
        $file = new File($this->file);
        $contents = $file->readContents();
        $this->assertEqual($contents, $test_msg);
    }
}
?>

Listing 6. Grupowanie testów

<?php
// plik tests.php
require_once('../validator.php');
require_once('../file.php');
class ValidatorTestCase extends UnitTestCase {
    /* ... */
}
class FileTestCase extends UnitTestCase {
    /* ... */
}
?>
<?php
// plik group_test.php
require_once('./simpletest/unit_tester.php');
require_once('./simpletest/reporter.php');
require_once('./tests.php');
$test = new GroupTest('Wszystkie testy');
$test->addTestCase(new ValidatorTestCase());
$test->addTestCase(new FileTestCase());
$test->run(new HtmlReporter());
?>

Listing 7. Zastosowanie metody addTestFile()

<?php
require_once('./simpletest/unit_tester.php');
require_once('./simpletest/reporter.php');
$test = new GroupTest('Wszystkie testy');
$test->addTestFile('tests.php');
$test->run(new HtmlReporter());
?>
Refaktoring

Z programowaniem ekstremalnym wiąże się jeszcze jedno ważne pojęcie: refaktoring (lub refaktoryzacja). Jest to proces wprowadzania zmian w kodzie, mający na celu poprawę obecnej architektury aplikacji, by dalsza jej rozbudowa była jak najmniej kłopotliwa, a kod pozostał prosty i przejrzysty.

TDD kontra reszta świata

Wyobraźmy sobie, jak wyglądałoby testowanie naszego Validatora tradycyjną techniką. Najpierw zaimplementowalibyśmy klasę, później stworzylibyśmy formularz rejestracji i obsługujący go kod PHP. Następnie kilkanaście lub kilkadziesiąt razy wpisywalibyśmy do formularza różne dane, by sprawdzić, jak Validator na nie zareaguje. Tymczasem, dzięki kilku linijkom dodatkowego kodu mamy pewność, że klasa działa bezbłędnie. Pojawia się pytanie: skoro unit testy są tak dobre, to dlaczego prawie wszyscy męczą się z wypisywaniem stanu zmiennych instrukcjami var_dump() i echo? Jest na to bardzo wiele wymówek. Wymienimy niektóre z nich i zastanowimy się, dlaczego są błędne:

  • Po co testować kod, który działa? Działający program bywa pojęciem względnym. Czasem program uruchamia się bezbłędnie na naszym komputerze, ale nawet najdrobniejsza różnica w konfiguracji serwera może spowodować, że niemożliwe będzie używanie aplikacji na innej maszynie. Trzeba wziąć to pod uwagę i dokładnie sprawdzić wszystkie ewentualności.
  • Po co tracić czas na pisanie setek linii dodatkowego kodu dla testów? Przecież nie mogę pozwolić sobie na jakiekolwiek opóźnienia. Większość opóźnień w pracach nad projektem jest spowodowanych złą architekturą i błędami w kodzie. Można tego uniknąć zawczasu pisząc testy, co wcale nie wymaga dużego nakładu pracy. Jak się przed chwilą przekonałeś, tworzenie i uruchamianie testu to dosłownie parę chwil.
  • Wszystkie błędy i tak prędzej, czy później zostaną wyłapane przez betatesterów i użytkowników. Nie liczmy na to, że betatesterzy wykryją każdą najdrobniejszą usterkę w naszym programie. O ile istnieje duże prawdopodobieństwo, że uda się im się wyłapać niedociągnięcia i błędy uciążliwe dla użytkownika, to trudno będzie im znaleźć bugi w architekturze czy logice kodu. O ile dla betatesterów szukanie błędów jest sprawą oczywistą, bo takie mają zadanie, to końcowi użytkownicy naszego programu wcale nie będą zadowoleni ze znajdowania w nim kolejnych usterek.
  • Mój program nie ma błędów. Każdy program zawiera błędy. Choćbyśmy się starali ze wszystkich sił, nie da się ich uniknąć. Możemy jednak skutecznie ograniczyć ich ilość i znacząco zmniejszyć ich szkodliwość.
  • Testy nie uwolnią mnie całkowicie od instrukcji var_dump(). To prawda - nie zawsze udaje się uniknąć szukania błędów za pomocą wypisywania stanu zmiennych na ekran. Nawet przy intensywnym testowaniu, czasem trzeba skorzystać ze starych sposobów. Takie sytuacje są jednak, na szczęście, dość sporadyczne.

Listing 8. Ignorowanie poszczególnych klas za pomocą instrukcji SimpleTestOptions::ignore()

<?php
//plik file_tests.php
require_once('../file.php');
class FileTestCase extends UnitTestCase {
    /* ... */
}
SimpleTestOptions::ignore('FileTestCase');
class AnotherFileTestCase extends FileTestCase {
    /* ... */
}
?>

Listing 9. Bardziej wyspecjalizowany zestaw testów

<?php
// plik config_group_test.php
require_once('./simpletest/unit_tester.php');
require_once('./simpletest/reporter.php');
require_once('./config_tests.php');
class ConfigGroupTest extends GroupTest {
    function construct()
    {
        $this->__construct('Testy konfiguracji');
        $this->addTestCase(new XMLConfigTestCase());
        $this->addTestCase(new INIConfigTestCase());
        $this->addTestCase(new DatabaseConfigTestCase());
    }
}
?>
<?php
// plik app_test.php
require_once('./config_group_test.php');
$test = new ConfigGroupTest();
$test->run(new HtmlReporter());
?>

Listing 10. Zagnieżdżone zestawy testów

<?php
require_once('config_group_test.php');
$test = new AppGroupTest('Test całej aplikacji');
$test->addTestCase(new ConfigGroupTest());
$test->run(new HtmlReporter());
?>

Listing 11. Prosta klasa odpowiedzialna za interakcję z bazą danych.

class DB {
    public function connect() {
    }
    public function execute($sql) {
    }
}
class News {
    public $title;
    public $text;
    public function __construct($title = '', $text = '') {
        $this->title = $title;
        $this->text = $text;
    }
    public static function findById($id, $db) {
        $data = $db->execute('SELECT * FROM news WHERE id = ' . $id);
        return new News($data['title'], $data['text']);
    }
}

Informacje na podobny temat:
Wasze opinie
Wszystkie opinie użytkowników: (1)
Usrapwnienia wizualne
Niedziela 15 Styczeń 2006 12:35:06 pm - aztech <scrabblewroclaw_at_op.pl>

Proponowałbym podlinkowanie wszystkich odnośników pojawiających (część jest, część natomiast nie - konrketnie w części artukułu: SimpleTest + Eclipse). Proponowałbym także wprowadzić podlinkowania do listingów (myślę, że to byłaby dobra praktyka dla wszystkich artykułów - szalenie ułatwia czytanie), ale tak aby przenosiły w miejsce listingu a nie tylko na stronę, gdzie znajduje się listing (a href="strona.html#name").
Można by w sumie też zrobić highlighting najważniejszych terminów, nazw klas, nazw funkcji (ale niekoniecznie, jeśli miałoby to zaciemnić artykuł).
P.S. Artykuł ciekawy. Z racji, że nigdy nie używałem SimpleTest, a zamierzam się nim pobawić po przeczytaniu artykułu, postaram się napisać wkrótce opinię, na ile sam artykuł pomaga w bezproblemowe (problemowe :D) wejście w SimpleTest

Mentax.pl    NQ.pl- serwery z dodatkiem świętego spokoju...   
O nas | Kontakt | Mapa serwisu
Copyright (c) 2003-2022 php.pl    Wszystkie prawa zastrzeżone    Powered by eZ publish Content Management System eZ publish Content Management System