使用 Codeception 快速启动您的 PHP 测试
已发表: 2022-03-11在继续讨论 Codeception 和 PHP 之前,我们应该先了解基础知识,并首先解释为什么我们需要在应用程序中进行测试。 也许我们可以在不浪费时间进行测试的情况下完成一个项目,至少这次是这样?
当然,您不需要对所有内容进行测试。 例如,当您想构建另一个主页时。 当您的项目包含由一个路由器链接的静态页面时,您可能不需要测试。
但是,您肯定需要在以下情况下进行测试:
- 您的团队使用 BDD/TDD。
- 您的 Git 存储库包含多个提交。
- 你是一个合格的专业人士,正在从事一个严肃的项目。
你可以说你已经有一个专门的测试部门,一组进行测试并在需要时编写新测试的人来为自己辩解。 但是,您能想象在您为项目添加新功能后需要多长时间修复错误吗?
测试能解决什么问题?
首先,让我们决定通过测试可以解决什么样的问题。 您无法通过测试摆脱所有错误,但您可以描述测试用例中的预期行为。 错误可能在您的测试用例中。 即使您使用测试用例,意大利面条代码仍然是意大利面条代码。
但是,您可以确定您的代码会在之后更改(通过修复错误或添加新功能),因此您的代码仍然不会出现测试中描述的错误。 此外,有时甚至可以在文档中使用编写良好的测试,因为在那里您可以看到典型场景如何展开并检查预期行为。 可以说,测试是对未来的一项小而重要的投资。
那么我们可以采用什么样的测试呢?
- 单元测试:检查一小部分代码的低级测试——你的类的方法与其他代码隔离。
- 集成测试:集成测试检查应用程序的一部分,它们可能包含多个类或方法,但应仅限于一个功能。 该测试还应检查不同类的交互方式。
- 功能测试:测试对您的应用程序的特定请求:浏览器响应、数据库更改等。
- 验收测试:在大多数情况下,验收测试意味着检查应用程序是否满足所有客户要求。
为了澄清,假设我们用有形的东西来说明这个过程,比如建筑物。 建筑物由形成墙壁的小块组成。 每块砖都必须符合规定的要求; 它必须承受所需的负载,具有特定的体积和形状,等等。 这些是单元测试。 集成测试的想法是检查砖块之间的紧密度和准确性,以及它们如何集成到建筑物的某个元素中。 功能测试可以比作在建筑物的一面墙上进行测试,检查内部是否受到保护,以及是否可以通过窗户看到阳光。 验收测试涉及将整个建筑作为一个完整的产品进行测试:打开门,进去,关上门,开灯,爬到二楼,看看楼外的花园。
认识 Codeception
然而,这种划分是有条件的,有时很难抵制混合不同类型测试的诱惑。
许多开发人员使用单元测试并声称这就足够了。 我曾经是这样的开发者之一; 我发现对不同类型的测试使用不同的系统既困难又费时。 前段时间,我决定找一些比 PHPUnit 更有用的东西; 我想更好地测试我的代码,但我不想阅读和学习大量文档并寻找陷阱。 这就是我发现 Codeception 的方式。 起初,我很怀疑,就像我们经常遇到新事物一样(这个项目已有五年历史,所以从技术上讲,它不能被认为是“新的”),但在玩了几天之后,我总结 Codeception 是一个非常有用和强大的系统。
那么如何安装 Codeception? 它很简单:
$ composer require "codeception/codeception" $ php vendor/bin/codecept bootstrap
安装完成后,你会在你的项目中找到一个名为tests的新文件夹,其中会有一些名为accept 、functional和unit的子文件夹。 看起来我们可以开始编写测试了。 很酷,但下一步是什么?
现在,尝试添加一个标准验收Hello World 测试。
$ php vendor/bin/codecept generate:cept acceptance HelloWorld
现在,我们得到一个验收测试文件tests/acceptance/HelloWorldCept.php,内容如下:
<?php $I = new AcceptanceTester($scenario); $I->wantTo('perform actions and see result');
名为$I
的默认变量不仅仅是一个字母; 它是一个字符。 什么进行测试? 测试员,很明显。 该测试人员打开您网站的页面或课程,对其进行处理,并向您展示其操作的最终结果。 你会看到什么有效,什么出了问题。 这就是为什么这个对象被命名为$I
以及为什么它包含名为wantTo()
、 see()
或amOnPage()
的方法。
因此,让我们像测试人员一样思考检查页面可操作性的方法。 第一种方法是打开页面并搜索短语。 它证明该页面可供访问者使用。
这应该很容易:
<?php $I->amOnPage('/'); $I->see('Welcome');
我们可以使用这个命令来运行 Codeception 的测试:
$ php vendor/bin/codecept run
我们立即看到有什么地方不对劲。 乍一看,信息似乎太长且不清楚,但当我们仔细观察时,一切都变得显而易见。
我们有一个测试Acceptance ,它检测到一个错误:
Acceptance Tests (1) Perform actions and see result (HelloWorldCept) Error ---------- 1) Failed to perform actions and see result in HelloWorldCept (tests/acceptance/HelloWorldCept .php) [GuzzleHttp\Exception\ConnectException] cURL error 6: Could not resolve host: localhost (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)
这是罪魁祸首: localhost不可用。
以下是我们测试的场景步骤:
1. $I->amOnPage("/")
好的,让我们打开 tests/acceptance.suite.yml 并将url: http://localhost/
更改为实际可用的内容。 就我而言,它是我的本地测试主机, url: https://local.codeception-article.com/
再次运行测试,这就是你应该得到的结果:
Acceptance Tests (1) --------------------------------------------------------------------------------------- Perform actions and result (HelloWorldCept) Ok
万岁! 我们的第一次成功测试!
当然, amOnPage()
不是唯一可用的测试方法,我们只是将其单独用于我们的示例。 所有 Codeception 测试方法都可以分为以下几组:
- 与页面交互:
fillField()
、selectOption()
、submitForm()
、click()
- 断言。
see()
,dontSee()
,seeElement()
,seeInCurrentUrl()
,seeCheckboxIsChecked()
,seeInField()
,seeLink()
。 对于所有这些方法,您可以添加一个后缀,并在您需要一种在找不到某些东西时不会中断测试场景的方法时使用它。 - Cookie 方法:
setCookie()
、grabCookie()
、seeCookie()
- 测试场景的评论和描述:
amGoingTo()
,wantTo()
,expect()
。 使用这些方法来获得很好的评论和描述的测试,这将帮助您记住测试的目标。
因此,如果我们要测试密码重置电子邮件页面,我们可以这样做:
<?php $I = new AcceptanceTester($scenario); $I->wantTo('Test forgotten password functionality'); $I->amOnPage('/forgotten') $I->see('Enter email'); $I->fillField('email', '[email protected]'); $I->click('Continue'); $I->expect('Reset password link not sent for incorrect email'); $I->see('Email is incorrect, try again'); $I->amGoingTo('Fill correct email and get link'); $I->see('Enter email'); $I->fillField('email', '[email protected]'); $I->click('Continue'); $I->expect('Reset password link sent for correct email'); $I->see('Please check your email for next instructions');
看起来应该这样做,但是如果页面上有一些 Ajax 加载的部分怎么办? 我们可以测试这样的页面吗? 答案是 Codeception 默认使用基于 Symfony BrowserKit 和 Guzzle 的 PhpBrowser。 它简单、快速,您只需要 curl 即可使用它。

您还可以使用 Selenium 并在真实浏览器中测试页面。 是的,它会更慢,但您也可以测试 JavaScript。
首先,您需要安装 Selenium 驱动程序,更改 Acceptance.suite.yml 并重建 AcceptanceTester 类。 在此之后,您可以使用方法wait()
和waitForElement()
。 而且,更有趣的是,您将能够通过使用方法saveSessionSnapshot()
和loadSessionSnapshot()
来节省时间和资源。 此方法允许您存储会话状态并在较早的会话中开始新的测试。 这在某些情况下很有用,例如在测试授权过程中。
因此,我们最终获得了一种简单而强大的测试许多功能的能力。
功能测试
好的,是时候进行功能测试了。
$ php vendor/bin/codecept generate:cept functional HelloWorld
这就是我们得到的:
<?php $I = new FunctionalTester($scenario); $I->amOnPage('/'); $I->see('Welcome');
等等,什么?
不,这不是一个错误。 功能测试应该以与集成测试相同的方式编写。 不同之处在于功能测试直接与您的应用程序交互。 这意味着您不需要网络服务器来运行功能测试,并且您有更多的能力来测试应用程序的不同部分。
这确实意味着缺乏对所有框架的支持,但支持的框架列表非常广泛:Symfony、Silex、Phalcon、Yii、Zend Framework、Lumen、Laravel。 对于大多数情况和大多数开发人员来说,这应该足够了。 请查阅 Codeception 的模块文档以获取可用功能的列表,然后在functional.suite.yml
中将其打开。
在我们进行单元测试之前,请允许我做一点题外话。 您可能已经注意到,我们使用关键 cept 创建了测试:
$ php vendor/bin/codecept generate: cept acceptance HelloWorld
这不是创建测试的唯一方法。 还有cest 测试。 不同之处在于您可以在一个类中构建多个相关场景:
$ php vendor/bin/codecept generate:cest acceptance HelloWorld <?php class HelloWorldCest { public function _before(AcceptanceTester $I) { $I->amOnPage('/forgotten') } public function _after(AcceptanceTester $I) { } // tests public function testEmailField(AcceptanceTester $I) { $I->see('Enter email'); } public function testIncorrectEmail(AcceptanceTester $I) { $I->fillField('email', '[email protected]'); $I->click('Continue'); $I->see('Email is incorrect, try again'); } public function testCorrectEmail(AcceptanceTester $I) { $I->fillField('email', '[email protected]'); $I->click('Continue'); $I->see('Please check your email for next instructions'); } }
在此示例中,方法_before()
和_after()
在每次测试之前和之后运行。 AcceptanceTester
类的实例被传递给每个测试,因此您可以像在 cest 测试中一样使用它。 这种类型的测试在某些情况下可能很有用,因此值得牢记。
单元测试
是时候进行一些单元测试了。
Codeception 基于 PHPUnit,因此您可以使用为 PHPUnit 编写的测试。 要添加新的 PHPUnit 测试,请使用以下方法:
$ php vendor/bin/codecept generate:phpunit unit HelloWorld
或者只是继承您在\PHPUnit_Framework_TestCase
上的测试。
但如果你想要更多,你应该试试 Codeception 的单元测试:
$ php vendor/bin/codecept generate:test unit HelloWorld <?php class HelloWorldTest extends \Codeception\TestCase\Test { /** * @var \UnitTester */ protected $tester; protected function _before() { } protected function _after() { } // tests public function testUserSave() { $user = User::find(1); $user->setEmail('[email protected]'); $user->save(); $user = User::find(1); $this->assertEquals('[email protected]', $user->getEmail()); } }
目前没有什么不寻常的。 _before()
和_after()
方法是setUp()
和tearDown()
的类似方法,将在每次测试之前和之后运行。
该测试的主要优势在于它能够通过包含可以在unit.suite.yml
中打开的模块来扩展您的测试过程:
- 访问 memcache 和数据库以跟踪更改(支持 MySQL、SQLite、PostgreSQL、MongoDB)
- 测试 REST/SOAP 应用程序
- 队列
每个模块都有自己的功能,因此最好在进行实际测试之前检查文档并收集每个模块的必要信息。
另外,您可以使用 Codeception/Specify 包(需要将其添加到composer.json
)并编写如下描述:
<?php class HelloWorldTest extends \Codeception\TestCase\Test { use \Codeception\Specify; private $user; protected function _before() { $this->user = User::find(1); } public function testUserEmailSave() { $this->specify("email can be stored", function() { $this->user->setEmail('[email protected]'); $this->user->save(); $user = User::find(1); $this->assertEquals('[email protected]', $user->getEmail()); }); } }
这些闭包函数内部的 PHP 代码是隔离的,因此内部的更改不会影响您的其余代码。 描述将帮助您使测试更具可读性,并更容易识别失败的测试。
作为可选的附加功能,您可以使用包Codeception\Verify
来实现类似 BDD 的语法:
<?php public function testUserEmailSave() { verify($map->getEmail())->equals('[email protected]'); }
当然,您可以使用存根:
<?php public function testUserEmailSave() { $user = Stub::make('User', ['getEmail' => '[email protected]']); $this->assertEquals('[email protected]', $user->getEmail()); }
结论:Codeception 节省时间和精力
那么你应该对 Codeception 有什么期待呢? 它是给谁的? 有什么注意事项吗?
在我看来,这个测试框架适合各种不同的团队:大大小小的、初学者和久经沙场的 PHP 专业人士、使用流行框架的人以及不使用任何框架的人。
无论如何,这一切都归结为: Codeception 已准备好迎接黄金时段。
它是一个成熟且文档齐全的框架,可以很容易地被许多模块扩展。 Codeception 是现代的,但基于经过时间考验的 PHPUnit,这应该可以让不想进行过多实验的开发人员放心。
它表现良好,这意味着它速度很快,不需要太多时间和精力。 更好的是,它相对容易掌握,丰富的文档应该有助于轻松的学习过程。
Codeception 也易于安装和配置,但它拥有许多高级选项。 虽然大多数用户不需要全部(或者实际上是大多数),但这完全取决于您打算用它做什么。 您可以从基础开始,额外的功能迟早会派上用场。