5

我需要开发一个相当简单的算法,但我有点困惑如何最好地为它编写测试。

一般描述:用户需要能够删除计划。计划有与之关联的任务,这些也需要删除(只要它们尚未完成)。

伪代码作为算法的行为方式:

   PlanController.DeletePlan(plan)
     =>
     PlanDbRepository.DeletePlan()
      ForEach Task t in plan.Tasks
          If t.Status = Status.Open Then
            TaskDbRepository.DeleteTask(t)
          End If
      End ForEach

现在据我了解,单元测试不应该接触数据库或通常需要访问任何外部系统,所以我猜我在这里有两个选择:

1) 模拟存储库调用,并检查它们是否作为断言被调用了适当的次数

2) 为两个存储库类创建存根,手动设置它们的删除标志,然后验证适当的对象已被标记为删除。

在这两种方法中,最大的问题是:我究竟在这里测试什么?这样的测试会给我带来什么额外的价值?

对此的任何见解将不胜感激。这在技术上与任何特定的单元测试框架无关,尽管我们有 RhinoMocks 可以使用。但我更喜欢一般性的解释,这样我就可以正确地解决这个问题。

4

7 回答 7

4

您应该模拟存储库,然后在单元测试中构建一个包含打开和关闭任务的虚拟计划。然后调用传递此计划的实际方法,最后验证该DeleteTask方法是否使用正确的参数调用(只有状态 = Open 的任务)。这样,您将确保您的方法仅删除了与此计划关联的未完成任务。也不要忘记(可能在单独的单元测试中)通过断言DeletePlan已在您传递的对象上调用该方法来验证计划本身已被删除。

于 2010-12-08T10:49:39.713 回答
2

为了补充达林的回答,我想告诉您您实际测试的是什么。里面有一些业务逻辑,例如状态检查。

这个单元测试现在可能看起来有点愚蠢,但是你的代码和模型的未来变化呢?这个测试是必要的,以确保这个看似简单的功能将始终保持工作。

于 2010-12-08T10:54:20.947 回答
2

正如您所指出的,您正在测试算法中的逻辑是否按预期运行。您的方法是正确的,但请考虑未来 - 几个月后,该算法可能需要更改,不同的开发人员将其切碎并重做,缺少关键逻辑。您的单元测试现在将失败,并且开发人员将被警告他们的错误。单元测试在开始时很有用,接下来的几周/几个月/几年也是如此。

如果要添加更多,请考虑如何处理失败。让您的数据库模拟在删除命令上抛出异常,测试您的算法是否正确处理此问题。

于 2010-12-08T10:55:38.073 回答
2

您的测试提供的额外价值是检查您的代码是否执行正确的操作(在这种情况下,删除计划,删除与计划关联的任何打开的任务,并保留与计划关联的任何关闭的任务)。

假设您已经为您的 Repository 类进行了测试(即,当对它们调用 delete 时它们会做正确的事情),那么您需要做的就是检查是否正确调用了 delete 方法。

您可以编写的一些测试是:
删除空计划是否只调用DeletePlan
删除具有两个未完成任务的计划是否需要DeleteTask两个任务?
删除包含两个已关闭任务的计划根本不会调用DeleteTask吗?
是否会在正确的任务上调用一次具有一个打开和一个关闭任务的计划DeleteTask

编辑:不过,我会使用达林的答案作为解决方法。

于 2010-12-08T10:56:06.310 回答
1

有趣的是,我发现单元测试有助于将注意力集中在规范上。为此,让我问这个问题......

如果我有一个包含 3 个任务的计划:

Plan1 {
 Task1: completed
 Task2: todo
 Task3: todo
}

我打电话给他们删除,计划会发生什么?

Plan1 : ?
Task1: not deleted
Task2: deleted
Task3: deleted

计划 1 被删除,孤立任务 1?还是以其他方式标记为已删除?

这是我在单元测试中看到的值的很大一部分(尽管它只是 4 个值中的一个:1)规范 2)反馈 3)回归 4)粒度

至于如何测试,我根本不建议模拟。我会考虑一个两部分的方法第一个看起来像

public void DeletePlan(Plan p)
{ 
  var objectsToDelete = GetDeletedPlanObjects(p);
  DeleteObjects(objectsToDelete);
} 

而且我不会测试这种方法。我将测试 GetDeletedPlanObjects 方法,它无论如何都不会触及数据库,并允许您发送类似上述情况的场景......然后我会用 www.approvaltests.com 断言,但这是另一个故事:- )

快乐测试,卢埃林

于 2013-04-23T12:17:09.523 回答
0

IMO,您可以围绕抽象编写单元测试,PlanRepository并且相同的测试也应该对测试数据库中的数据完整性很有用。

例如,您可以编写一个测试 -

void DeletePlanTest()
{
    PlanRepository repo = new PlanDbRepository("connection string");
    repo.CreateNewPlan(); // create plan and populate with tasks
    AssertIsTrue(repo.Plan.OpenTasks.Count == 2); // check tasks are in open state
    repo.DeletePlan();
    AssertIsTrue(repo.Plan.OpenTasks.Count == 0);
}

即使您的存储库删除了计划并且您的数据库通过级联删除触发器删除了相关任务,此测试也将起作用。

这种测试的价值是测试是否运行,PlanDbRepository或者MockRepository它仍然会检查行为是否正确。因此,当您更改任何存储库代码甚至数据库架构时,您可以运行测试以检查是否有任何损坏。

您可以创建涵盖存储库所有可能行为的此类测试,然后使用它们来确保您的任何更改都不会破坏实现。

您还可以使用具体的存储库实例参数化此测试,并将它们重用于测试存储库的任何未来实现。

于 2010-12-08T11:06:03.113 回答
0

我不会为此编写单元测试,因为对我来说这不是测试行为而是实现。如果在某些时候您希望该行为不删除任务,而是将它们设置为“禁用”或“忽略”状态,那么您的单元测试将失败。如果您以这种方式测试所有控制器,您的单元测试非常脆弱,需要经常更改。

如果您想为此测试业务逻辑并将删除的实现细节留给类本身,请将业务逻辑重构为“TaskRemovalStrategy”。

于 2010-12-08T10:58:06.357 回答