当前位置: 首页 > news >正文

使用模拟库进行测试的意义是什么?

目录
  • 前言
  • 正文
    • DbUtil 工具类
    • DbUtilTests 测试类
    • 模拟测试的意义
    • 总结

前言

事情是这样的,我在编写一个 ADO.NET 的工具类,然后通过 Github Copilot 生成对应的测试类,然后生成的测试类中包括 DbConnectionDbCommand等的一些模拟对象,然后看测试方法中还包括一些对结果的模拟,然后我就产生了一个疑问:对象和结果都是模拟出来的,那这个单元测试有什么意义?

正文

DbUtil 工具类

以下代码是 Github Copilot 生成之后,再让 Github Copilot 调整的。

public static class DbUtil
{public static int ExecuteNonQuery(DbConnection connection, string commandText, params DbParameter[] parameters){using var command = CreateCommand(connection, commandText, parameters);return command.ExecuteNonQuery();}public static DataTable ExecuteDataTable(DbConnection connection, string commandText, params DbParameter[] parameters){using var command = CreateCommand(connection, commandText, parameters);using var reader = command.ExecuteReader();var table = new DataTable();table.Load(reader);return table;}public static object? ExecuteScalar(DbConnection connection, string commandText, params DbParameter[] parameters){using var command = CreateCommand(connection, commandText, parameters);return command.ExecuteScalar();}public static DbDataReader ExecuteReader(DbConnection connection, string commandText, params DbParameter[] parameters){using var command = CreateCommand(connection, commandText, parameters);return command.ExecuteReader(CommandBehavior.CloseConnection);}private static DbCommand CreateCommand(DbConnection connection, string commandText, params DbParameter[] parameters){var command = connection.CreateCommand();command.CommandText = commandText;command.CommandType = CommandType.Text;if (parameters is { Length: > 0 }){command.Parameters.AddRange(parameters);}EnsureConnectionOpen(connection);return command;}private static void EnsureConnectionOpen(DbConnection connection){if (connection.State != ConnectionState.Open){connection.Open();}}
}

DbUtilTests 测试类

使用的测试框架是 xunit,断言库是 FluentAssertions,模拟库是 Moq,生成代码如下:

public class DbUtilTests
{private readonly Mock<DbConnection> _mockConnection;private readonly Mock<DbCommand> _mockCommand;private readonly Mock<DbDataReader> _mockReader;public DbUtilTests(){_mockConnection = new Mock<DbConnection>();_mockCommand = new Mock<DbCommand>();_mockReader = new Mock<DbDataReader>();_mockConnection.Setup(c => c.CreateCommand()).Returns(_mockCommand.Object);_mockCommand.Setup(c => c.ExecuteReader()).Returns(_mockReader.Object);_mockCommand.Setup(c => c.ExecuteReaderAsync(It.IsAny<CommandBehavior>(), default)).ReturnsAsync(_mockReader.Object);}[Fact]public void ExecuteNonQuery_ShouldExecuteCommand(){_mockCommand.Setup(c => c.ExecuteNonQuery()).Returns(1);var result = DbUtil.ExecuteNonQuery(_mockConnection.Object, "SELECT 1");result.Should().Be(1);_mockCommand.Verify(c => c.ExecuteNonQuery(), Times.Once);}[Fact]public void ExecuteScalar_ShouldReturnScalarValue(){_mockCommand.Setup(c => c.ExecuteScalar()).Returns(42);var result = DbUtil.ExecuteScalar(_mockConnection.Object, "SELECT 42");result.Should().Be(42);_mockCommand.Verify(c => c.ExecuteScalar(), Times.Once);}[Fact]public void ExecuteDataTable_ShouldReturnDataTable(){var dataTable = new DataTable();_mockReader.Setup(r => r.Read()).Returns(false);var result = DbUtil.ExecuteDataTable(_mockConnection.Object, "SELECT * FROM Test");result.Should().BeEquivalentTo(dataTable);_mockCommand.Verify(c => c.ExecuteReader(), Times.Once);}
}

随后就对 Github Copilot 发出了灵魂拷问,我觉得它回答的很好,便有了这篇记录。

模拟测试的意义

  1. 验证逻辑是否正确

