第 13 章 PHPUnit 与 Selenium

Selenium Server

Selenium Server 是一个测试工具,允许你用任意主流浏览器为任意 HTTP 网站上的用任意编程语言开发的 web 应用程序编写自动用户界面测试。它通过操作系统来驱动浏览器进程来执行自动测试。Selenium 测试直接运行于某个浏览器中,就和真实用户一样。这些测试既可以用于验收测试(通过在集成好的系统中执行较高层面的测试而非仅对系统的各个单元分别单独测试。)也可以用于浏览器兼容性测试(通过在不同的操作系统与浏览器上对 web 应用程序进行测试)。

PHPUnit_Selenium 只支持 Selenium 2.x 服务器的脚本。服务器可以通过从 1.x 就提供的传统 Selenium RC API 访问,也可以从 PHPUnit_Selenium 1.2 用 WebDriver API(部分实现)访问。

这个决定的原因是 Selenium 2 是向后兼容的,而 Selenium RC 已经不再维护了。

安装

首先,安装 Selenium Server:

  1. 下载 Selenium Server的分发档。
  2. 将分发档解压,然后将 selenium-server-standalone-2.9.0.jar(注意版本后缀) 复制到比如说 /usr/local/bin
  3. 运行 java -jar /usr/local/bin/selenium-server-standalone-2.9.0.jar 来启动 Selenium Server 服务器端。

PHPUnit 的 PHAR 分发中已经包含了 PHPUnit_Selenium 组件包。若要通过 Composer 安装此组件包,添加如下 "require-dev" 依赖项:

"phpunit/phpunit-selenium": ">=1.2"

现在可以用 Selenium Server 的客户端/服务器端协议来向它发送命令了。

PHPUnit_Extensions_Selenium2TestCase

PHPUnit_Extensions_Selenium2TestCase 测试用例让你能够使用 WebDriver API(部分实现)。

例 13.1展示了如何测试 http://www.example.com/ 网站的 <title> 元素的内容。

例 13.1: PHPUnit_Extensions_Selenium2TestCase 的用法范例

<?php
class WebTest extends PHPUnit_Extensions_Selenium2TestCase
{
    protected function setUp()
    {
        $this->setBrowser('firefox');
        $this->setBrowserUrl('http://www.example.com/');
    }

    public function testTitle()
    {
        $this->url('http://www.example.com/');
        $this->assertEquals('Example WWW Page', $this->title());
    }

}
?>
phpunit WebTest
PHPUnit 4.2.0 by Sebastian Bergmann.

F

Time: 28 seconds, Memory: 3.00Mb

There was 1 failure:

1) WebTest::testTitle
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'Example WWW Page'
+'IANA — Example domains'

/home/giorgio/WebTest.php:13

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.


Selenium2TestCare 的命令是通过 __call() 来实现的。请参考 the end-to-end test for PHPUnit_Extensions_Selenium2TestCase 以获取所有受支持的特性的列表。

PHPUnit_Extensions_SeleniumTestCase

PHPUnit_Extensions_SeleniumTestCase 测试用例扩展实现了客户端/服务器端协议来与 Selenium Server 沟通,同时还为 web 测试实现了一些特殊的断言方法。

例 13.2展示了如何测试 http://www.example.com/ 网站的 <title> 元素的内容。

例 13.2: PHPUnit_Extensions_SeleniumTestCase 的用法范例

<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class WebTest extends PHPUnit_Extensions_SeleniumTestCase
{
    protected function setUp()
    {
        $this->setBrowser('*firefox');
        $this->setBrowserUrl('http://www.example.com/');
    }

    public function testTitle()
    {
        $this->open('http://www.example.com/');
        $this->assertTitle('Example WWW Page');
    }
}
?>
phpunit WebTest
PHPUnit 4.2.0 by Sebastian Bergmann.

F

Time: 9 seconds, Memory: 6.00Mb

There was 1 failure:

1) WebTest::testTitle
Current URL: http://www.iana.org/domains/example/

Failed asserting that 'IANA — Example domains' matches PCRE pattern "/Example WWW Page/".


