简体   繁体   English

我该如何进行单元测试呢?

[英]How would I go about unit testing this?

I need to develop a fairly simple algorithm, but am kindof confused as how to best write a test for it. 我需要开发一个相当简单的算法,但我很困惑如何最好地为它编写测试。

General description: User needs to be able to delete a Plan. 一般说明:用户需要能够删除计划。 Plan has Tasks associated with it, these need to be deleted as well (as long as they're not already done). 计划具有与之关联的任务,这些也需要删除(只要它们尚未完成)。

Pseudo-code as how the algorithm should behave: 伪代码算法应该如何表现:

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

Now as far as I understand it, unit tests are not supposed to touch the Database or generally require access to any outside systems, so I'm guessing I have two options here: 现在据我所知,单元测试不应该触及数据库,或者通常需要访问任何外部系统,所以我猜我有两个选择:

1) Mock out the Repository calls, and check whether they have been called the appropriate number of times as Asserts 1)模拟Repository调用,并检查它们是否被称为Asserts的适当次数

2) Create stubs for both repository classes, setting their delete flag manually and then verify that the appropriate objects have been marked for deletion. 2)为两个存储库类创建存根,手动设置其删除标志,然后验证是否已将相应的对象标记为删除。

In both approaches, the big question is: What exactly am I testing here? 在这两种方法中,最大的问题是:我到底在测试什么? What is the EXTRA value that such tests would give me? 这些测试给我的EXTRA值是多少?

Any insight in this would be highly appreciated. 对此的任何见解都将受到高度赞赏。 This is technically not linked to any specific unit testing framework, although we have RhinoMocks to be used. 虽然我们使用了RhinoMocks,但这在技术上并没有与任何特定的单元测试框架相关联。 But I'd prefer a general explanation, so that I can properly wrap my head around this. 但是我更喜欢一般性的解释,这样我才能正确地解决这个问题。

You should mock the repository and then construct a dummy plan in your unit test containing both Open and Closed tasks. 您应该模拟存储库,然后在单元测试中构建包含Open和Closed任务的虚拟计划。 Then call the actual method passing this plan and at the end verify that the DeleteTask method was called with correct arguments (tasks with only status = Open). 然后调用传递此计划的实际方法,最后验证是否使用正确的参数调用了DeleteTask方法(只有status = Open的任务)。 This way you would ensure that only open tasks associated to this plan have been deleted by your method. 这样,您可以确保方法仅删除与此计划关联的打开任务。 Also don't forget (probably in a separate unit test) to verify that the plan itself has been deleted by asserting that the DeletePlan method has been called on the object your are passing. 另外,请不要忘记(可能在单独的单元测试中)通过断言已经传递的对象上调用了DeletePlan方法来验证计划本身是否已被删除。

To add to Darin's answer I'd like to tell you what you are actually testing. 为了增加Darin的答案,我想告诉你你在测试什么。 There's a bit of business logic in there, for example the check on the status. 那里有一些业务逻辑,例如检查状态。

This unit test might seem a bit dumb right now, but what about future changes to your code and model? 这个单元测试现在看起来有点愚蠢,但是你的代码和模型的未来变化呢? This test is necessary to make sure this seemingly simple functionality will always keep working. 此测试是必要的,以确保这个看似简单的功能将始终保持工作。

As you noted, you are testing that the logic in the algorithm behaves as expected. 如您所述,您正在测试算法中的逻辑是否按预期运行。 Your approach is correct, but consider the future - Months down the road, this algorithm may need to be changed, a different developer chops it up and redoes it, missing a critical piece of logic. 你的方法是正确的,但考虑未来 - 未来几个月,这个算法可能需要改变,一个不同的开发人员砍掉它并重做它,错过了一个关键的逻辑。 Your unit tests will now fail, and the developer will be alerted to their mistake. 您的单元测试现在将失败,开发人员将收到他们的错误警报。 Unit testing is useful at the start, and weeks/months/years down the road as well. 单元测试在一开始就很有用,也可以在几周/几个月/几年内完成。

If you want to add more, consider how failure is handled. 如果要添加更多内容,请考虑如何处理故障。 Have your DB mock throw an exception on the delete command, test that your algorithm handles this correctly. 让你的数据库模拟在delete命令上抛出异常,测试你的算法是否正确处理了这个问题。

The extra value provided by your tests is to check that your code does the right things (in this case, delete the plan, delete any open tasks associated with the plan and leave any closed tasks associated with the plan). 测试提供的额外价值是检查您的代码是否做了正确的事情(在这种情况下,删除计划,删除与计划相关的任何打开的任务,并保留与计划相关的任何已完成的任务)。

