使用 Travis CI 进行集成测试





5.00/5 (2投票s)
您会编写集成测试吗?单元测试呢?我相信对于第二个问题,回答“是”的人比第一个问题多。这有点奇怪——对于许多应用程序来说,编写集成测试真的没那么难。甚至可能不需要设置您自己的基础设施来运行这些测试。
您会编写集成测试吗?单元测试呢?我相信对于第二个问题,回答“是”的人比第一个问题多。这有点奇怪——对于许多应用程序来说,编写集成测试真的没那么难。甚至可能不需要设置您自己的基础设施来运行这些测试。如今,许多 CI 工具允许您在其构建代理上安装数据库、队列等。通过在构建服务器上拥有外部依赖项,您可以在单元测试旁边运行一套互补的测试。
在这篇博文中,我将展示使用 Travis CI 设置集成测试有多么容易。在此之前,让我们先深入探讨一些理论;集成测试和单元测试到底有什么区别?
集成测试与单元测试
让我们从可能更熟悉的单元测试开始。通过单元测试,您可以测试一段隔离的、通常很小的代码,例如一个函数或一个类。您可以模拟或存根外部依赖项。这些依赖项可以是代码中的其他类或函数。它们也可以是完全不同的应用程序、API 端点、数据库、队列等等。每当您测试一个具有此类外部依赖项的函数时,您都会为其模拟注入预设的行为或状态。因此,您可以测试:当 API 调用返回响应 X 时,我的函数会发生什么?如果返回响应 Y 呢?
通过集成测试,您可以测试不同组件的组合。这里的组件的定义是关键:它可以是一个类、一个服务/应用程序,甚至是一组应用程序。您测试您的组件如何与其他组件集成。与单元测试的关键区别在于,在单元测试中,您会模拟或存根外部依赖项。在集成测试中,您做的恰恰相反——您实际上会连接到 MySQL 数据库、API 端点、RabbitMQ 队列或 Memcache 实例。如果您连接到多个外部依赖项,您仍然可以模拟依赖项来测试与特定依赖项的集成。这里的挑战在于正确设置您的外部依赖项。您不会向模拟添加正确的状态,而是会将正确的状态放入数据库。这正是我们在这篇博文中要做的事情。
介绍:The Doubler
我设置了一个与外部数据库集成的非常简单的应用程序。这个应用程序的功能相当令人印象深刻:它将数字乘以二。这个单文件应用程序可以在我的 Github 仓库 中找到。如果您想在阅读这篇博文的同时进行跟进,我建议您在 Github 上创建一个(临时的)仓库,并在跟进时添加代码。最后,我们将把该仓库与 Travis CI 集成以执行集成测试。
我的仓库还包含一个 Vagrantfile,您可以使用它来启动应用程序。如果您不熟悉 Vagrant:它会使用 Virtualbox 启动一个虚拟机(并支持其他虚拟化技术和 Docker)。在我们的例子中,它使用 CentOS7 基础镜像并安装 PHP7、MySQL5.6 以及我们的应用程序。
The doubler 是一个批处理 PHP 应用程序,它与 MySQL 集成。运行该应用程序会将 MySQL 表中的每个数字乘以二,并将其存储在同一张表中。表的结构如下;
CREATE TABLE numbers(
number INT(11) NOT NULL,
number_calculated INT(11) DEFAULT NULL
);
number
字段中的每个值都会乘以二,并存储在 number_calculated
字段中。PHP 应用程序的总内容只有几行
$mysql_host = getenv('MYSQL_HOST') ?: 'localhost';
$mysql_user = getenv('MYSQL_USER') ?: 'root';
$mysql_password = getenv('MYSQL_PASSWORD') ?: '';
$connection_string = "mysql:host={$mysql_host};dbname=numbers";
$db = new PDO($connection_string, $mysql_user, $mysql_password);
$db->exec("UPDATE numbers SET number_calculated = number*2 WHERE number_calculated IS NULL"); (TEST THIS)
正如我们稍后将看到的:在 Travis CI 中,我会用正确的设置覆盖环境变量,以配置 Travis CI 构建容器上安装的 MySQL 实例。这使得我可以在本地使用我的本地设置开发应用程序,同时应用程序在 Travis 环境中也能正常工作。
通过使用 php-cli 执行 doubler.php
文件来运行应用程序
php -f /vagrant/doubler.php
创建集成测试
正确配置集成测试需要我们完成三个不同的步骤;
- 用测试数据填充数据库。测试数据是一组测试数据。在我们的例子中:一组添加到 MySQL 的
numbers
字段中的数字。 - 运行我们的应用程序。我们将“运行”我们的应用程序,使其针对刚刚填充的数据库。在我们非常简单的设置中,这意味着:执行 doubler.php 文件。
- 检查是否将正确的内容添加到数据库。我们现在检查
number_calculated
字段是否已填充正确的数字。
插入测试数据
在插入测试数据之前,需要一个具有架构的数据库。一个简单的脚本可以设置这个
#!/usr/bin/env php
<?php
$mysql_host = getenv('MYSQL_HOST') ?: 'localhost';
$mysql_user = getenv('MYSQL_USER') ?: 'root';
$mysql_password = getenv('MYSQL_PASSWORD') ?: '';
$connection_string = "mysql:host={$mysql_host}";
$db = new PDO($connection_string, $mysql_user, $mysql_password);
$schema = file_get_contents(dirname(__FILE__) . '/schema.sql');
$db->exec($schema);
脚本中的第一行称为 shebang。由于我们将从命令行执行此脚本,因此 shell 需要知道使用哪个解释器来执行此脚本。因此,第一行会告诉它在当前环境的 $PATH 中查找并使用 PHP 可执行文件。
就像之前一样,我们使用环境变量来获取数据库的凭据,以防我们不在本地运行脚本。为了便于阅读,我将创建 MySQL 架构的脚本分到了一个单独的文件中。此文件会设置上一节中所示的架构。
接下来,我们将插入测试数据。与编写单元测试类似,重要的是要考虑您想要测试的边缘情况。在乘以数字的情况下,您可能需要测试负数、0(零)和非常大的数字是否正确执行。
#!/usr/bin/env php
<?php
$mysql_host = getenv('MYSQL_HOST') ?: 'localhost';
$mysql_user = getenv('MYSQL_USER') ?: 'root';
$mysql_password = getenv('MYSQL_PASSWORD') ?: '';
$connection_string = "mysql:host={$mysql_host};dbname=numbers";
$db = new PDO($connection_string, $mysql_user, $mysql_password);
$count = $db->exec("
INSERT INTO numbers
(number)
VALUES
(1),
(2),
(10),
(42),
(0),
(-100),
(123456789),
(-33);
");
运行应用程序
运行应用程序应该是比较容易的部分。对于像本例中的批处理作业,这尤其容易,因为它主要任务是执行、处理一些数据然后退出。对于包含前端 UI 的 Web 应用程序,这一部分会更难。请记住,您想测试与外部依赖项的集成:在我将一个项目添加到购物车后,我的数据库是否包含正确的数据?当我从购物车中删除它时,它是否消失了?
回到 doubler:运行这个应用程序就像执行 doubler.php 文件一样简单。它会在数据库中找到测试数据并执行乘法。
require dirname(__FILE__) . '/../doubler.php';
接下来,是时候找出是否将正确的数据插入表中了。
断言结果
查询 MySQL 数据库将向我们展示 doubler 是否插入了正确的数据。在从表中获取所有行之后,我使用一个简单的辅助函数来断言每行中是否存在预期的值。当发现错误时,返回正确的非零退出代码非常重要。这是脚本的执行者知道脚本是否成功执行的一种方式。0(零)表示一切正常。大于 0 的值表示出现问题。
$stmt = $db->prepare("SELECT number_calculated FROM numbers");
$stmt->execute();
$result = $stmt->fetchAll();
testCalculatedNumber(0, 2, $result);
testCalculatedNumber(1, 4, $result);
testCalculatedNumber(2, 20, $result);
testCalculatedNumber(3, 84, $result);
testCalculatedNumber(4, 0, $result);
testCalculatedNumber(5, -200, $result);
testCalculatedNumber(6, 246913578, $result);
testCalculatedNumber(7, -66, $result);
echo "All numbers OK";
// a simple helper function to easily test for the correct data
function testCalculatedNumber($index, $expected, $result)
{
$number_calculated = (int)$result[$index]['number_calculated'];
if($number_calculated !== $expected) {
echo "Expected number calculated to be '{$expected}', got '{$number_calculated}'";
// exit with the correct error code so Travis CI picks this up as a failed test
exit(1);
}
}
再次,请务必查看我的 Github 仓库 以获取包括测试在内的完整项目。
在 Travis CI 上配置集成测试
最后,所有之前的脚本将在 Travis CI 配置中汇集。我们在项目中添加一个 .travis.yml
文件,Travis CI 使用它来正确构建和测试应用程序。
language: php
php:
- '7.0'
env:
- MYSQL_HOST=127.0.0.1 MYSQL_USER=root
services:
- mysql
before_script: ./tests/setup_mysql.php
script: ./tests/integration_test.php
顶部的值用于告诉 Travis CI 我们正在运行的语言和版本。接下来,我们将环境变量设置为 Travis CI 的默认 MySQL 凭据。在 services 数组中,我们告诉 Travis CI 在构建容器中安装 MySQL。最后,我们在 before_script
步骤中设置 MySQL,并设置 Travis CI 必须执行的 script
来测试应用程序。就是这样;请务必查看 Travis CI 文档 以了解有关 .travis.yml
文件的更多信息。
现在是时候将 Travis CI 链接到我们的 Github 仓库,以便它可以获取内容并执行集成测试。如果您正在跟进:请确保您的仓库中的所有文件都位于正确的位置,以便 Travis CI 能够找到它们。访问 travis-ci.org 并点击使用 Github 登录按钮。授予 Travis CI 访问您帐户的权限。您可以精确指定 Travis CI 应获得访问权限的仓库。启用您的仓库,推送新更改,Travis CI 应该会开始运行您仓库的第一个测试!Travis CI 现在将在每次推送到 master 分支后测试您的仓库。希望一切顺利,构建会显示为绿色。如果不行,请注意返回的错误并应用适当的修复。
但是如果您的构建失败了呢?我们可以为我们的应用程序引入一个“bug”,看看当我们把这段代码推送到我们的仓库时会发生什么。假设我们将乘以所有数字的行更改为
$db->exec("UPDATE numbers SET number_calculated = number*3 WHERE number_calculated IS NULL");
糟糕;我们现在正在乘以三,这肯定会导致集成测试失败。将此更改推送到您的仓库,看看您的提交失败(或者在 我的提交历史 中查看“Introduce Bug”提交;点击红色的 X 查看更多信息)。
撤销您的更改,然后再次看到您的构建变成绿色
就是这样!我们让 Travis CI 为我们的应用程序运行了一个集成测试。
结论
在这篇博文中,我们为非常简单的批处理应用程序创建了一个集成测试。当然,这是一个相对简单的用例。当您的流程包含多个(批处理)应用程序和数据库时会发生什么?测试 Web 应用程序又如何?
这些是我希望在未来的博文中介绍的一些主题。就目前而言,我希望这篇博文已经展示了使用 Travis CI 等 CI 工具运行集成测试的便捷性。
文章 Travis CI 集成测试 最初发布于 Sander Knape。