FAILURES!
Tests: 1, Assertions: 1, Failures: 1.


PHPUnit_Framework_TestCase 类不同,扩展自 PHPUnit_Extensions_SeleniumTestCase 的测试用例类必须提供 setUp() 方法。这个方法用来配置 Selenium Server 会话。表 13.1中列出了所有用于这方面的方法。

表 13.1. Selenium Server API: 建立

方法含义
void setBrowser(string $browser)设置用于 Selenium Server 服务器的浏览器。
void setBrowserUrl(string $browserUrl)设置测试的基准 URL (base URL)。
void setHost(string $host)设定 Selenium Server 服务器连接的主机名。
void setPort(int $port)设定 Selenium Server 服务器连接的端口号。
void setTimeout(int $timeout)设定 Selenium Server 服务器连接的超时时间。
void setSleep(int $seconds)设定 Selenium Server 客户端向 Selenium Server 服务器端发送动作多个命令之间需要休眠的秒数


PHPUnit 还可以在 Selenium 测试失败时截屏。要启用这个功能,在测试用例类里设置 $captureScreenshotOnFailure$screenshotPath$screenshotUrl,如 例 13.3中所示。

例 13.3: 当测试失败时截屏

<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class WebTest extends PHPUnit_Extensions_SeleniumTestCase
{
    protected $captureScreenshotOnFailure = TRUE;
    protected $screenshotPath = '/var/www/localhost/htdocs/screenshots';
    protected $screenshotUrl = 'http://localhost/screenshots';

    protected function setUp()
    {
        $this->setBrowser('*firefox');
        $this->setBrowserUrl('http://www.example.com/');
    }

    public function testTitle()
    {
        $this->open('http://www.example.com/');
        $this->assertTitle('Example WWW Page');
    }
}
?>
phpunit WebTest
PHPUnit 4.2.0 by Sebastian Bergmann.

F

Time: 7 seconds, Memory: 6.00Mb

There was 1 failure:

1) WebTest::testTitle
Current URL: http://www.iana.org/domains/example/
Screenshot: http://localhost/screenshots/334b080f2364b5f11568ee1c7f6742c9.png

Failed asserting that 'IANA — Example domains' matches PCRE pattern "/Example WWW Page/".


FAILURES!
Tests: 1, Assertions: 1, Failures: 1.


可以对每个测试都用一组浏览器运行:不要用 setBrowser() 来设定单个浏览器,而是在测试用例类里声明一个名称为 $browserspublic static 数组。这个数组里的每个项目都描述了一个浏览器配置。这些浏览器可以各自由不同的 Selenium Server 服务器承载。 例 13.4 展示了一个例子。

例 13.4: 设定多个浏览器配置

<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class WebTest extends PHPUnit_Extensions_SeleniumTestCase
{
    public static $browsers = array(
      array(
        'name'    => 'Firefox on Linux',
        'browser' => '*firefox',
        'host'    => 'my.linux.box',
        'port'    => 4444,
        'timeout' => 30000,
      ),
      array(
        'name'    => 'Safari on MacOS X',
        'browser' => '*safari',
        'host'    => 'my.macosx.box',
        'port'    => 4444,
        'timeout' => 30000,
      ),
      array(
        'name'    => 'Safari on Windows XP',
        'browser' => '*custom C:\Program Files\Safari\Safari.exe -url',
        'host'    => 'my.windowsxp.box',
        'port'    => 4444,
        'timeout' => 30000,
      ),
      array(
        'name'    => 'Internet Explorer on Windows XP',
        'browser' => '*iexplore',
        'host'    => 'my.windowsxp.box',
        'port'    => 4444,
        'timeout' => 30000,
      )
    );

    protected function setUp()
    {
        $this->setBrowserUrl('http://www.example.com/');
    }

    public function testTitle()
    {
        $this->open('http://www.example.com/');
        $this->assertTitle('Example Web Page');
    }
}
?>