Assuming that you have tests in place for your Repository classes (ie that they do the right things when delete is called on them), then all you need to do is check that the delete methods are called appropriately. 假设您已经为Repository类准备了测试(即,当他们调用delete时他们做了正确的事情),那么您需要做的就是检查是否正确调用了delete方法。

Some tests you could write are: 您可以编写的一些测试是:
Does deleting an empty plan only call DeletePlan ? 删除空计划只会调用DeletePlan吗?
Does deleting a plan with two open tasks call DeleteTask for both tasks? 删除具有两个打开任务的计划是否为这两个任务调用DeleteTask
Does deleting a plan with two closed tasks not call DeleteTask at all? 删除具有两个已关闭任务的计划是否根本不调用DeleteTask
Does deleting a plan with one open and one closed task call DeleteTask once on the right task? 删除具有一个打开和一个关闭任务的计划是否在正确的任务上调用DeleteTask一次?

Edit: I'd use Darin's answer as the way to go about it though. 编辑:我会用Darin的答案来解决这个问题。

Interesting, I find unit testing helps to focus the mind on the specifications. 有趣的是,我发现单元测试有助于将注意力集中在规范上。 To that end let me ask this question... 为此我要问这个问题......

If I have a plan with 3 tasks: 如果我有一个包含3个任务的计划:

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

and I call delete on them, what should the happen to the Plan? 我打电话给他们删除,计划会发生什么?

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

Is plan1 deleted, orphaning task1? plan1被删除了,orphaning task1? or is it otherwise marked deleted?. 或者是否标记为已删除?

This is a big part of the Value I see in unit tests (Although it is only 1 of the 4 values: 1) Spec 2) Feedback 3) Regression 4) granularity 这是我在单元测试中看到的值的一个重要部分(尽管它只是4个值中的1个:1)规范2)反馈3)回归4)粒度

As for how to test, I wouldn't suggest mocks at all. 至于如何测试,我根本不会建议模拟。 I would consider a 2 part method The first would look like 我会考虑一个2部分方法第一个看起来像

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

And I wouldn't test this method. 我不会测试这种方法。 I would test the method GetDeletedPlanObjects, which wouldn't touch the database anyways, and would allow you to send in scenarios like the above situation.... which I would then assert with www.approvaltests.com , but that's another story :-) 我会测试方法GetDeletedPlanObjects,它无论如何都不会触及数据库,并且允许你发送上述情况的场景....然后我会用www.approvaltests.com断言,但这是另一个故事: - )

Happy Testing, Llewellyn 快乐的测试,卢埃林

I would not write unit tests for this because to me this is not testing behaviour but rather implementation. 我不会为此编写单元测试,因为对我来说这不是测试行为而是实现。 If at some point you want to chance the behaviour to not delete the tasks but rather set them to a state of 'disabled' or 'ignored', your unit tests will fail. 如果在某些时候您希望机会不删除任务,而是将它们设置为“禁用”或“忽略”状态,则单元测试将失败。 If you test all controllers this way your unit tests are very brittle and will need to be changed often. 如果以这种方式测试所有控制器,则单元测试非常脆弱,需要经常更换。

Refactor out the business logic to a 'TaskRemovalStrategy' if you want to test the business logic for this and leave the implementation details of the removal up to the class itself. 如果要为此测试业务逻辑并将删除的实现细节留给类本身,请将业务逻辑重构为“TaskRemovalStrategy”。

IMO you can write your unit tests around the abstract PlanRepository and the same tests should be useful in testing the data integrity in the database also. IMO您可以围绕抽象PlanRepository编写单元测试,并且相同的测试也可用于测试数据库中的数据完整性。

For example you could write a test - 例如,你可以写一个测试 -

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);
}

This test will work even if your repository deletes the plan and your database deletes the related tasks via a cascaded delete trigger. 即使您的存储库删除了计划,并且您的数据库通过级联删除触发器删除相关任务,此测试也会起作用。

The value of such test is whether the test is run for PlanDbRepository or a MockRepository it will still check that the behavior is correct. 此类测试的价值在于是否为PlanDbRepositoryMockRepository运行测试,它仍将检查行为是否正确。 So when you change any repository code or even your database schema, you can run the tests to check nothing is broken. 因此,当您更改任何存储库代码甚至数据库架构时,您可以运行测试以检查是否有任何损坏。

You can create such tests which cover all the possible behaviors of your repository and then use them to make sure that any of your changes do not break the implementation. 您可以创建此类测试,其中包含存储库的所有可能行为,然后使用它们来确保您的任何更改都不会破坏实现。

You can also parameterize this test with a concrete repository instance and reuse them the test any future implementations of repositories. 您还可以使用具体的存储库实例参数化此测试,并将其重用于测试任何未来的存储库实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM