如何进行单元测试?

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,但我更喜欢一般性的解释,以便我能够正确地理解这个问题。
7个回答

4
你应该在单元测试中模拟存储库并构建一个包含开放和关闭任务的虚拟计划。然后调用实际的方法,将此计划作为参数传递,并在最后验证 DeleteTask 方法是否以正确的参数(只有状态为 Open 的任务)被调用。这样,您将确保只有与此计划关联的开放任务已被您的方法删除。另外,别忘了(可能是在单独的单元测试中)通过断言对传递的对象调用 DeletePlan 方法来验证计划本身是否已被删除。

2

除了Darin的回答之外,我想告诉你实际上你正在测试什么。其中有一些业务逻辑,例如状态检查。

这个单元测试现在可能看起来有点愚蠢,但是将来对你的代码和模型进行更改呢?为了确保这个看似简单的功能始终正常工作,这个测试是必要的。


2
正如您所指出的那样,您正在测试算法中的逻辑是否按预期运行。您的方法是正确的,但请考虑未来——几个月后,可能需要更改此算法,不同的开发人员对其进行修改并重新编写,错过了关键的逻辑部分。现在,您的单元测试将失败,并将向开发人员提示他们的错误。单元测试在开始时以及未来的数周/数月/数年都非常有用。
如果您想添加更多内容,请考虑如何处理失败。让您的DB模拟在删除命令上抛出异常,测试算法是否能够正确处理。

2
您的测试提供的额外价值是检查您的代码是否做了正确的事情(在这种情况下,删除计划,删除与计划相关的任何打开任务,并保留与计划相关的任何关闭任务)。
假设您已经为存储库类编写了测试(即当对它们调用delete时,它们会执行正确的操作),那么您只需要检查是否适当地调用了delete方法。
您可以编写一些测试,例如: - 删除一个空计划是否仅调用DeletePlan? - 删除具有两个打开任务的计划是否为两个任务都调用DeleteTask? - 删除具有两个关闭任务的计划是否根本不调用DeleteTask? - 删除具有一个打开任务和一个关闭任务的计划是否在正确的任务上调用DeleteTask?
编辑:我建议按照Darin的答案进行操作。

1
有趣的是,我发现单元测试有助于将注意力集中在规范上。为此,让我问一个问题...
如果我有一个包含3个任务的计划:
Plan1 {
 Task1: completed
 Task2: todo
 Task3: todo
}

如果我对它们调用delete,Plan应该发生什么?
Plan1 : ?
Task1: not deleted
Task2: deleted
Task3: deleted

计划1被删除了吗?导致任务1变成孤立状态?还是其它方式标记为已删除?

这是我在单元测试中看到的价值的重要部分(虽然只有四个价值中的一个): 1)规范 2)反馈 3)回归 4)细粒度

至于如何进行测试,我不建议使用模拟。我会考虑采用两部分方法。 第一部分应该是:

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

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

愉快的测试, Llewellyn


0

我认为你可以围绕抽象的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运行测试,它仍将检查行为是否正确。因此,当您更改任何存储库代码甚至数据库架构时,可以运行测试以检查是否存在问题。

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

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


0

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接