#はじめに
PHPUnit は、最も古く、最も有名な PHP 単体テスト パッケージの 1 つです。これは主に単体テストに使用されます。つまり、コードを可能な限り最小のコンポーネントでテストできますが、非常に柔軟性が高く、単体テスト以外にも使用できます。 PHPUnit には、コードを簡単にテストできるシンプルで柔軟なアサーションが多数含まれており、これらのアサーションは特定のコンポーネントをテストするときに非常に効果的です。ただし、コントローラーやフォーム送信の検証など、より高度なコードのテストははるかに複雑になる可能性があることを意味します。 開発者にとって開発を容易にするために、Laravel フレームワークには、アプリケーションの複雑な部分をテストするための非常に単純な PHPUnit テストを作成できる一連のアプリケーション テスト ヘルパーが含まれています。 このチュートリアルの目的は、デフォルトの PHPUnit アサーションと Laravel テスト ヘルパーを使用した、PHPUnit テストの基本を紹介することです。このチュートリアルを終了するまでに、自信を持ってアプリケーションの基本テストを作成できるようになるという考えです。前提条件
このチュートリアルは、読者がすでに Laravel に精通しており、アプリケーション ディレクトリでコマンドを実行する方法 (php 職人コマンドなど) を知っていることを前提としています。さまざまなテスト ツールがどのように機能するかを学ぶために、いくつかの基本的なサンプル クラスを作成します。そのため、このチュートリアル用に新しいアプリケーションを作成することをお勧めします。 Laravel がすでにインストールされている場合は、次のコマンドを実行して新しいテスト アプリケーションを作成できます:laravel new phpunit-tests
composer create-project laravel/laravel --prefer-dist
新しいテストの作成
PHPUnit を使用する最初のステップは、新しいテスト クラスを作成することです。テスト クラスの規則では、アプリケーション ディレクトリの ./tests/ に保存されます。このフォルダーでは、各テスト クラスの名前はphp artisan make:test BasicTest
<?php class BasicTest extends TestCase { /** * 一个基本的测试示例。 * * @return void */ public function testExample() { $this->assertTrue(true); } }
<?xml version="1.0" encoding="UTF-8"?> <phpunit ... > <testsuites> <testsuite name="Application Test Suite"> <directory>./tests/</directory> </testsuite> </testsuites> ... </phpunit>
./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors. .. Time: 103 ms, Memory: 12.75Mb OK (2 tests, 3 assertions)
基本的なテストの作成
PHPUnit が提供する基本的なアサーションを容易にするために、最初にいくつかの単純な機能を提供する基本クラスを作成します /app/ ディレクトリに Box.php という名前の新しいファイルを作成し、このサンプル クラスをコピーします:<?php namespace App; class Box { /** * @var array */ protected $items = []; /** * 使用给定项构造框 * * @param array $items */ public function __construct($items = []) { $this->items = $items; } /** * 检查指定的项目是否在框中。 * * @param string $item * @return bool */ public function has($item) { return in_array($item, $this->items); } /** * 从框中移除项,如果框为空,则为 null 。 * * @return string */ public function takeOne() { return array_shift($this->items); } /** * 从包含指定字母开头的框中检索所有项目。 * * @param string $letter * @return array */ public function startsWith($letter) { return array_filter($this->items, function ($item) use ($letter) { return stripos($item, $letter) === 0; }); } }
assertTrue() 和 assertFalse() 允许你声明一个值等于 true 或 false 。这意味着它们非常适合测试返回布尔值的方法。在我们的 Box 类中,我们有一个名为 has($item) 的方法,当指定的项在 box 中或不在 box 中时,该方法返回对应返回 true 或 false . 要在 PHPUnit 中为此编写测试,我们可以执行以下操作: 注意我们如何只将一个参数传递给 assertTrue() 和 assertFalse() 方法,并且它是 has($item) 方法的输入. 如果您现在运行 ./vendor/bin/phpunit 命令,您会注意到输出包括: 这意味着我们的测试已经通过。 如果您将 assertFalse() 替换成 assertTrue() 并运行 phpunit 命令,输出将如下所示: 这告诉我们第 12 行的断言未能断言 false 值是 true - 因为我们将 assertFalse() 替换为 assertTrue() 。 将其交换回来,然后重新运行 PHPUnit 。测试应该再次通过,因为我们已经修复了破损的测试。 assertEquals () 与 assertNull () 接下来,让我们看看 assertEquals(), 以及 assertNull()。 assertEquals() 用于比较变量实际值与预期值是否相等。我们用它来检查 takeOne() 方法的返回值是否为 Box 内的当前值。当 Box 为空时,takeOne() 将返回 null,我们亦可使用 assertNull() 来进行检查。 与 assertTrue()、assertFalse() 以及 assertNull() 不同,assertEquals() 需要两个参数。第一个参数为 预期 值,第二个参数则为 实际 值。 可参照如下代码实现以上断言(assertions): 运行 phpunit 命令,你应当看到如下输出: assertContains () 和 assertCount () 以及 assertEmpty () 终于,我们有三个作用于数组有关的断言,我们能够使用它们去检查 Box 类中的 startsWith($item) 方法。 assertContains() 断言传递进来的数组中包含指定值, assertCount() 断言数组的项数为指定数量,assertEmpty() 断言传递进来的数组为空。 让我们来执行以下测试: 保存并再一次运行你的测试: 恭喜你,你刚刚使用七个基础的 PHPUnit 断言完成了对 Box 类的全部测试。通过这些简单的断言你能够做许多事,对于其他断言,大多数要更复杂,不过它们仍遵循以上使用规则。 测试你的程序 在你的程序里,对每个组件进行单元测试在很多情况下都是有必要的,而且也应该成为你开发过程中必不可少的一部分,但这并不是你需要做的全部的测试。当你构建一个包含复杂视图、导航和表单的程序时,你同样想测试这些组件。这时,Laravel 的测试助手可以使这些测试像单元测试简单组件一样容易。 我们之前查看在 ./tests/ 目录下的默认文件时跳过了 ./tests/ExampleTest.php 文件。 现在打开它,内容如下所示: 我们可以看到这个测试示例非常简单。在不知道测试助手如何运作的情况下,我们可以猜测它的意思如下: 当我访问 / (根目录) 我应该看到 'Laravel 5' 如果你打开你的 web 浏览器,访问我们的程序(如果你没有启动你的 web 服务器,你可以运行 php artisan serve ),你应该可以在 web 根目录上看到屏幕上有 “Laravel 5” 的文本。 鉴于这个测试已经通过了 PHPUnit,我们可以很确定地说我们对这个测试示例改造是正确的。 这个测试确保了访问 / 路径,网页可以返回 “'Laravel 5” 的文本。一个如此简单的检查也许不代表什么,但如果你的网站上要显示关键信息,它就可以在一个别处的改动导致这个页面无法正常显示正确的信息时,防止你部署一个被损坏的程序。 visit ()、see () 以及 dontSee () 现在尝试编写自己的测试,更进一步理解它吧。 首先,编辑 ./app/Http/routes.php ,增加一个新的路由。为了教程目的,我们创建希腊字母定义的路由: 然后,创建视图文件 ./resources/views/alpha.blade.php,使用 Alpha 作为关键字,保存基本的 HTML 文件: 打开浏览器,输入网址: http://localhost:8000/beta,页面会显示出 "This is the Alpha page." 的内容。 现在我们有了测试用到的模版文件,下一步,我们通过运行命令 make:test 来创建一个新的测试文件: 然后变成刚创建好的测试文件,按照框架提供的例子,测试 "alpha" 页面上没有包含 "beta" 。 我们可以使用方法 dontSee() ,它是 see() 的对应的反向方法。 下面代码是上面实现的简单例子: 保存并运行 PHPUnit (./vendor/bin/phpunit),测试代码应该会全部通过,你会看到像这样的测试状态内容显示: 开发前先写测试 对于测试来说,测试驱动开发 (TDD) 是非常酷的方法,首先我们先写测试。写完测试并执行它们,你会发现测试没通过,接下来 我们编写满足测试的代码,再次执行测试,使测试通过。 接下来让我们开始。 首先,建立一个 BetaTest 类使用 make:test artisan 命令: 接下来,更新测试用例以便检查 /beta 的路由 route 为「Beta」: 现在使用 ./vendor/bin/phpunit 命令来执行测试。结果是一个看起来简洁但不好的错误信息,如下: 我们现在需要创建这个不存在的路由。让我们开始。 首先,编辑 ./app/Http/routes.php 文件来创建新的 /beta 路由: 接下来,在 ./resources/views/beta.blade.php 下创建如下视图模版: 现在再一次执行 PHPUnit,结果应该再一次回到绿色。 这样我们就通过在完成新的页面之前写测试的方式,对 测试驱动开发 进行了实践。 click () 和 seePageIs () Laravel 也提供一个辅助函数 (click()) 允许测试点击页面中存在的连接 ,以及一个方法 (seePageIs()) 检查点击展示的结果页面。 让我们使用这两个辅助函数去执行在 Alpha 和 Beta 页面的链接。 首先,我们更新我们的测试。打开 AlphaTest 类,我们将添加一个新的测试方法,这将点击 「alpha」页面上的「Next」链接跳转到 「beta」页面。 新的测试代码如下: 注意到,在我们新建的 testClickNextForBeta() 方法中,我们并没有检查每一个页面的内容。 其他测试都成功的检查了两个页面的内容,所以这里我们只关心点击 「Next」链接将发送到 /beta。 你现在可以运行测试组件了,但就像预料的一样测试将不通过,因为我们还没有更新我们的 HTML。 接下来,我们将更新 BetaTest 来做类似的事情: 接下来,我们更新我们的 HTML 模版。 ./resources/views/alpha.blade.php: 保存文件,再一次执行 PHPUnit: 然而测试失败了。如果你仔细观察我们的新 HTML,你将注意到我们分别有术语 beta 和 alpha 在 /alpha 和 /beta 页面。这意味着我们需要稍微更改我们的测试让它们与误报不匹配。 在每一个 AlphaTest 和 BetaTest 类,更新 testDisplays* 方法去使用 dontSee(' 两个测试文件如下所示: ./tests/AlphaTest.php: ./tests/BetaTest.php:<?php
use App\Box;
class BasicTest extends TestCase
{
public function testHasItemInBox()
{
$box = new Box(['cat', 'toy', 'torch']);
$this->assertTrue($box->has('toy'));
$this->assertFalse($box->has('ball'));
}
}
OK (2 tests, 4 assertions)
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.
F.
Time: 93 ms, Memory: 13.00Mb
There was 1 failure:
1) BasicTest::testHasItemInBox
Failed asserting that false is true.
./tests/BasicTest.php:12
FAILURES!
Tests: 2, Assertions: 4, Failures: 1.
<?php
use App\Box;
class BasicTest extends TestCase
{
public function testHasItemInBox()
{
$box = new Box(['cat', 'toy', 'torch']);
$this->assertTrue($box->has('toy'));
$this->assertFalse($box->has('ball'));
}
public function testTakeOneFromTheBox()
{
$box = new Box(['torch']);
$this->assertEquals('torch', $box->takeOne());
// 当前 Box 为空,应当为 Null
$this->assertNull($box->takeOne());
}
}
OK (3 tests, 6 assertions)
<?php
use App\Box;
class BasicTest extends TestCase
{
public function testHasItemInBox()
{
$box = new Box(['cat', 'toy', 'torch']);
$this->assertTrue($box->has('toy'));
$this->assertFalse($box->has('ball'));
}
public function testTakeOneFromTheBox()
{
$box = new Box(['torch']);
$this->assertEquals('torch', $box->takeOne());
// Null,现在这个 box 是空的。
$this->assertNull($box->takeOne());
}
public function testStartsWithALetter()
{
$box = new Box(['toy', 'torch', 'ball', 'cat', 'tissue']);
$results = $box->startsWith('t');
$this->assertCount(3, $results);
$this->assertContains('toy', $results);
$this->assertContains('torch', $results);
$this->assertContains('tissue', $results);
// 如果传递复数断言数组为空
$this->assertEmpty($box->startsWith('s'));
}
}
OK (4 tests, 9 assertions)
<?php
class ExampleTest extends TestCase
{
/**
* 一个基本功能测试示例。
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
<?php
Route::get('/',function () {
return view('welcome');
});
Route::get('/alpha',function () {
return view('alpha');
});
<!DOCTYPE html>
<html>
<head>
<title>Alpha</title>
</head>
<body>
<p>This is the Alpha page.</p>
</body>
</html>
php artisan make:test AlphaTest
<?php
class AlphaTest extends TestCase
{
public function testDisplaysAlpha()
{
$this->visit('/alpha')
->see('Alpha')
->dontSee('Beta');
}
}
OK (5 tests,12 assertions)
php artisan make:test BetaTest
<?php
class BetaTest extends TestCase
{
public function testDisplaysBeta()
{
$this->visit('/beta')
->see('Beta')
->dontSee('Alpha');
}
}
> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.
....F.
Time: 144 ms, Memory: 14.25Mb
There was 1 failure:
1) BetaTest::testDisplaysBeta
一个对 [http://localhost/beta] 的请求失败了。收到状态码 [404]。
...
FAILURES!
Tests: 6, Assertions: 13, Failures: 1.
<?php
Route::get('/', function () {
return view('welcome');
});
Route::get('/alpha', function () {
return view('alpha');
});
Route::get('/beta', function () {
return view('beta');
});
<!DOCTYPE html>
<html>
<head>
<title>Beta</title>
</head>
<body>
<p>This is the Beta page.</p>
</body>
</html>
> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.
......
Time: 142 ms, Memory: 14.00Mb
OK (6 tests, 15 assertions)
<?php
class AlphaTest extends TestCase
{
public function testDisplaysAlpha()
{
$this->visit('/alpha')
->see('Alpha')
->dontSee('Beta');
}
public function testClickNextForBeta()
{
$this->visit('/alpha')
->click('Next')
->seePageIs('/beta');
}
}
<?php
class BetaTest extends TestCase
{
public function testDisplaysBeta()
{
$this->visit('/beta')
->see('Beta')
->dontSee('Alpha');
}
public function testClickNextForAlpha()
{
$this->visit('/beta')
->click('Previous')
->seePageIs('/alpha');
}
}
<!DOCTYPE html>
<html>
<head>
<title>Alpha</title>
</head>
<body>
<p>This is the Alpha page.</p>
<p><a href="/beta">Next</a></p>
</body>
</html>
./resources/views/beta.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Beta</title>
</head>
<body>
<p>This is the Beta page.</p>
<p><a href="/alpha">Previous</a></p>
</body>
</html>
> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.
F....F..
Time: 175 ms, Memory: 14.00Mb
There were 2 failures:
1) AlphaTest::testDisplaysAlpha
Failed asserting that '<!DOCTYPE html>
<html>
<head>
<title>Alpha</title>
</head>
<body>
<p>This is the Alpha page.</p>
<p><a href="/beta">Next</a></p>
</body>
</html>
' does not match PCRE pattern "/Beta/i".
2) BetaTest::testDisplaysBeta
Failed asserting that '<!DOCTYPE html>
<html>
<head>
<title>Beta</title>
</head>
<body>
<p>This is the Beta page.</p>
<p><a href="/alpha">Previous</a></p>
</body>
</html>
' does not match PCRE pattern "/Alpha/i".
FAILURES!
Tests: 8, Assertions: 23, Failures: 2.
<?php
class AlphaTest extends TestCase
{
public function testDisplaysAlpha()
{
$this->visit('/alpha')
->see('Alpha')
->dontSee('Beta page');
}
public function testClickNextForBeta()
{
$this->visit('/alpha')
->click('Next')
->seePageIs('/beta');
}
}
<?php
class BetaTest extends TestCase
{
public function testDisplaysBeta()
{
$this->visit('/beta')
->see('Beta')
->dontSee('Alpha page');
}
public function testClickNextForAlpha()
{
$this->visit('/beta')
->click('Previous')
->seePageIs('/alpha');
}
}
再一次运行你的测试,所有的测试都应该通过了。我们现在已经测试我们所有的新文件,包括页面中的 Next/Previous 链接。
通过 Semaphore 对 PHPUnit 持续集成
通过 Semaphore 设置 持续集成你可以自动执行你的测试。
这样每一次你进行 git push 提交代码的时候都会执行你的测试,并且 Semaphore 预装了所有最新的 PHP 版本。
如果你还没有一个 Semaphore 账户, 先去 注册一个免费的 Semaphore 账户 。接下来需要做的是将它 添加到你的项目,并按照提示逐步去做来执行你的测试:
composer install --prefer-source
phpunit
关于 PHP 持续集成 的更多信息,请参照 Semaphore 文档。
结语
你应该注意到本教程中的所有测试都有一个共同的主题:它们都非常简单。 这是学习如何使用基本的测试断言和辅助函数,并且尽可能的使用它们的好处之一。编写测试越简单,测试就越容易理解和维护。
掌握了本教程中介绍的 PHPUnit 断言之后,你还可以去 PHPUnit 文档 找到更多内容。 所有的断言都遵循基本的模式,但你会发现,在大多数测试中都会返回基本的断言。
对于 PHPUnit 断言来说,Laravel 的测试辅助函数是极好的补充,这让应用程序的测试变的非常容易。也就是说,重要的是要认识到,对于我们写测试,我们只检查关键信息,而不是整个页面。这使得测试变得简单,并允许页面内容随着应用程序的变化而变化。如果关键信息仍然存在,测试仍然通过,每个人都会满意。
以上がLaravel単体テストPHPUnitの基本的な使い方の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。