这些模拟测试主要目的是验证DbUtil类的方法逻辑是否正确,而不是测试数据库本身。通过模拟数据库连接和命令对象,可以确保:

  • 类方法正确调用DbCommand的方法
  • 方法是否正确处理了输入参数(如 SQL 语句)
  • 方法是否返回预期结果
_mockCommand.Verify(c => c.ExecuteNonQuery(), Times.Once);

上面代码就验证了ExecuteNonQuery 方法是否被调用了一次,如果在实现中有遗漏,就会测试失败。

  1. 隔离依赖,专注于代码逻辑

在本例中,数据库操作是一个外部依赖,直接依赖真实数据库会导致以下问题:

  • 测试不稳定:数据库可能不可用,或数据状态不一致
  • 测试速度慢:与数据库交互通常比内存操作慢得多
  • 环境依赖:测试需要配置数据库环境,增加了复杂性

通过使用 Mock 对象,测试可以完全隔离数据库的影响,专注于验证 DbUtil 的逻辑是否正确。

  1. 提高测试覆盖率

通过模拟对象,可以覆盖到代码中所有与数据库交互的逻辑,而不需要实际运行 SQL 查询。如:

  • 测试是否正确处理了 ExecuteNonQuery 的返回值
  • 测试方法是否正确执行

这些测试可以帮助开发者在不依赖真实数据库的情况下,确保代码的行为符合预期。

  1. 防止回归问题

这些测试可以作为回归测试的一部分,如果将来有人修改了 DbUtil 的实现(如:改变了调用顺序或者漏掉了一些调用),这些测试会立即失败,从而提醒开发者代码修改后可能存在问题。

  1. 模拟复杂场景

通过 Mock 对象,可以模拟一些真实环境中难以复现的场景。如:

  • 模拟数据库抛出异常
  • 模拟数据库返回特定结果
  • 模拟异步操作的延迟

这些场景在真实数据库中可能很难测试,但通过 Mock 可以轻松实现。

总结

这些测试通过 Mock 对象而没有直接操作真实数据库,它们的意义在于:

  • 验证代码逻辑是否正确
  • 隔离外部依赖,确保测试稳定
  • 提高测试覆盖率
  • 防止未来的代码回归问题
  • 模拟复杂场景,确保代码在各种情况下都能正常工作
http://www.agseo.cn/news/70/

相关文章:

  • MyEMS:开源领域的能源管理创新解决方案
  • 【Containerd交互命令】ctr、crictl常用基本命令
  • DAG Matters! GFlowNets Enhanced Explainer For Graph Neural Networks | |
  • abap字符串操作
  • [完结16章]COZE AI 智能体开发体系课(从入门到高级)零基础零代码
  • 推出其新一代高性能Sub-GHz射频收发芯片UM2011A
  • 在 Athena UDF 中使用 Java 将数据写入 DynamoDB
  • Pychram 激活
  • 掌控AI编程全链路:Cline让你随意选模型、透明成本、零信任安全 - 公众号
  • 数据库事务隔离级别引发的应用安全竞态条件漏洞分析
  • Node-Red学习笔记
  • 隧道工程LoRa无线监测设备集成方案 直击隧道深部监测痛点
  • 【k8s】为什么ctr导入后通过crictl查看不到导入的镜像
  • Swift 结合 Tesseract 进行验证码识别
  • 当虚拟机目录空间不足时的扩容
  • 使用IText创建PDF
  • MyEMS 深度解析:碳管理赋能与系统集成的实践路径
  • uv包管理 - 小学弟
  • 对口型视频创作指南:AI如何让“假唱”变成真艺术?
  • 用Python + Tesseract OCR:验证码识别全流程解析
  • Dockerfile中的yum install、yum clean和rm -rf /var/cache/yum
  • Linux 完整的用户登录工作流程详解(GUI TTY)
  • 0 元夺宝小程序介绍
  • 线上频繁FullGC?慌得一比!竟是Log4j2的这个“特性”坑了我
  • clickhouse进程stop之后为什么还自动启动
  • 294、瑶池
  • Unix/Linux 高效的平台通过 IP 地址获取接口名的 C++ 构建
  • 每周读书与学习-初识JMeter 元件(一)
  • CloudBeaver轻量级的云数据库管理工具
  • kylin V10使用安装盘制作本地镜像