• Willkommen im Zend Framework Forum

    ZF1 Zend Framework 1 + ZF2 Zend Framework 2

    Das Zend Framework Forum ist seit 2006 die erste Anlaufstelle für Zend Framework Entwickler in Deutschland. Mit über 70.000 Beiträgen und einer steigenden Nutzerzahl bietet das Forum hilfreiche Themen und ZF-Tutorials für professionelle Entwickler, fortgeschrittene Programmierer sowie Zend Framework Einsteiger.
    Wenn dies Dein erster Besuch in der Zend Framework Community ist, lies bitte zuerst die Hilfe - FAQ durch. Du musst Dich registrieren, bevor Du Beiträge verfassen kannst. Klicke oben auf 'Registrieren', um die Registrierung zu starten. Du kannst auch jetzt schon Beiträge lesen. Hier im Forum findest Du die Zend Framework Hilfe, die Du suchst!

    Grüße an alle Zend Framework Entwickler. Das Team vom Zend Framework Forum!

    Drupal Agentur

Testen der Business Logik mit DBUnit

zf2_timo

New member
Hallo,

ich stehe wieder vor einem Problem bei der Entwicklung einer ZF2 Anwendung.
Beim schreiben meiner Unit Tests habe ich eine Funktion erstellt, die überprüft ob die EditAction eine Exception schmeißt, wenn die id nicht existiert.
Als id habe ich 500 übergeben, die nicht existiert, und die exception wurde geworfen. Soweit alles ganz gut.
Allerdings wird irgendwann die 500 existieren und die exception wird nicht geschmissen.

Daher bin ich jetzt an dem Punkt angekommen, an dem ich DBUnit einsetzten muss.
Ich habe also meine Test Klasse erweitert und die getConnection() und getDataSet() Methoden eingefügt und sowie in den Tutorials, Blogs und Dokus beschrieben angepasst.
PHP:
/**
  * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection     
  */    
public function getConnection()    
{
        if (is_null($this->_connection)) {
            $this->_connection = false;
            $pdo = new Pdo('mysql:host=localhost;dbname=testdb', 'root', '');
            $this->_connection = $this->createDefaultDBConnection($pdo, 'testdb');
        }
         return $this->_connection;    
}
Jetzt bekomme ich immer den Fehler

1) Portal\Controller\ClubOfficesControllerTest::testIndexActionCanAccessed
Argument 2 passed to Zend\Db\Adapter\Driver\Pdo\Pdo::__construct() must be an instance of Zend\Db\Adapter\Driver\Pdo\Statement, string given, called in /srv/dev/www/timo/PortalV2/tests/module/Portal/src/Portal/Controller/ClubOfficesControllerTest.php on line 89 and defined
Ist ja auch logisch, weil die Zend-Klasse verwendet wird und die erwartet nun mal ein Statement-Objekt.
Aber welche Klasse muss ich nun Verwenden, damit meine Statements funktionieren und auf die CSV-Datei angewendet werden. Gibt es da eine von DBUnit?

Kann mir da jemand weiter helfen oder einen Tipp geben?

Danke und Grüße
 

st0ffel

New member
Ich habe noch keine ZF-Anwendung mit den Zend-Boardmitteln getestet. Bei ZF1 war es aber glaube ich so, dass die Applikation über die Zend-Testklasse gebootstrapt wurde. In der application.ini kann man fürs Testen dann ja die Verbindungsdaten für die Testdatenbank angeben. Wenn Du hier die gleichen Daten, wie in der getConnection-Methode von DBUnit angibst, sollte das mit der Verbindung passen. Wie das bei ZF2 gehen kann, musst Du leider selbst herausfinden :(

BTW: Wenn Du wirklich gegen eine Datenbank testen musst, dann ist es gut, wenn Du kompatibel zu SQLite bist. Die Datenbank kann dann im Speicher laufen und die Tests gehen bedeutend schneller.

Ich bin mir aber nicht sicher, ob Du wirklich gegen eine Datenbank testen musst. Du willst ja nur testen, dass eine Exception geworfen wird, wenn die Datenbank nix zurück liefert. Je nachdem wie Dein Code aufgebaut ist, sollte sich das auch mit einem Mock-Objekt der Datenbank testen lassen. Beim Testen ist es (fast) immer eine gute Idee die Datenbank so lange wie möglich außen vor zu lassen.
 

zf2_timo

New member
Danke für deine Antwort.

Bei ZF1 war es aber glaube ich so, dass die Applikation über die Zend-Testklasse gebootstrapt wurde.
Soweit ich es gesehen habe, gibt es im ZF2 keine Klasse zum testen.
Und die application.ini gibt es auch nicht mehr, bzw. ich setzte sie nicht ein. Meine Datenbankverbindung steht in der config/autoload/{global,local}.php. Bisher habe ich nichts gefunden, wie man hier unterschiedliche Entwicklungsumgebungen angeben könnte.

Dann werde ich noch mal schauen, ob ich den test über ein Mock-Object realisieren kann.

Es wäre trotzdem interessant zu wissen, wie ich gegen die DB testen kann, da ich es sicherlich früher oder später in dem Projekt benötige ;)

Grüße
 

DennisBecker

Super-Moderator
Wenn deine Business Logik richtig getrennt ist von der Peristenzschicht, dann brauchst du die Datenbank doch garnicht erst mocken. Ich vermute stark, dass deine Klassen noch zu viele Zuständigkeiten haben.
 

zf2_timo

New member
Ich habe mich bei der Erstellung an das Album-Tutorial gehalten. Aber vielleicht ist ja doch was an falscher Stelle gelandet.

Controller
PHP:
public function editAction(){
    $id = $this->params()->fromRoute('id', 0);
    $editClub =  $this->getOfficesChangeListTable()->getChangesById($id);

    return new ViewModel(
        array(
             'Club' => $editClub,
        )
    );
}
Model
PHP:
    public function getChangesById($id)
    {
        if ((int)$id <= 0) { 
           throw new InvalidArgumentException(
                'Parameter must be an Integer in "' . __METHOD__ . '"'
            );
        }

         $resultSet = $this->_TableGateway->select(array('Id' => $id));
        $row = $resultSet->current();

        if (!$row) {
            throw new RecordNotFoundException(
                'No Record with Id "' . $id . '" in "' .__METHOD__ . '"'
            );
        }

        return $row;    
}
Und ich möchte nun die RecordNotFoundException testen.
Dazu habe ich mir eine Unit-Test für den Controller geschrieben, in dem ich die Id auf 500 gesetzt habe.
Diese Id existiert momentan noch nicht in der Datenbank. Aber irgendwann wird es diese geben, wodurch die Exception nicht mehr ausgelöst wird.

Daher wollt ich nun mit DBUnit eine Testbank erstellen, in der die Id nicht existiert und auch nicht angelegt wird (es sein denn ein Entwickler mach dies per Hand)

Wie kann ich das Problem nun am besten lösen?

Danke und Grüße
 

DennisBecker

Super-Moderator
Dann musst du doch dein TableGateway mocken und die select() Methode überschreiben, so dass $resultset->surrent() die erwartete Rückgabe liefert, dass die Exception geworfen werden kann. Eine Interaktion mit der Datenbank ist nicht notwendig.
 

zf2_timo

New member
So, diese Woche habe ich mal wieder Zeit, an dem Projekt zu arbeiten (daher die späte Rückmeldung)

Ich habe nun für das TableGateway ein Mock erstellt und dies an mein Model übergeben.

PHP:
public function testEditActionThrowsActionIfRowNotExists(){
    $this->setExpectedException('\Portal\Exception\RecordNotFoundException');

    $resultSetPrototype = new ResultSet();
    $resultSetPrototype->setArrayObjectPrototype(new OfficeChangeList());

    $dbAdapter = $this->getDBAdapter();

    $mockTableGateway =
        $this->getMock(
            'Zend\Db\TableGateway\TableGateway',
            array('select'),
            array('OfficeChangeList', $dbAdapter, null, $resultSetPrototype),
            '',
            false
        );

    $mockTableGateway
        ->expects($this->once())
        ->method('select')
        ->with($this->equalTo(array('Id' => 500)))
        ->will($this->returnValue($resultSetPrototype)
    );

    $officeTable = new OfficeChangeListTable($mockTableGateway);
    $officeTable->getChangesById(500);
}
Wenn ich PHPUnit ausführe, erhalte ich einen Fatal Error, weil er die Methode Current in getChangesById nicht aufrufen kann.
Dies liegt (vermutlich) daran, dass bei dem ResultSet die Membervariable dataSource null ist. Beim Aufruf im Browser ist diese mit einer Instanz des Objekts "Zend\Db\Adapter\Driver\Pdo\Result" gefüllt.

Was mache ich falsch?

 

zf2_timo

New member
Ich hätte vielleicht die ganze Fehlermeldung posten sollen:
PHP Fatal error: Call to a member function current() on a non-object in /srv/www/timo/Portal/trunk/vendor/library/ZF2/library/Zend/Db/ResultSet/AbstractResultSet.php on line 193
Die current Methode habe ich trotzdem mit eingetragen, aber es ist immer noch der gleiche Fehler:
PHP:
    public function testEditActionThrowsExceptionIfRowNotExists()
    {
        $this->setExpectedException('\Portal\Exception\RecordNotFoundException');

        $resultSetPrototype = new ResultSet();
        $resultSetPrototype->setArrayObjectPrototype(new OfficeChangeList());

        $dbAdapter = $this->getDBAdapter();

        $mockTableGateway =
            $this->getMock(
                'Zend\Db\TableGateway\TableGateway',
                array('current', 'select'),
               array('OfficeChangeList', $dbAdapter, null, $resultSetPrototype), // <-- Vermute hier ist ein Fehler
               '',
                false
            );

        $mockTableGateway
            ->expects($this->once())
            ->method('select')
            ->with($this->equalTo(array('Id' => 500)))
            ->will($this->returnValue($resultSetPrototype)
        );

        $mockTableGateway
            ->expects($this->once())
            ->method('current')
            ->will($this->returnValue($resultSetPrototype));

        $officeTable = new OfficeChangeListTable($mockTableGateway);
        $officeTable->getChangesById(500);
    }
Da die Membervariable dataSource leer ist vermute ich in der markiert Zeile einen Fehler.
 
Zuletzt bearbeitet:

DennisBecker

Super-Moderator
Dein Testcode enthält auch einen Fehler:

PHP:
        $mockTableGateway
            ->expects($this->once())
            ->method('select')
            ->with($this->equalTo(array('Id' => 500)))
            ->will($this->returnValue($resultSetPrototype)
        );

        $mockTableGateway
            ->expects($this->once())
            ->method('current')
            ->will($this->returnValue($resultSetPrototype));
Willst du "current" nicht von ResultSet überschreiben? Laut deinem Produktivcode schon.
Zudem bin ich mir nicht sicher, ob du wirklich nur die Klasse "ResultSet" hast, oder ob du nicht eher "Zend\Db\ResultSet\ResultSet"?
 

zf2_timo

New member
Ich glaube, ich habe es jetzt (Beim ausführen wird jedenfalls kein Fehler angezeigt) :
PHP:
 public function testEditActionThrowsExceptionIfRowNotExists(){
    $this->setExpectedException('\Portal\Exception\RecordNotFoundException');

    $mockResultSet = $this->getMock(
        '\Zend\Db\ResultSet\ResultSet',
        array('current'),
        array(),
        '',
        false
    );

    $mockResultSet
        ->expects($this->once())
        ->method('current')
        ->will($this->returnValue(false));

    $mockResultSet->setArrayObjectPrototype(new OfficeChangeList());

    $dbAdapter = $this->getDBAdapter();

    $mockTableGateway = $this->getMock(
        '\Zend\Db\TableGateway\TableGateway',
        array('select'),
        array('OfficeChangeList', $dbAdapter, null, $mockResultSet),
        '',
        false
    );

    $mockTableGateway
        ->expects($this->once())
        ->method('select')
        ->with($this->equalTo(array('Id' => 500)))
        ->will($this->returnValue($mockResultSet));

    $officeTable = new OfficeChangeListTable($mockTableGateway);
    $officeTable->getChangesById(500);
}
Bitte korrigiert mich, wenn ich mich nicht an den "Standard" gehalten habe.
 
Oben