介绍 PHPUnit 测试的基础知识,使用基本的 PHPUnit 断言和 Laravel 测试助手。

介绍

PHPUnit 是最古老和最著名的 PHP 单元测试包之一。它主要用于单元测试,这意味着可以用尽可能小的组件测试代码,但是它也非常灵活,可以用于很多不仅仅是单元测试。

PHPUnit 包含许多简单和灵活的断言允许您轻松地测试代码,当您测试特定的组件时,这些断言非常有效。但是,它确实意味着测试更高级的代码(如控制器和表单提交验证)可能会复杂得多。

为了帮助开发人员更容易地进行开发, Laravel 框架  包含了一系列 应用程序测试帮助程序 ,允许您编写非常简单的 PHPUnit 测试来测试应用程序的复杂部分。

本教程的目的是向您介绍 PHPUnit 测试的基础知识,使用默认 PHPUnit 断言和 Laravel 测试助手。这样做的目的是在本教程结束时,您可以自信地为应用程序编写基本测试。

前提

本教程假设您已经熟悉 Laravel 并知道如何在应用程序目录中运行命令(例如 php artisan 命令)。我们将创建几个基本的示例类来学习不同的测试工具如何工作,因此建议您为本教程创建一个新的应用程序。

如果已经安装了 Laravel ,则可以通过运行以下命令创建新的测试应用程序:

laravel new phpunit-tests复制代码

或者,您可以直接使用 Composer 创建新应用程序:

composer create-project laravel/laravel --prefer-dist复制代码

其他安装方法也可以在 Laravel 文档中找到。

创建一个新的测试

使用 PHPUnit 的第一步是创建一个新的测试类。测试类的约定是它们存储在应用程序目录的 ./tests/ 下。在这个文件夹中,每个测试类都被命名为 <name>Test.php 。这种格式允许 PHPUnit 查找每个测试类---它将忽略任何不以 Test.php 结尾的文件。

在新的 Laravel 应用程序中,你会注意到 ./tests/ 目录中有两个文件:  ExampleTest.php 和 TestCase.php.  TestCase.php 文件是一个引导文件用于在我们的测试中设置 Laravel 环境。这允许我们在测试中使用 Laravel Facades 并为测试助手提供框架,我们将在稍后介绍。 ExampleTest.php 是一个示例测试类,其中包含使用应用程序测试助手的基本测试用例-暂时忽略它。

要创建一个新的测试类,我们可以手动创建一个新文件,或者运行由 Laravel 提供的 Artisan 命令 make:test

为了创建一个名为 BasicTest 的测试类,我们只需要运行这个 artisan 命令:

php artisan make:test BasicTest复制代码

Laravel 将创建一个如下所示的基本测试类:

<?php
class BasicTest extends TestCase
{/*** 一个基本的测试示例。** @return void*/public function testExample(){$this->assertTrue(true);}
}复制代码

这里要注意的最重要的事情是 test 方法名称上的前缀,与 Test 类名后缀一样,这样 test 前缀告诉 PHPUnit 在测试时运行哪些方法。如果您忘记了 test 前缀,那么 PHPUnit 将忽略该方法。

在我们第一次运行测试套件之前,有必要指出 Laravel 提供的默认 phpunit.xml 文件。 PHPUnit 在运行时会自动在当前目录中查找名为 phpunit.xml 或者 phpunit.xml.dist 的文件。您可以在此处配置测试的特定选项。

这个文件中有很多信息,但是现在最重要的部分是在 testsuite 目录定义:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit ... ><testsuites><testsuite name="Application Test Suite"><directory>./tests/</directory></testsuite></testsuites>...
</phpunit>复制代码

这将告诉 PHPUnit 运行时在 ./tests/ 目录中找到的测试,正如我们之前所知,这是存储测试的约定。

现在我们已经创建了一个基本测试,并且知道了 PHPUnit 配置,现在是第一次运行测试的时候了。

