1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
|
<?xml version="1.0" encoding="UTF-8"?>
<!-- EN-Revision: 24249 -->
<!-- Reviewed: 22236 -->
<sect2 id="zend.test.phpunit.examples">
<title>Beispiele</title>
<para>
Zu wissen, wie man die eigene Infrastruktur für Tests einstellt und wie Zusicherungen zu
erstellen sind, ist nur die halbe Miete; jetzt ist es Zeit, sich einige Testszenarien
anzuschauen und herauszufinden, wie diese wirksam eingesetzt werden können.
</para>
<example id="zend.test.phpunit.examples.userController">
<title>Den UserController testen</title>
<para>
Betrachten wir eine Standardaufgabe für eine Webseite: Authentifizierung und Registrierung
von Benutzern. In unserem Beispiel definieren wir einen UserController, um das zu
behandeln und haben die folgenden Anforderungen:
</para>
<itemizedlist>
<listitem>
<para>
Wenn ein Benutzer nicht authentifiziert ist, wird er immer zur Login-Seite des
Controllers umgeleitet, unabhängig von der angeforderten Aktion.
</para>
</listitem>
<listitem>
<para>
Die Login-Formularseite wird sowohl das Login-Formular als auch das
Registrationsformular anzeigen.
</para>
</listitem>
<listitem>
<para>
Die Angabe von ungültigen Anmeldedaten soll zur Anzeige des Login-Formulars
führen.
</para>
</listitem>
<listitem>
<para>
Das Ansehen der Anmeldedaten soll zu einer Umleitung zur Profilseite des
Benutzers führen.
</para>
</listitem>
<listitem>
<para>
Die Profilseite soll angepasst werden, um den Benutzernamen des Benutzers
anzuzeigen.
</para>
</listitem>
<listitem>
<para>
Authentifizierte Benutzer, welche die Loginseite besuchen, sollen zu ihrer
Profilseite umgeleitet werden.
</para>
</listitem>
<listitem>
<para>
Bei der Abmeldung soll ein Benutzer zur Loginseite umgeleitet werden.
</para>
</listitem>
<listitem>
<para>
Mit ungültigen Daten soll die Registrierung fehlschlagen.
</para>
</listitem>
</itemizedlist>
<para>
Wir können und sollten zusätzliche Tests definieren, aber diese reichen vorerst aus.
</para>
<para>
Für unsere Anwendung definieren wir ein Plugin, 'Initialisieren' es, damit es bei
<methodname>routeStartup()</methodname> läuft. Das erlaubt es uns, das Bootstrapping in
einem OOP-Interface zu kapseln, was auch einen einfachen Weg bietet, um ein Callback zu
ermöglichen. Schauen wir uns erstmals die Grundlagen dieser Klasse an:
</para>
<programlisting language="php"><![CDATA[
class Bugapp_Plugin_Initialize extends Zend_Controller_Plugin_Abstract
{
/**
* @var Zend_Config
*/
protected static $_config;
/**
* @var string Aktuelle Umgebung
*/
protected $_env;
/**
* @var Zend_Controller_Front
*/
protected $_front;
/**
* @var string Pfad zum Root der Anwendung
*/
protected $_root;
/**
* Constructor
*
* Umgebung, Root Pfad und Konfiguration initialisieren
*
* @param string $env
* @param string|null $root
* @return void
*/
public function __construct($env, $root = null)
{
$this->_setEnv($env);
if (null === $root) {
$root = realpath(dirname(__FILE__) . '/../../../');
}
$this->_root = $root;
$this->initPhpConfig();
$this->_front = Zend_Controller_Front::getInstance();
}
/**
* Route beginnen
*
* @return void
*/
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$this->initDb();
$this->initHelpers();
$this->initView();
$this->initPlugins();
$this->initRoutes();
$this->initControllers();
}
// Die Definition von Methoden würde hier folgen...
}
]]></programlisting>
<para>
Das erlaubt es uns einen Bootstrap-Callback wie folgt zu erstellen:
</para>
<programlisting language="php"><![CDATA[
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public function appBootstrap()
{
$controller = $this->getFrontController();
$controller->registerPlugin(
new Bugapp_Plugin_Initialize('development')
);
}
public function setUp()
{
$this->bootstrap = array($this, 'appBootstrap');
parent::setUp();
}
// ...
}
]]></programlisting>
<para>
Sobald das fertig ist, können wir unsere Tests schreiben. Was ist jedoch mit den
Tests, die erfordern, dass der Benutzer angemeldet ist? Die einfache Lösung besteht darin,
dass unsere Anwendungslogik das macht... und ein bisschen trickst, indem die Methoden
<methodname>resetRequest()</methodname> und <methodname>resetResponse()</methodname>
verwendet werden, die es uns erlauben eine andere Anfrage abzusetzen.
</para>
<programlisting language="php"><![CDATA[
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
// ...
public function loginUser($user, $password)
{
$this->request->setMethod('POST')
->setPost(array(
'username' => $user,
'password' => $password,
));
$this->dispatch('/user/login');
$this->assertRedirectTo('/user/view');
$this->resetRequest()
->resetResponse();
$this->request->setPost(array());
// ...
}
// ...
}
]]></programlisting>
<para>
Jetzt schreiben wir Tests:
</para>
<programlisting language="php"><![CDATA[
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
// ...
public function testCallWithoutActionShouldPullFromIndexAction()
{
$this->dispatch('/user');
$this->assertController('user');
$this->assertAction('index');
}
public function testLoginFormShouldContainLoginAndRegistrationForms()
{
$this->dispatch('/user');
$this->assertQueryCount('form', 2);
}
public function testInvalidCredentialsShouldResultInRedisplayOfLoginForm()
{
$request = $this->getRequest();
$request->setMethod('POST')
->setPost(array(
'username' => 'bogus',
'password' => 'reallyReallyBogus',
));
$this->dispatch('/user/login');
$this->assertNotRedirect();
$this->assertQuery('form');
}
public function testValidLoginShouldRedirectToProfilePage()
{
$this->loginUser('foobar', 'foobar');
}
public function testAuthenticatedUserShouldHaveCustomizedProfilePage()
{
$this->loginUser('foobar', 'foobar');
$this->request->setMethod('GET');
$this->dispatch('/user/view');
$this->assertNotRedirect();
$this->assertQueryContentContains('h2', 'foobar');
}
public function
testAuthenticatedUsersShouldBeRedirectedToProfileWhenVisitingLogin()
{
$this->loginUser('foobar', 'foobar');
$this->request->setMethod('GET');
$this->dispatch('/user');
$this->assertRedirectTo('/user/view');
}
public function testUserShouldRedirectToLoginPageOnLogout()
{
$this->loginUser('foobar', 'foobar');
$this->request->setMethod('GET');
$this->dispatch('/user/logout');
$this->assertRedirectTo('/user');
}
public function testRegistrationShouldFailWithInvalidData()
{
$data = array(
'username' => 'This will not work',
'email' => 'this is an invalid email',
'password' => 'Th1s!s!nv@l1d',
'passwordVerification' => 'wrong!',
);
$request = $this->getRequest();
$request->setMethod('POST')
->setPost($data);
$this->dispatch('/user/register');
$this->assertNotRedirect();
$this->assertQuery('form .errors');
}
}
]]></programlisting>
<para>
Es ist zu beachten, dass die Tests knapp sind und größtenteils nicht den
aktuellen Inhalt suchen. Stattdessen suchen sie nach Teilen in der Anfrage --
Anfrage Codes und Header sowie DOM-Knoten. Das erlaubt es schnell zu prüfen, dass die
Strukturen wie erwartet sind -- und verhindern, dass die Tests jedesmal scheitern,
wenn der Site neue Inhalte hinzugefügt werden.
</para>
<para>
Es ist auch zu beachten, dass wir die Struktur des Dokuments in unseren Tests verwenden.
Zum Beispiel suchen wir im letzten Test nach einer Form, die einen Knoten der Klasse
"errors" hat; das erlaubt es uns lediglich auf das Vorhandensein von
Form-Prüfungsfehlern zu testen und uns keine Sorgen darüber zu machen, warum spezielle
Fehler überhaupt geworfen werden.
</para>
<para>
Diese Anwendung <emphasis>könnte</emphasis> eine Datenbank verwenden. Wenn dem so ist,
muss man wahrscheinlich einige Grundlagen ändern um sicherzustellen, dass die Datenbank am
Anfang jedes Tests in einer unverfälschten, testbaren Konfiguration ist. PHPUnit bietet
bereits Funktionalität um das sicherzustellen; <ulink
url="http://www.phpunit.de/manual/3.4/en/database.html">Lesen Sie darüber in
der PHPUnit-Dokumentation nach</ulink>. Wir empfehlen eine separate Datenbank für das
Testen zu verwenden statt der Produktionsdatenbank und entweder eine SQLite-Datei oder
eine Datenbank im Speicher zu verwenden, da beide Optionen sehr performant sind, keinen
separaten Server benötigen und die meisten <acronym>SQL</acronym>-Syntax verwenden
können.
</para>
</example>
</sect2>
|