如何使用单元测试测试以下方案

本文关键字:方案 测试 何使用 单元测试 | 更新日期: 2023-09-27 18:36:26

我会对以下内容进行单元测试还是进行其他类型的测试:

我想更新

数据库中的值,更新值后,我想确保数据库更新了正确的值,但这意味着我必须查询数据库并确定是否存在正确的值,我认为在单元测试中,触摸数据库是禁忌。

我想对如下方法进行单元测试(db 和 Update 由 Tab 组成):

public void UpdateValue(int value)
{
   db.Update(value);
}

如何使用单元测试测试以下方案

您可以简单地测试您的方法是否成功调用数据库。嘲笑往往集中在这一点上,即你期望发生的电话确实发生了。

这将涉及将db替换为测试版本,以便您的UpdateValue测试将断言它期望使用相同的值调用db.Update(value)

应该测试存储过程或 SQL(归根结底,它仍然受到与 C# 代码相同的错误可能性的影响),但可能独立于代码单元测试完成。我们有一个单独的测试项目来测试存储过程逻辑。由于这涉及物理数据库,因此它是独立且最小的 - 但我们仍然认为它是必不可少的。我们已经到了测试几乎所有数据库脚本的阶段,但首先,您通常可以不测试基本的 CRUD 代码。任何具有条件语句的 SQL 都会经过测试。

如果要执行端到端测试以查看是否可以使用代码保存到数据库并再次获取值,则根据定义,这不是单元测试。正如@lazyberezovsky所说,这是一个集成测试。单元测试旨在删除代码单元周围的所有依赖项,以便单独测试该单元。

也就是说,(在我看来)也必须进行集成测试。我们针对用例使用它们,以便我们测试用户已签署的内容作为用例操作。这一次性敲定了大量代码,但有一个明显的缺点,即与共享状态/副作用代码作斗争。您会发现,对于集成测试,大部分测试是准备而不是断言代码。您还发现它们无法识别失败的特定代码,因此诊断集成测试失败更加困难。

我们在集成测试中达成了中间立场。我们的数据库通过 DAL 接口馈送,我们只需存根此接口(不同于模拟),以便提供内存中的测试数据,而不是物理数据库。这样做的一个缺点是我们错过了对数据库本身的集成测试。

事实上,根据纯粹的定义,接触数据库的测试不是"单元测试",而是"集成测试"。

典型的答案是替换对 db 的调用。Update() 带有模拟,但在某些情况下,如果您这样做,您可能会觉得您没有测试足够的代码。

根据您使用的数据库,可能还有其他选择。如果使用 SQL Server,则可以针对本地文件 SQL Express db 执行单元测试。在每次单元测试执行时,该文件都可以用原始的干净副本覆盖,因此您的测试也变得非常可重复。您需要做的就是使用单元测试代码部署 SQL Express 数据库,并在单元测试 app.config 中将连接字符串设置为该本地数据库。

重要的是要记住,单元测试并不是所有自动化软件测试的完美补救措施。

您描述的情况通常通过两种方式进行测试:

  • 首先,您将单元测试作为常规开发的一部分进行,并且此测试将简单地确保使用适当的值调用依赖项(如Adam Houldsworth和lazyberezovsky答案中所述)。这只是告诉你(和其他开发人员)这段代码是这样做的,这是证据
  • 接下来,执行集成测试。此测试需要实际组件和适当的设置(例如,有效配置,无/最少数量的模拟)。此测试表明您的组件(或整个应用程序)在实际场景中工作和交互。

每种类型的测试都有其位置,您通常希望同时拥有这两种测试。

对象

的责任是将值传递给它的依赖项(即数据库)。因此,注入一些抽象,它表示对象的数据库:

public Foo(IDatabase db)
{
   _db = db;
}

并验证此依赖项与要测试的对象之间的交互:

Mock<IDatabase> db = new Mock<IDatabase>();
db.Setup(x => x.Update(5));
Foo foo = new Foo(db.Object);
foo.Update(5);
db.VerifyAll();

你是对的,在单元测试中接触数据库是禁忌。单元测试应仅验证一个单元的行为(隔离)。如果你想检查几个一起工作的单元(你的foo和db),那么你需要一个集成测试来验证数据库中实际更改的数据。

更新:此示例中使用了 Moq 框架。

> 我将使用一个更具体的例子,因为它更容易谈论。假设UpdateValue实际上位于零售系统中的某个位置,用新的美分值更新旧价格。

也许您的类负责提供当前价格或历史价格 - 我们称之为PriceProvider。也许它知道如何为收据提供退款价格。也许它知道如何为白色冰箱冰柜提供一系列价格。它还知道如何使用便士中提供的新价格更新价格。如果这是它的工作,那么它不应该知道价格存储在哪里,因为它已经有一个责任(单一责任原则)。它应该将检索价格的责任委托给其他东西,它的工作是将业务请求转换为对知道价格存储位置的参数化调用。

或者,也许您的类负责从数据库中检索价格并将价格更新到数据库 - 一个DatabasePriceRepository。它知道如何查找日期和项目的价格,或者获取日期和类别的价格列表,但它不在乎你为什么要查找它。如果这是你的类,它与数据库紧密耦合 - 这是它的唯一责任 - 所以没有必要嘲笑这一点,否则该类将没有价值。相反,您可以通过执行集成测试或编写全栈方案甚至手动测试来测试它。

我曾在使用 Hibernate 的项目中工作,并通过实例化不同的对象来测试配置文件。我们将Hibernate连接到内存数据库来执行此操作。这非常快,给了我们非常快的反馈!我还曾在有从 UI 到数据库的端到端场景的地方工作过,更多的地方我们通过使用 HTTP 请求命中某些服务并检查响应来测试某些服务,还有一些地方我们手动测试了它。

不管你是否对这个类进行单元测试,在某些时候你必须在生产环境中接触数据库,我相信你会想在上线之前对其进行测试。如果对此类进行单元测试,则需要在其他地方执行集成测试,无论是自动测试还是其他测试。