您可以通过运行以下 phpunit 命令来运行测试:

./vendor/bin/phpunit复制代码

您应该看到与此类似的输出:

PHPUnit 4.8.19 by Sebastian Bergmann and contributors...Time: 103 ms, Memory: 12.75MbOK (2 tests, 3 assertions)复制代码

现在我们已经有了一个有效的 PHPUnit 设置,现在是时候开始编写一个基本测试了。

注意,它会统计2个测试和3个断言,因为 ExampleTest.php 文件包含了一个带有两个断言的测试。我们的新基本测试包括一个单独的断言,该断言已通过。

写一个基础测试

为了帮助 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;});}
}复制代码

接下来, 打开你的 ./tests/BasicTest.php 类(我们之前创建的类),并删除默认创建的 testExample 方法, 你应该留一个空类。

我们现在将使用七个基本的 PHPUnit 断言来为我们的 Box 类编写测试。这些断言是:

  • assertTrue()
  • assertFalse()
  • assertEquals()
  • assertNull()
  • assertContains()
  • assertCount()
  • assertEmpty()

assertTrue() 和 assertFalse()

assertTrue() 和 assertFalse() 允许你声明一个值等于 true 或 false 。这意味着它们非常适合测试返回布尔值的方法。在我们的 Box 类中,我们有一个名为 has($item) 的方法,当指定的项在 box 中或不在 box 中时,该方法返回对应返回 true 或 false .

要在 PHPUnit 中为此编写测试,我们可以执行以下操作:

<?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'));}
}复制代码

注意我们如何只将一个参数传递给 assertTrue() 和 assertFalse() 方法,并且它是 has($item) 方法的输入.

如果您现在运行 ./vendor/bin/phpunit 命令,您会注意到输出包括:

OK (2 tests, 4 assertions)复制代码

这意味着我们的测试已经通过。

如果您将 assertFalse() 替换成 assertTrue() 并运行 phpunit 命令,输出将如下所示:

PHPUnit 4.8.19 by Sebastian Bergmann and contributors.F.Time: 93 ms, Memory: 13.00MbThere was 1 failure:1) BasicTest::testHasItemInBox
Failed asserting that false is true../tests/BasicTest.php:12FAILURES!
Tests: 2, Assertions: 4, Failures: 1.复制代码

这告诉我们第12行的断言未能断言 false 值是 true - 因为我们将 assertFalse() 替换为 assertTrue()

将其交换回来,然后重新运行 PHPUnit 。测试应该再次通过,因为我们已经修复了破损的测试。

assertEquals() 与 assertNull()

接下来,让我们看看 assertEquals(), 以及 assertNull()

assertEquals() 用于比较变量实际值与预期值是否相等。我们用它来检查 takeOne() 方法的返回值是否为 Box 内的当前值。当 Box 为空时,takeOne() 将返回 null,我们亦可使用 assertNull() 来进行检查。

与 assertTrue()assertFalse() 以及 assertNull() 不同,assertEquals() 需要两个参数。第一个参数为 预期 值,第二个参数则为 实际 值。

可参照如下代码实现以上断言(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());// 当前 Box 为空,应当为 Null$this->assertNull($box->takeOne());}
}复制代码

运行 phpunit 命令,你应当看到如下输出:

OK (3 tests, 6 assertions)复制代码

assertContains() 和 assertCount() 以及 assertEmpty()

终于,我们有三个作用于数组有关的断言,我们能够使用它们去检查 Box 类中的  startsWith($item) 方法。 assertContains() 断言传递进来的数组中包含指定值, assertCount() 断言数组的项数为指定数量,assertEmpty() 断言传递进来的数组为空。

让我们来执行以下测试:

<?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)复制代码

恭喜你,你刚刚使用七个基础的 PHPUnit 断言完成了对 Box 类的全部测试。通过这些简单的断言你能够做许多事,对于其他断言,大多数要更复杂,不过它们仍遵循以上使用规则。

测试你的程序

在你的程序里,对每个组件进行单元测试在很多情况下都是有必要的,而且也应该成为你开发过程中必不可少的一部分,但这并不是你需要做的全部的测试。当你构建一个包含复杂视图、导航和表单的程序时,你同样想测试这些组件。这时,Laravel的测试助手可以使这些测试像单元测试简单组件一样容易。

我们之前查看在 ./tests/ 目录下的默认文件时跳过了 ./tests/ExampleTest.php 文件。 现在打开它,内容如下所示:

<?php
class ExampleTest extends TestCase
{/**
* 一个基本功能测试示例。
*
* @return void
*/public function testBasicExample(){$this->visit('/')->see('Laravel 5');}
}复制代码

我们可以看到这个测试示例非常简单。在不知道测试助手如何运作的情况下,我们可以猜测它的意思如下:

  1. 当我访问/ (根目录)
  2. 我应该看到 'Laravel 5'

如果你打开你的web浏览器,访问我们的程序(如果你没有启动你的web服务器,你可以运行 php artisan serve ),你应该可以在web根目录上看到屏幕上有“Laravel 5”的文本。 鉴于这个测试已经通过了PHPUnit,我们可以很确定地说我们对这个测试示例改造是正确的。

这个测试确保了访问/路径,网页可以返回“'Laravel 5”的文本。一个如此简单的检查也许不代表什么,但如果你的网站上要显示关键信息,它就可以在一个别处的改动导致这个页面无法正常显示正确的信息时,防止你部署一个被损坏的程序。

visit()、see() 以及 dontSee()

现在尝试编写自己的测试,更进一步理解它吧。

首先,编辑 ./app/Http/routes.php ,增加一个新的路由。为了教程目的,我们创建希腊字母定义的路由:

<?php
Route::get('/',function () {return view('welcome');
});Route::get('/alpha',function () {return view('alpha');
});复制代码

然后,创建视图文件 ./resources/views/alpha.blade.php,使用 Alpha 作为关键字,保存基本的HTML文件:

<!DOCTYPE html>
<html><head><title>Alpha</title></head><body><p>This is the Alpha page.</p></body>
</html>复制代码

打开浏览器,输入网址: http://localhost:8000/beta,页面会显示出 "This is the Alpha page." 的内容。

现在我们有了测试用到的模版文件,下一步,我们通过运行命令 make:test 来创建一个新的测试文件:

php artisan make:test AlphaTest复制代码

然后变成刚创建好的测试文件,按照框架提供的例子,测试 "alpha" 页面上没有包含 "beta" 。 我们可以使用方法 dontSee() ,它是 see() 的对应的反向方法。

下面代码是上面实现的简单例子:

<?php
class AlphaTest extends TestCase
{public function testDisplaysAlpha(){$this->visit('/alpha')->see('Alpha')->dontSee('Beta');}
}复制代码

保存并运行 PHPUnit (./vendor/bin/phpunit),测试代码应该会全部通过,你会看到像这样的测试状态内容显示:

OK (5 tests,12 assertions)复制代码

开发前先写测试

对于测试来说,测试驱动开发 (TDD) 是非常酷的方法,首先我们先写测试。写完测试并执行它们,你会发现测试没通过,接下来 我们编写满足测试的代码,再次执行测试,使测试通过。 接下来让我们开始。

首先,建立一个 BetaTest 类使用 make:test artisan 命令:

php artisan make:test BetaTest复制代码

接下来,更新测试用例以便检查 /beta 的路由 route 为「Beta」:

<?php
class BetaTest extends TestCase
{public function testDisplaysBeta(){$this->visit('/beta')->see('Beta')->dontSee('Alpha');}
}复制代码

现在使用 ./vendor/bin/phpunit 命令来执行测试。结果是一个看起来简洁但不好的错误信息,如下:

> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.....F.Time: 144 ms, Memory: 14.25MbThere was 1 failure:1) BetaTest::testDisplaysBeta
一个对 [http://localhost/beta] 的请求失败了。收到状态码 [404]。...FAILURES!
Tests: 6, Assertions: 13, Failures: 1.复制代码

我们现在需要创建这个不存在的路由。让我们开始。

首先,编辑 ./app/Http/routes.php 文件来创建新的 /beta 路由:

<?php
Route::get('/', function () {return view('welcome');
});Route::get('/alpha', function () {return view('alpha');
});Route::get('/beta', function () {return view('beta');
});复制代码

接下来,在 ./resources/views/beta.blade.php 下创建如下视图模版:

<!DOCTYPE html>
<html><head><title>Beta</title></head><body><p>This is the Beta page.</p></body>
</html>复制代码

现在再一次执行 PHPUnit,结果应该再一次回到绿色。

> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.......Time: 142 ms, Memory: 14.00MbOK (6 tests, 15 assertions)复制代码

这样我们就通过在完成新的页面之前写测试的方式,对 测试驱动开发 进行了实践。

click() 和 seePageIs()

Laravel 也提供一个辅助函数 (click()) 允许测试点击页面中存在的连接 ,以及一个方法 (seePageIs()) 检查点击展示的结果页面。

让我们使用这两个辅助函数去执行在 Alpha 和 Beta 页面的链接。

首先,我们更新我们的测试。打开 AlphaTest 类,我们将添加一个新的测试方法,这将点击 「alpha」页面上的「Next」链接跳转到 「beta」页面。

新的测试代码如下:

<?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');}
}复制代码

注意到,在我们新建的 testClickNextForBeta() 方法中,我们并没有检查每一个页面的内容。 其他测试都成功的检查了两个页面的内容,所以这里我们只关心点击 「Next」链接将发送到 /beta

你现在可以运行测试组件了,但就像预料的一样测试将不通过,因为我们还没有更新我们的 HTML。

接下来,我们将更新 BetaTest 来做类似的事情:

<?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');}
}复制代码

接下来,我们更新我们的 HTML 模版。

./resources/views/alpha.blade.php

<!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>复制代码

保存文件,再一次执行 PHPUnit:

> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.F....F..Time: 175 ms, Memory: 14.00MbThere 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.复制代码

然而测试失败了。如果你仔细观察我们的新 HTML,你将注意到我们分别有术语 beta 和 alpha 在 /alpha 和 /beta 页面。这意味着我们需要稍微更改我们的测试让它们与误报不匹配。

在每一个 AlphaTest 和 BetaTest 类,更新 testDisplays* 方法去使用 dontSee('<page> page')。通过这种方式,这将仅仅匹配字符串而不是那个术语。

两个测试文件如下所示:

./tests/AlphaTest.php

<?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');}
}复制代码

./tests/BetaTest.php

<?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 的测试辅助函数是极好的补充,这让应用程序的测试变的非常容易。也就是说,重要的是要认识到,对于我们写测试,我们只检查关键信息,而不是整个页面。这使得测试变得简单,并允许页面内容随着应用程序的变化而变化。如果关键信息仍然存在,测试仍然通过,每个人都会满意。

文章转自: https://learnku.com/laravel/t/24559
更多文章:https://learnku.com/laravel/c/translations

