slack/hack-sql-fake

Hacklang 数据库测试库


README

Build Status

Hack SQL Fake 是 Hack 语言的一个单元测试库。它通过内存中 MySQL 的模拟,允许测试数据库驱动的应用程序。它支持广泛的查询,包括测试用例之间的快速数据库快照/还原等。这是通过一个模拟对象实现的,该对象包含数据库的实现,避免了显式存根或模拟的需要。

动机

在大多数单元测试库中,SQL 查询传统上被 Mock 或 Stub 实现所取代。Mock 需要一个显式的查询列表,其中包含预期运行和返回结果的查询,而 stub 可能甚至不检查正在运行的查询,只返回一个硬编码的结果。这导致了大量的手动工作来设置预期,并且测试是脆弱的,甚至代码或查询的良性更改也需要更新。这也意味着数据访问层没有进行单元测试。

另一种常见策略是使用实际数据库进行测试,例如 SQLite。这可能导致测试数据库的行为与生产数据库不匹配,并且任何使用生产数据库特定功能的代码可能无法测试。这也意味着不同的测试用例没有相互隔离,这可能会使测试难以调试。这可以通过在每次测试用例之间截断表来解决,但这可能会引起性能问题。

Hack SQL Fake 采取不同的方法 - 它解析和执行针对内存中存储在 hack 数组中的“数据库”的 SELECT、INSERT、UPDATE 和 DELETE 查询。只要用于测试的数据量很小,就可以解决上述问题。

支持的 SQL 语法

此库支持广泛的查询语法,包括

  • FROMWHEREGROUP BYHAVINGORDER BYLIMIT 子句,适用于每种查询类型的适当子句
  • 支持所有连接类型的 JOIN 查询
  • 多查询,如子查询、UNIONUNION ALLINTERSECTEXCEPT
  • 复杂的表达式,如 CASEBETWEEN 和行比较器 (1, 2, 3) < (4, 5, 6)
  • 所有基本运算符都实现了运算符优先级
  • 列别名、跨数据库查询
  • INSERT ... ON DUPLICATE KEY UPDATE
  • 各种 SQL 函数,如 COUNT(), NULLIF(), COALESCE(), CONCAT_WS() 以及许多其他函数
  • 可以启用或禁用严格 SQL 模式以抛出异常,用于无效数据类型和缺少非空字段
  • 验证解析器:查询解析器将在大多数无效 SQL 查询上抛出异常,有助于保护您的生产环境免受意外的 SQL 语法错误的影响

有关支持的所有内容的概述,请参阅此库的 tests/ 目录,该目录记录了其支持的大多数 SQL 功能。

使用方法

要求

SQL Fake 需要

  • HHVM
  • Composer

安装

此软件包可以通过 composer 安装

composer require slack/hack-sql-fake

版本和发布

Hack/HHVM是一个快速发展的生态系统,经常发生重大变化。因此,本项目中记录的版本号追踪HHVM版本号。我们的目标是支持main分支上的最新LTS(长期支持)版本HHVM。其他支持的HHVM版本的长期存活分支也可能被维护。目前,支持HHVM 4.80、4.56和4.40的版本。有关兼容性,请参阅https://github.com/slackhq/hack-sql-fake/releases页面。

工作原理

SQL Fake通过提供AsyncMysqlConnectionPool的子类,这是Hack内置推荐的查询MySQL的方法。还提供了一个AsyncMysqlClient的子类。

此库假设您当前正在使用AsyncMysqlConnectionPool建立某种形式的数据库连接。使用SQLFake的最佳方式取决于您的代码,但您可以使用依赖注入或fb_intercept()函数在测试时实例化一个Slack\SQLFake\AsyncMysqlConnectionPool。这将在整个测试运行期间模拟数据库。

在每次测试运行中,您还应该调用Slack\SQLFake\init()来注册数据库模式。有关说明,请参阅导出数据库模式

例如,假设您有一个名为Db的类,用于管理数据库连接,该方法名为getConnectionPool(): AsyncMysqlConnectionPool。在您的测试中,您可以拦截该函数以返回SQLFake连接池的一个实例。

$pool = new Slack\SQLFake\AsyncMysqlConnectionPool(darray[]);

fb_intercept('Db::getConnectionPool', (string $name, mixed $obj, varray<mixed> $args, mixed $data, inout bool $done) ==> {
	$done = true;
	return $pool;
});

其余的代码可以按正常方式运行,以相同的方式使用数据库,就像在生产环境中使用一样。

设置和清理

您可以使用Slack\SQLFake\snapshot($name);Slack\SQLFake\restore($name);函数来创建快照并恢复这些快照。这有助于在测试用例之间共享设置,同时将测试对数据库的修改与其他测试隔离开。如果使用HackTest,您可能需要在beforeFirstTestAsync中调用snapshot()和在beforeEachTestAsync()中调用restore()

导出数据库模式

