Mit phpunit TDD-Serien üben
Beginnen Sie mit einem Bankkonto
Angenommen, Sie haben phpunit installiert.
Wir beginnen mit einem einfachen Beispiel eines Bankkontos, um die Idee von TDD (Test-Driven-Development) zu verstehen.
Erstellen Sie zwei Verzeichnisse, src
und test
, unter dem Projektverzeichnis, erstellen Sie die Datei src
unter BankAccount.php
und erstellen Sie die Datei test
unter dem Verzeichnis BankAccountTest.php
.
Gemäß der Idee von TDD schreiben wir zuerst Tests und dann Produktionscode, also bleibt BankAccount.php
leer und wir schreiben zuerst BankAccountTest.php
.
<code><?php class BankAccountTest extends PHPUnit_Framework_TestCase { } ?></code>
Jetzt lassen Sie es uns ausführen und die Ergebnisse sehen. Die Befehlszeile zum Ausführen von phpunit lautet wie folgt:
<code>phpunit --bootstrap src/BankAccount.php test/BankAccountTest.php</code>
--bootstrap src/BankAccount.php
bedeutet, dass src/BankAccount.php
geladen wird, bevor der Testcode ausgeführt wird. Der auszuführende Testcode ist test/BankAccountTest.php
.
Wenn Sie keine bestimmte Testdatei angeben und nur ein Verzeichnis angeben, führt phpunit alle Dateien im Verzeichnis aus, deren Dateinamen mit *Test.php
übereinstimmen. Da es nur eine Datei test
im Verzeichnis BankAccountTest.php
gibt, führen Sie
<code>phpunit --bootstrap src/BankAccount.php test</code>
wird das gleiche Ergebnis erzielen.
<code>There was 1 failure: 1) Warning No tests found in class "BankAccountTest". FAILURES! Tests: 1, Assertions: 0, Failures: 1.</code>
Ein Warnfehler, da keine Tests vorhanden sind.
Kontoinstanziierung
Fügen wir unten einen Test hinzu. Beachten Sie, dass TDD eine Entwurfsmethode ist, die Ihnen dabei helfen kann, die Funktionalität eines Moduls von Grund auf zu entwerfen. Wenn wir Tests schreiben, müssen wir aus der Perspektive des Benutzers beginnen. Was macht der Benutzer zuerst, wenn er unsere Klasse BankAccount
verwendet? Es muss eine neue Instanz von BankAccount sein. Unser erster Test ist also der Test der Instanziierung .
<code>public function testNewAccount(){ $account1 = new BankAccount(); }</code>
Das Ausführen von phpunit ist wie erwartet fehlgeschlagen.
<code>PHP Fatal error: Class 'BankAccount' not found in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 5</code>
Keine Definition der Klasse BankAccount
gefunden. Als Nächstes schreiben wir den Produktionscode. Machen Sie den Test erfolgreich. Geben Sie in src/BankAccount.php
(im Folgenden als Quelldatei bezeichnet) den folgenden Inhalt ein:
<code><?php class BankAccount { } ?></code>
Führen Sie phpunit aus und der Test ist erfolgreich.
<code>OK (1 test, 0 assertions)</code>
Als nächstes müssen wir Tests hinzufügen, damit die Tests fehlschlagen. Wenn Sie ein neues Konto erstellen, sollte der Kontostand 0 sein. Deshalb haben wir eine assert
-Anweisung hinzugefügt:
<code>public function testNewAccount(){ $account1 = new BankAccount(); $this->assertEquals(0, $account1->value()); }</code>
Beachten Sie, dass value()
eine Mitgliedsfunktion von BankAccount
ist. Natürlich wurde diese Funktion noch nicht definiert. Als Benutzer hoffen wir, dass BankAccount
diese Funktion bereitstellt.
Führen Sie phpunit aus, die Ergebnisse sind wie folgt:
<code>PHP Fatal error: Call to undefined method BankAccount::value() in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 6</code>
Das Ergebnis zeigt uns, dass BankAccount
nicht über die Memberfunktion value()
verfügt. Produktionscode hinzufügen:
<code>class BankAccount { public function value(){ return 0; } }</code>
Warum sollte value()
direkt 0 zurückgeben, weil der Testcode erwartet, dass value()
0 zurückgibt? Das Prinzip von TDD besteht darin, keinen redundanten Produktionscode zu schreiben, sondern nur so viel, dass der Test bestanden werden kann.
Kontozugriff
Nachdem phpunit ausgeführt und bestanden hat, gehen wir zunächst davon aus, dass die Instanziierung von BankAccount
die Anforderungen erfüllt hat. Wie möchte der Benutzer dann BankAccount
verwenden? Sie möchten auf jeden Fall Geld einzahlen. Ich hoffe, dass BankAccount
über eine Einzahlungsfunktion verfügt. Also fügen wir den nächsten Test hinzu.
<code>public function testDeposit(){ $account = new BankAccount(); $account->deposit(10); $this->assertEquals(10, $account->value()); }</code>
Der Anfangssaldo des Kontos beträgt 0. Wenn wir 10 Yuan darauf einzahlen, sollte der Kontostand natürlich 10 betragen. Beim Ausführen von phpunit schlägt der Test fehl, da die Einzahlungsfunktion nicht definiert wurde:
<code>.PHP Fatal error: Call to undefined method BankAccount::deposit() in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 11</code>
Als nächstes fügen Sie die Einzahlungsfunktion in der Quelldatei hinzu:
<code>public function deposit($ammount) { }</code>
Führen Sie phpunit erneut aus und erhalten Sie die folgenden Ergebnisse:
<code>1) BankAccountTest::testDeposit Failed asserting that 0 matches expected 10.</code>
Da wir den Kontostand zu diesem Zeitpunkt nicht in der Einzahlungsfunktion bearbeitet haben, beträgt der Anfangswert des Kontostands 0 und ist nach Ausführung der Einzahlungsfunktion immer noch 0, was nicht dem vom Benutzer erwarteten Verhalten entspricht . Wir sollten den vom Benutzer eingezahlten Betrag zum Guthaben hinzufügen.
Um den Kontostand zu verwalten, sollte der Kontostand eine Mitgliedsvariable von BankAccount sein. Diese Variable darf von der Außenwelt nicht geändert werden und ist daher als private Variable definiert. Als nächstes fügen wir die private Variable $value
zum Produktionscode hinzu, dann sollte die Funktion value
den Wert von $value
zurückgeben.
<code>class BankAccount { private $value; public function value(){ return $this->value; } public function deposit($ammount) { $this->value = 10; } }</code>
Führen Sie phpunit aus und der Test ist erfolgreich. Als nächstes dachten wir: Was brauchen Benutzer sonst noch? Ja, Geld abheben. Beim Abheben von Geld wird dieser Wert vom Kontostand abgezogen. Wenn Sie der Funktion deposit
eine negative Zahl übergeben, entspricht dies einer Geldabhebung.
Also fügen wir der testDeposit
-Funktion des Testcodes zwei Codezeilen hinzu.
<code>$account->deposit(-5); $this->assertEquals(5, $account->value());</code>
Führen Sie phpunit erneut aus und der Test ist fehlgeschlagen.
<code>1) BankAccountTest::testDeposit Failed asserting that 10 matches expected 5.</code>
Das liegt daran, dass wir im Produktionscode einfach $value
auf das Ergebnis 10 gesetzt haben. Verbessern Sie den Produktionscode.
<code>public function deposit($ammount) { $this->value += $ammount; }</code>
Führen Sie phpunit erneut aus und der Test ist erfolgreich.
Neuer Konstruktor
Als nächstes kam mir der Gedanke, dass der Benutzer möglicherweise einen anderen Konstruktor benötigt, der beim Erstellen des BankAccount
-Objekts einen Wert als Kontostand übergeben könnte. Deshalb haben wir diesen Instanziierungstest in testNewAccount
hinzugefügt.
<code>public function testNewAccount(){ $account1 = new BankAccount(); $this->assertEquals(0, $account1->value()); $account2 = new BankAccount(10); $this->assertEquals(10, $account2->value()); }</code>
Führen Sie phpunit aus, das Ergebnis ist:
<code>1) BankAccountTest::testNewAccount Failed asserting that null matches expected 10.</code>
这时因为BankAccount
没有带参数的构造函数,因此new BankAccount(10)
会返回一个空对象,空对象的value()
函数自然返回的也是null。为了通过测试,我们在生产代码中增加带参数的构造函数。
<code>public function __construct($n){ $this->value = $n; }</code>
再运行测试:
<code>1) BankAccountTest::testNewAccount Missing argument 1 for BankAccount::__construct(), called in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 5 and defined /home/wuchen/projects/jolly-code-snippets/php/phpunit/src/BankAccount.php:5 /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php:5 2) BankAccountTest::testDeposit Missing argument 1 for BankAccount::__construct(), called in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 12 and defined /home/wuchen/projects/jolly-code-snippets/php/phpunit/src/BankAccount.php:5 /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php:12</code>
两个调用new BankAccount()
的地方都报告了错误,增加了带参数的构造函数,不带参数的构造函数又不行了。从c++/java
过渡来的同学马上想到增加一个默认的构造函数:
<code>public function __construct() { $this->value = 0; }</code>
但这样是不行的,因为php不支持函数重载,所以不能有多个构造函数。
怎么办?对了,我们可以为参数增加默认值。修改构造函数为:
<code>public function __construct($n = 0){ $this->value = $n; }</code>
这样调用 new BankAccount()
时,相当于传递了0给构造函数,满足了需求。
phpunit运行以下,测试通过。
这时,我们的生产代码为:
<code><?php class BankAccount { private $value; // default to 0 public function __construct($n = 0){ $this->value = $n; } public function value(){ return $this->value; } public function deposit($ammount) { $this->value += $ammount; } } ?></code>
总结
虽然我们的代码并不多,但是每一步都写得很有信心,这就是TDD的好处。即使你对php的语法不是很有把握(比如我),也可以对自己的代码很有信心。
用TDD的方式写程序的另一个好处,就是编码之前不需要对单个模块进行仔细的设计,可以在写测试的时候进行设计。这样开发出来的模块既可以满足用户需要,也不会冗余。
后面将会介绍 phpunit 的更多用法。
以上就介绍了用phpUnit入门TDD,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。