PHPUnit_Extensions_SeleniumTestCase 可以为通过 Selenium 运行的测试收集代码覆盖率信息:

  1. PHPUnit/Extensions/SeleniumCommon/phpunit_coverage.php 复制到 web 服务器文档根目录下。
  2. 在 web 服务器上的 php.ini 配置文件中,分别将 PHPUnit/Extensions/SeleniumCommon/prepend.phpPHPUnit/Extensions/SeleniumCommon/append.php 配置为 auto_prepend_fileauto_append_file
  3. 在扩展自 PHPUnit_Extensions_SeleniumTestCase 的测试用例类中,用
    protected $coverageScriptUrl = 'http://host/phpunit_coverage.php';
    来配置 phpunit_coverage.php 脚本所在的 URL。

表 13.2 列出了 PHPUnit_Extensions_SeleniumTestCase 所提供的各种断言方法。

表 13.2. 断言

断言含义
void assertElementValueEquals(string $locator, string $text)当由 $locator 所标识的元素的值与给定的 $text 不等时报告一个错误。
void assertElementValueNotEquals(string $locator, string $text)当由 $locator 所标识的元素的值与给定的 $text 时报告一个错误。
void assertElementValueContains(string $locator, string $text)当由 $locator 所标识的元素的值不包含给定的 $text 时报告一个错误。
void assertElementValueNotContains(string $locator, string $text)当由 $locator 所标识的元素的值包含给定的 $text 时报告一个错误。
void assertElementContainsText(string $locator, string $text)当由 $locator 所标识的元素不包含给定的 $text 时报告一个错误。
void assertElementNotContainsText(string $locator, string $text)当由 $locator 所标识的元素包含给定的 $text 时报告一个错误。
void assertSelectHasOption(string $selectLocator, string $option)当给定的选项不可用时报告一个错误。
void assertSelectNotHasOption(string $selectLocator, string $option)当给定的选项可用时报告一个错误。
void assertSelected($selectLocator, $option)当给定的标签被未被选定时报告一个错误。
void assertNotSelected($selectLocator, $option)当给定的标签被被选定时报告一个错误。
void assertIsSelected(string $selectLocator, string $value)当给定的值被未被选定时报告一个错误。
void assertIsNotSelected(string $selectLocator, string $value)当给定的值被被选定时报告一个错误。


表 13.3列出了 PHPUnit_Extensions_SeleniumTestCase 的模板方法:

表 13.3. 模板方法

方法含义
void defaultAssertions()覆盖本方法来为所有测试用例中的测试执行公用的断言。每个发送往 Selenium Server 服务器的命名之后都会调用本方法。


请查看 Selenium 命令文档来获得所有可用的命令的参考以及它们的使用方法。

Selenium 1 命令是通过 __call 来动态实现的。请查看 PHPUnit_Extensions_SeleniumTestCase_Driver::__call() 的 API 文档 来获取所有 PHP 侧所支持的所有方法的列表以及它们的参数和返回类型。

通过使用 runSelenese($filename) 方法,可以从 Selenese/HTML 规格来运行 Selenium 测试。此外,通过使用静态属性 $seleneseDirectory,可以从包含 Selenese/HTML 文件的目录里自动创建测试对象。PHPUnit 会在指定的目录中的递归查找 .htm 文件,并预期这些文件内含 Selenese/HTML。例 13.5展示了一个例子。

例 13.5: 用包含 Selenese/HTML 文件的目录作为测试

<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class SeleneseTests extends PHPUnit_Extensions_SeleniumTestCase
{
    public static $seleneseDirectory = '/path/to/files';
}
?>


从 Selenium 1.1.1 开始,包含了一个实验性的特性,允许用户在不同测试之间共享会话(session)。唯一支持的情况是当使用单个浏览器时在所有测试间共享会话。在初始启动(bootstrap)文件中调用 PHPUnit_Extensions_SeleniumTestCase::shareSession(true) 来启用会话共享。在碰到不成功的测试(失败或不完整)时会话将会重置;用户自行决定如何避免测试之间的互动,可以重设 cookies 或者从被测应用程序登出(通过 tearDown() 方法)。