默认情况下,SQL Fake将允许您使用插入语句调用任意表的存在,而无需具有模式信息。然而,此库包含一个模式导出器,它可以从.sql文件生成Hack代码。这允许SQL Fake更加严谨,提高测试的价值。

  • 验证数据类型,包括nullability
  • 对不存在于表中的查询失败
  • 对不存在于列中的查询失败
  • 强制执行主键和唯一约束
  • 实现INSERT ... ON DUPLICATE KEY UPDATE

将模式转储到*.sql文件

利用模式的第一步是创建一个或多个包含您的模式的.sql文件。您应该使用如下命令为每个数据库(可能每个服务器有多个)创建一个

mysqldump -d -u someuser -p mydatabase > mydatabase.sql
mysqldump -d -u someuser -p mydatabase2 > mydatabase2.sql

*.sql文件生成Hack模式

将所有先前生成的SQL文件作为参数传递给bin/hack-sql-schema,并将结果输出到任何Hack文件。

bin/hack-sql-schema mydatabase.sql mydatabase2.sql > schema.hack

这将生成一个包含单个const的文件,表示您的数据库模式。您可以将此文件放置在代码库中的任何位置,因为包含的常量可以自动加载。sql文件的文件名将用作数据库名称,形状表示该数据库中每个表的模式。

将模式传递给SQLFake

模式的预期用例是生成一个包含模式的常量,并在启动时将其分配给SQLFake - 但您可以选择在任何时间以任何值分配它。假设您使用上述脚本生成一个名为DB_SCHEMA的常量,您将像这样将其分配给SQLFake

Slack\SQLFake\init(DB_SCHEMA);

这使得 SQLFake 了解哪些数据库中存在哪些表和列。您还希望告诉它哪些 服务器 包含这些信息。

服务器配置

MySQL 连接在 主机或 IP 上操作。由于 SQLFake 在与 AsyncMysqlClient 相同的级别上运行,它也按主机名组织数据。我们建议在测试中使用与生产数据库基础设施相关的假主机名。

在任何时候,您都可以告诉 SQL Fake 哪些服务器存在,并可以通过主机名提供这些服务器的设置。尝试连接到未明确定义的服务器将导致异常。

Slack\SQLFake\add_server("fakehost1", shape(
	// SQL Fake can do some minimal differentiation between 5.6 and 5.7 hosts
	'mysql_version' => '5.6',
	// SQL Fake can further validate queries that are supported by Vitess databases
	'is_vitess' => false,
	// On invalid data types, should SQLFake coerce to a valid type or throw? Similar to how MySQL strict mode behaves
	'strict_sql_mode' => true,
	// If this setting is false, queries that access tables not defined in the schema are allowed
	'strict_schema_mode' => true,
	// Identify the database name in the schema dictionary to use schema from here
	'inherit_schema_from' => 'db1',
));

指标

SQL Fake 能够收集测试期间运行的查询的指标,并可选地包含查询运行的堆栈跟踪。如果您想跟踪代码关键部分的查询数量和类型,这将很有用。这可能会消耗很多内存,因此默认情况下是禁用的。

要启用指标收集,设置 Slack\SQLFake\Metrics::$enable = true;。然后您可以调用

// get the total count of queries that have been invoked
Slack\SQLFake\Metrics::getTotalQueryCount();

// get details on each query that was run
Slack\SQLFake\Metrics::getQueryMetrics();

// get counts by type (insert, update, select, delete)
Slack\SQLFake\Metrics::getCountByQueryType();

调用栈

在记录测试中调用的查询信息时,您可以捕获查询运行时的调用栈。这有助于阐明这些查询来自何处,并有助于报告触发最多查询的代码路径。要启用调用栈,提供 SQLFake\Metrics::$enableCallstacks = true;。SQLFake 将自动从这些调用栈的末尾过滤掉自己的函数。您还可以通过传递要忽略的模式的关键集来过滤掉您自己的低级库代码,以确保调用栈最具可读性。

Slack\SQLFake\Metrics::\$stackIgnorePatterns = keyset['mysql_*', 'db_*', 'log_*', '_log_*'];

与这些模式匹配的任何函数名都将从堆栈跟踪的末尾移除。

为什么不支持 X

这个库旨在支持用户在 MySQL 中使用的所有内容,而不是 MySQL 提供的所有可能功能。我们欢迎拉取请求来添加对新语法、sql 函数、数据类型、错误修复和其他功能的支持。查看我们的 #issues 页面以获取愿望清单。

贡献

请参阅 贡献指南

开发

该存储库包含一个 VSCode devcontainer 设置。在有本地 docker 守护进程运行的条件下,在您对这个存储库的签出根目录中调用 code .,VSCode 应该会自动构建一个容器并安装依赖项。还包括一个调试操作,可以在 HHVM 调试器中运行单元测试。

最新版本

  • v4.0.5

有关更多详细信息,请参阅 变更日志