设计和实现测试策略
成功生成的 SQL 数据库项目并不意味着内部的存储过程返回正确的结果。 过程可以编译且没有错误,但仍计算错误的总计或跳过验证检查。 测试在问题到达生产环境之前捕获它们。
了解数据库测试级别
数据库测试在层中工作,每个层都会捕获不同的问题类:
-
生成验证 是
dotnet build你已有的步骤。 它捕获语法错误和损坏的对象引用。 快速,但它只是证明架构在结构上有效。 - 单元测试 验证单个存储过程和函数是否为给定的一组输入生成正确的结果。 它们检测逻辑错误。
- 集成测试 针对已部署的数据库执行端到端方案。 它们证明对象能够正确协同工作,但需要运行实例并花费最多时间。
每个层的速度较慢,比之前的层更昂贵,但也捕获了上一层无法解决的问题。
创建 SQL Server 单元测试
Visual Studio 中的 SQL Server Data Tools (SSDT)包含用于数据库单元测试的内置框架。 每个测试针对实时数据库执行 T-SQL,并使用测试条件验证结果。
单元测试的结构
每个测试都有三个部分,这些部分遵循 setup-execute-cleanup 模式:设置-执行-清理。
- 预测试:设置测试所需的数据。 插入客户记录,清除以前的运行中的剩余数据。
- 测试:执行要测试的操作。 调用存储过程并查询视图。
- 测试后:在测试后进行清理,使其不会污染下一次运行。
测试条件
测试 T-SQL 运行后, 测试条件 将验证返回的内容。 最常用的条件包括:
- 行计数:验证结果集是否包含预期的行数。
- 标量值:验证结果集中的特定单元格是否包含预期值。
- 预期架构:验证结果集是否具有预期的列名称和数据类型。
其他内置条件包括 数据校验和、 空结果集、 非空结果集和执行 时间。
创建单元测试项目
在 Visual Studio 中设置 SQL Server 单元测试需要执行几个步骤:
- 打开 SQL 数据库项目。
- 在 SQL Server 对象资源管理器中,找到要测试的存储过程或函数。
- 右键单击该对象,然后选择“ 创建单元测试”。
- 选择或创建 C# 测试项目。
- 将测试连接设置到您的开发数据库。
- 在 运行单元测试之前,选择“自动部署数据库项目”。 此选项使测试数据库与最新的项目更改保持同步。
设计器将打开一个 T-SQL 模板,可在其中编写测试逻辑并附加测试条件。
编写有效的数据库单元测试
良好的单元测试回答一个具体问题:“该过程是否会对已知输入产生预期的效果?” 这里有一个测试 uspPlaceNewOrder 的示例,用来验证下单是否正确更新了客户的年初至今总计:
-- Pre-test: Set up customer and clear previous data
DECLARE @CustomerID INT;
INSERT INTO [Sales].[Customer] (CustomerName) VALUES (N'Test Customer');
SET @CustomerID = SCOPE_IDENTITY();
DELETE FROM [Sales].[Orders] WHERE [CustomerID] = @CustomerID;
-- Test: Place an order and verify YTDOrders updated correctly
DECLARE @RC INT;
EXECUTE @RC = [Sales].[uspPlaceNewOrder] @CustomerID, 100, GETDATE(), 'O';
SELECT [YTDOrders]
FROM [Sales].[Customer]
WHERE [CustomerID] = @CustomerID;
📝 将此测试 T-SQL 与标量值测试条件配对,该条件期望 YTDOrders 值为 100。
否定测试
你还需要验证存储过程在收到无效输入时是否正确失败。 例如,取消已发货的订单应该引发错误,而不是无声地成功。 负测试确认发生预期错误。
在上一部分的步骤 3 中选择“ 创建单元测试 ”时,Visual Studio 会在 解决方案资源管理器 中生成一个 .cs C# 测试项目,其中包含所选的每个存储过程的测试文件。 例如,如果为uspCancelOrder创建了一个测试,则生成的文件包含一个名为Sales_uspCancelOrder_Test的部分。 若要将该测试标记为负测试,请在.cs解决方案资源管理器中打开该文件,并直接在其上方添加ExpectedSqlException该属性并保存文件:
[TestMethod()]
[ExpectedSqlException(Severity = 16, MatchFirstError = false, State = 1)]
public void Sales_uspCancelOrder_FilledOrder_Test()
仅当存储过程引发与指定严重性和状态匹配的错误时,测试才会通过。 如果存储过程无声地成功,则测试失败,这正是你想要的结果。
设计集成测试方法
单元测试隔离各个对象。 集成测试会更进一步,并测试跨多个操作的场景。 他们回答以下问题:
- 存储过程调用序列是否使数据库保持预期状态?
- 当数据通过应用程序层到达时,触发器是否正确触发?
- 视图在一系列相关表更改后是否返回准确的结果?
集成测试需要专用数据库。 每次运行之前,使用 “在运行单元测试前自动部署数据库项目” 设置,将 SQL 数据库项目部署到测试实例。 此设置使测试架构保持最新状态。
测试数据库的注意事项
单元测试和集成测试都针对实时数据库运行 T-SQL,因此测试环境需要仔细设置才能生成可靠的结果。
- 将测试与生产隔离开来。 使用单独的实例或专用测试数据库。 永远不建议在生产环境中运行测试。
- 在每次运行之前重置为已知状态。 部署后脚本或测试清理脚本可处理此问题。
- 在配置文件中外部化连接字符串,以便本地开发和 CI 管道每个点指向正确的数据库,而无需更改代码。
将测试集成到 CI/CD 管道中
在构建和部署后添加测试步骤,以便在每次提交时自动运行测试。 在 Azure DevOps 中,使用 VSTest 任务:
- task: VSTest@2
inputs:
testAssembly: '**\*Tests.dll'
searchFolder: '$(System.DefaultWorkingDirectory)'
在 GitHub Actions 中,使用 dotnet test以下命令运行测试:
- name: Run database unit tests
run: dotnet test ./DatabaseTests/DatabaseTests.csproj
小窍门
将测试项目配置为在测试运行之前自动部署数据库项目。 此设置可确保测试数据库架构在测试时与项目匹配。
当测试失败时,管道停止,更改不会到达暂存环境或生产环境。 此延迟使团队在影响任何环境之前有时间修复问题。
关键结论
数据库测试跨三个级别:为结构正确性生成验证、逻辑验证单元测试,以及端到端工作流的集成测试。 SQL Server 单元测试遵循测试前操作、测试操作和测试后操作的三阶段结构。 若要验证存储过程和函数行为,请使用测试条件,例如 Scalar Value, Row Count以及 Expected Schema。 若要测试应验证错误处理路径的方法,请添加属性 ExpectedSqlException 。 将测试项目接入你的 CI/CD 管道,以便失败的测试在更改到达暂存环境或生产环境之前阻止部署。 这三个层共同构成了一个安全网,让你的团队能够自信地部署数据库更改,而不是焦虑。