1

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

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

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

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

5 回答 5

3

您可以简单地测试您的方法是否成功调用数据库。Mocking 倾向于关注这一点,即您期望发生的呼叫实际上确实发生了。

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

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

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

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

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

于 2012-06-06T14:24:56.823 回答
1

您的对象的职责是将值传递给它的依赖项(即数据库)。所以,注入一些抽象,它代表你的对象的数据库:

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框架。

于 2012-06-06T14:29:23.677 回答
1

实际上,根据纯粹的定义,涉及数据库的测试不是“单元测试”而是“集成测试”。

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

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

于 2012-06-06T14:30:17.447 回答
1

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

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

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

这些类型的测试中的每一种都有它的位置,您通常希望同时拥有它们。

于 2012-06-06T14:41:42.697 回答
1

我将使用一个更具体的例子,因为它会更容易谈论。假设这UpdateValue实际上是在零售系统的某个地方,用新的美分值更新旧价格。

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

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

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

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

于 2012-06-06T15:00:58.993 回答