2009-02-13 13 views
9

Dla obiektów składających się na inny obiekt w ramach jego implementacji, jaki jest najlepszy sposób napisania testu jednostki, tak aby testowany był tylko podstawowy obiekt? Trywialny przykład:Testowanie obiektów z zależnościami w PHPUnit

class myObj { 
    public function doSomethingWhichIsLogged() 
    { 
     // ... 
     $logger = new logger('/tmp/log.txt'); 
     $logger->info('some message'); 
     // ... 
    } 
} 

wiem, że obiekt może być zaprojektowany tak, że zależność obiekt Rejestrator może być wstrzykiwany i stąd wyśmiewany w badanej jednostki, ale nie zawsze jest to przypadek - w bardziej skomplikowanych sytuacjach, trzeba zrobić do komponowania innych obiektów lub wykonywania wywołań metod statycznych.

Ponieważ nie chcemy testować obiektu rejestratora, tylko myObj, jak postępować? Czy tworzymy skrót "double" ze skryptem testowym? Coś jak:

class logger 
{ 
    public function __construct($filepath) {} 
    public function info($message) {} 
} 

class TestMyObj extends PHPUnit_Framework_TestCase 
{ 
    // ... 
} 

Wydaje wykonalne dla małych obiektów, ale byłoby uciążliwe dla bardziej skomplikowanych API gdzie SUT zależała od wartości zwracanych. Co się stanie, jeśli chcesz przetestować wywołania obiektu zależności w taki sam sposób, jak w przypadku obiektów próbnych? Czy istnieje sposób na kpiny z obiektów, które są tworzone przez SUT, a nie przekazywane?

Przeczytałem stronę podręcznika na temat makiet, ale nie wydaje się, aby obejmowała tę sytuację, w której zależność jest tworzona, a nie zagregowana. Jak ty to robisz?

+0

Pan spojrzał na PHPUnit która wprowadziła zależności testowe, a więc ponowne osprzętu od 3.4? http://sebastian-bergmann.de/archives/848-Fixture-Reuse-in-PHPUnit-3.4.html Cheers Markus –

Odpowiedz

4

Wydaje się, że wiesz już, że konkretne zależności klasy sprawiają, że testowanie jest trudne (lub wręcz niemożliwe). Musisz odłączyć tę zależność. Prostą zmianą, która nie narusza istniejącego interfejsu API, jest domyślne zachowanie, ale zapewnia hak do nadpisania go. Istnieje wiele sposobów, które można wdrożyć.

Niektóre języki mają narzędzia, które mogą wstrzykiwać fałszywe klasy do kodu, ale nie wiem nic takiego w PHP. W większości przypadków prawdopodobnie lepiej byłoby zrestrukturyzować swój kod.

+0

Wygląda na to, że mówisz, że każda forma agregacji jest zła, ponieważ jest to konkretna zależność, która jest trudna sprzężenie, a zatem trudne do testowania. Czy to jest poprawne?Myślę, że się zgadzam, ale czy nie doprowadziłoby to do ciężkiego skryptu startowego, w którym wszystkie obiekty zależności są wstrzykiwane tam, gdzie są potrzebne? – DavidWinterbottom

+1

@troelskn, tylko z ciekawości, możesz podać przykład narzędzi dla tych języków, o których mówisz. –

+0

@david Tak do pierwszej części i sort-of-yes do drugiej części, z tym że "ciężki" jest subiektywny. Kontenery DI to sposób na złagodzenie bólu. – troelskn

0

Wygląda źle zrozumiałem pytanie, pozwól mi spróbować jeszcze raz:

Należy użyć wzorca singleton lub fabrykę rejestratora, jeśli nie jest to zbyt późno już:

class LoggerStub extends Logger { 
    public function info() {} 
} 
Logger::setInstance(new LoggerStub()); 
... 
$logger = Logger::getInstance(); 

Jeśli możesz „t zmiany kodu, można użyć typu catch-all klasy, która jest obciążająca __call()

class GenericStub { 
    public function __call($functionName, $arguments) {} 
} 
6

Po troelskn doradza tutaj, że jest to podstawowy przykład tego, co powinieneś zrobić.

<?php 

class MyObj 
{ 
    /** 
    * @var LoggerInterface 
    */ 
    protected $_logger; 

    public function doSomethingWhichIsLogged() 
    { 
     // ... 
     $this->getLogger()->info('some message'); 
     // ... 
    } 

    public function setLogger(LoggerInterface $logger) 
    { 
     $this->_logger = $logger; 
    } 

    public function getLogger() 
    { 
     return $this->_logger; 
    } 
} 


class MyObjText extends PHPUnit_Framework_TestCase 
{ 
    /** 
    * @var MyObj 
    */ 
    protected $_myObj; 

    public function setUp() 
    { 
     $this->_myObj = new MyObj; 
    } 

    public function testDoSomethingWhichIsLogged() 
    { 
     $mockedMethods = array('info'); 
     $mock = $this->getMock('LoggerInterface', $mockedMethods); 
     $mock->expects($this->any()) 
      ->method('info') 
      ->will($this->returnValue(null)); 

     $this->_myObj->setLogger($mock); 

     // do your testing 
    } 
} 

Więcej informacji o atrapa obiektu można znaleźć in the manual.

0

Istnieje właściwie nowe rozszerzenie przeciążania klasy PHP wydane przez tych samych użytkowników, którzy budują PHPUnit. Pozwala na zastąpienie nowego operatora w przypadkach, w których nie można refaktoryzacji kodu, niestety instalacja systemu Windows nie jest łatwa.

URL jest http://github.com/johannes/php-test-helpers/blob/master/

Powiązane problemy