Laravel 测试: PHPUnit 入门教程相关推荐

  1. php laravel 入门教程,Laravel 5 系列入门教程(一)【最适合中国人的 Laravel 教程】...

    Laravel 5 系列入门教程(一)[最适合中国人的 Laravel 教程] 2015-3-7 / 阅读数:314392 / 分类: Laravel 十分建议学习 5.5,跟 5.0 比变化非常大. ...

  2. Laravel框架快速入门教程

    项目介绍 一款 PHP 语言基于 Laravel5.8.Layui.MySQL等框架精心打造的一款模块化.插件化.高性能的前后端分离架构敏捷开发框架,可用于快速搭建前后端分离后台管理系统,本着简化开发 ...

  3. Cypress 前端 E2E 测试——手把手入门教程

    目录 初始化项目 准备好 Docker 镜像 安装依赖 ESLint 配置 IDE 集成 配置 手动配置 Gitlab 集成 测试用例 配置环境变量 静态变量 动态变量 使用变量 添加 Command ...

  4. php laravel 入门教程,PHP语言菜鸟笔记,laravel 入门教程

    本文主要向大家介绍了PHP语言菜鸟笔记,laravel 入门教程,通过具体的内容向大家展示,希望对大家学习php语言有所帮助. 安装 Laravel: 查看laravel包: composer sea ...

  5. php laravel 入门教程,Laravel Lighthouse-php 入门篇

    这是一份使用 Lighthouse 构建 GraphQL 服务器的入门教程. 虽然我们试图保持初学者的友好,但我们建议您熟悉一下 GraphQL 和 Laravel. GraphQL 是什么? Gra ...

  6. Android渗透测试Android渗透测试入门教程大学霸

    Android渗透测试Android渗透测试入门教程大学霸 第1章  Android渗透测试 Android是一种基于Linux的自由及开放源代码的操作系统,主要用于移动设备,如智能手机.平板等.目前 ...

  7. 2016 版 Laravel 系列入门教程(一)

    https://www.golaravel.com/post/2016-ban-laravel-xi-lie-ru-men-jiao-cheng-yi/ 2016 版 Laravel 系列入门教程(一 ...

  8. php入门公开课,【PHP公开课|送你一篇有关laravel入门教程的php菜鸟笔记】- 环球网校...

    [摘要]PHP作为一种超文本预处理器,已经成为了我们常用的网站编程语言,并且结合了C语言,Java等我们常见的编程语言,所以,有很多web开发领域的新人都看中了他的使用广泛性,有很多人都想了解php的 ...

  9. laravel入门教程

    laravel 入门教程 laravel 简介 laravel['lærəvel] : 读作 拉瑞涡. laravel 教程 laravel 简介及资料 laravel 安装,配置,以及部署 lara ...

最新文章

  1. 解决网通英文wiki无法显示图片问题【20100723更新】
  2. oracle一行变一列,oracle多行变一行
  3. java死锁以及解决方案
  4. linux mysql数据库定时备份
  5. AI技术在空气净化机器人中的高能应用
  6. Oracle从零开始1——SQLplus
  7. python sys模块详解_python中os和sys模块的区别与常用方法总结
  8. 开发基于GUI的R包(2)
  9. 基于核极限学习机(KELM)回归预测 -附代码
  10. C++虚复制构造函数,设置Clone()方法返回基类指针,并设置为虚函数
  11. C++读写txt文件
  12. java计算机毕业设计高考填报信息系统源码+数据库+系统+lw文档+部署
  13. 今日头条信息流 - 广告策略
  14. openwrt dnsmasq rebind_protection域名劫持保护
  15. 性能测试分析之应用程序减少TCP压力
  16. excel打不开怎么办_第52期分享:Excel大佬有哪些骚操作呢?
  17. Windows触控手势
  18. 义乌市python培训
  19. bootstrap侧面菜单栏
  20. asps英文_常用汽车词汇的英文简称都有哪些比如AT、MT、AMT、BATT的这些?谁给列个清单?...

热门文章

  1. oracle xml中cdata,XML CDATA的作用
  2. 将给定数据源生成静态HTML页面持久化到项目之外的硬盘
  3. Invalid argument: Key: label. Data types don't match. Data type: int64 but expected type: float
  4. 洛谷P1420 最长连号
  5. 利用photoshop制作gif图片
  6. mysql+mybatis递归调用
  7. java.lang.OutOfMemoryError: GC overhead limit exceeded
  8. linux查看文件大小
  9. 修复 XE7 , XE8 Frame 内 PopupMenu 快捷键失效问题
  10. 鸟哥学习笔记六(基础篇第十一章)