【问题标题】:How do i unit test this business logic?我如何对这个业务逻辑进行单元测试?
【发布时间】:2009-04-07 13:07:20
【问题描述】:

我有一个方法可以接收一个对象并将其保存到数据库中。但是,在我保存对象之前,我会执行以下操作...

(伪代码)

if (IsAuthenticated)
{
   foo.UserId = AuthenticatedUser.Id;
}
else
{
   foo.AnonEmail = "Jon@World-Domination";
   foo.AnonName = "Jon Skeet";
}

try
{
    _fooService.Save(foo);
}
catch
{
    // Some view, with error stuff now added to 
    return View(...); ViewData.ModelState.
}

// all good, redirect to the proper next view.
return RedirectToAction(...);

该代码运行良好,但我不确定如何编写两个单元测试才能成功。 a) 用户使用有效数据进行身份验证 b) 用户未使用有效数据进行身份验证。

我不确定该怎么做的原因是,这两种情况都返回相同的 RedirectToAction(..) 视图对象。所以我可以成功地测试它..但它并没有告诉我保存的对象是否包含经过身份验证的用户 ID 或匿名信息。就像我想要第一个单元测试说的那样

  • 起订一个经过身份验证的用户
  • 调用方法
  • 测试结果是否为 RedirectToActionView
  • 测试持久化的 foo 对象是否包含 moq 的用户 ID。

想法?

更新

常见的建议是我模拟 fooService。我目前正在使用依赖注入和起订量,所以有人可以告诉我如何使用起订量吗?不过,我不确定 DI 在这里有多重要???

【问题讨论】:

  • 3 票投给 moq foo 服务至今...
  • 是的 :) 但我不知道如何以编程方式执行此操作:(

标签: asp.net-mvc unit-testing moq


【解决方案1】:

我会模拟_fooService 对象,并测试它作为测试的一部分接收到的内容。这样,您的周围代码保持不变并且不受影响,并且通过检查 _fooService 收到的内容,您可以断言行为是否符合预期。在这种情况下,返回对象不感兴趣。

你如何模拟你的 _fooService ?您可以实现自己的“测试”版本(遵循与真实世界版本相同的接口),或使用模拟框架。无论您使用哪种方法,您的上面的代码都需要使用_fooService 的给定实现进行配置(通常在构建中 - 请参阅dependency injection 了解有关其工作原理的更多信息)

【讨论】:

  • 目前我正在为我的 DI 和 moq 使用结构图进行模拟。我几乎不明白如何很好地使用起订量 - 你能否更新你的答案以包含一些起订量代码示例,关于如何做到这一点?
  • 恐怕我还没用起订量。我是从 Java 的角度来解决这个问题的,所以我只有使用 Java 工具进行模拟的经验。但是,您可以轻松地将 2 个版本的 _fooService 组合在一起。一个做真正的工作,一个只检查传递给它的结果。这有帮助吗?
  • 当然可以,我已经有了。我有假存储库和真实存储库。至于服务,我不假装那些。 DI 确定要使用的存储库。我仍然需要有人向我展示如何使用 MOQ 来验证结果是否通过。
【解决方案2】:

您可以模拟 _fooService.Save(foo) 并检查提供的 foo。

【讨论】:

  • 请检查我在 Brian 的回答中的评论好吗?
【解决方案3】:

也许您发现很难测试对象,因为您在一个方法中发生了多个活动。

这里的总体主题是控制器逻辑。

  1. 用用户信息装饰域对象
  2. 坚持更新逻辑
  3. 根据成功/失败确定要渲染的下一个视图

如果您提取另一个对象 (IUserDecoratorService),那么您的代码如下所示

userService.UpdateUserInformation(foo);

try
{
    _fooService.Save(foo);
}
catch
{
    // Some view, with error stuff now added to 
    return View(...); ViewData.ModelState.
}

// all good, redirect to the proper next view.
return RedirectToAction(...);

此方法易于测试,因为它是与 2 种服务的 2 次简单交互以及您已经可以测试的路由决策。

现在您只需要为您的新服务编写测试:

[Test]
public void ShouldDecorateWithUserIdForAuthenticatedUser()
{
    {setup authenticated user}
    :
    service.UpdateUserInformation(foo);

    Assert.AreEqual(expectedId, foo.UserId);
    Assert.IsNull(foo.AnonEmail);
    Assert.IsNull(foo.AnonEName);

}

[Test]
public void ShouldSpoofTheAllKnowingSkeetIfAnonymousUser()
{
    {setup anonymous user}
    :
    service.UpdateUserInformation(foo);

    Assert.AreEqual(UnassignedId, foo.UserId);
    Assert.AreEqual("Jon@World-Domination", foo.AnonEmail);
    Assert.AreEqual("Jon Skeet", foo.AnonName);

}

【讨论】:

    【解决方案4】:

    你仍然有你的对象的引用。在您的单元测试结束时,您不能只断言 foo.AnonEmail 或 UserId 的值是什么吗?

    单元测试不应该接触外部源,(这是一个集成测试)所以如果你走这条路,你应该模拟你的数据源,然后通过你的模拟进行测试。

    【讨论】:

      【解决方案5】:

      你可以在测试中访问 foo 吗?我假设这是课堂上的一个领域。有公共吸气剂吗?如果不是,您可能必须使用反射(我假设它在 asp.net 中可用,但我不太熟悉)

      【讨论】:

        【解决方案6】:

        您想使用 DI 将 fooservice 正确实现到您的对象中,因此在测试时您可以这样做;

        (使用最小起订量)

        [TestMethod]
        public void Jon_Skeet_Is_Saved_If_User_Not_Authenticated()
        {
          bool jonWasSaved = false;
          var mockFooService = new Mock<IFooService>();
          mockFooService
               .Expect(x => x.Save(It.Is<Foo>(foo => foo.AnonName == "Jon Skeet")))
               .Callback(() => jonWasSaved = true;);
        
          FooManager instance = new FooManager(mockFooService.Object);
          Foo testFoo = new Foo();
          testFoo.UserId = 1; // this is not the authenticated id
        
          instance.baa(foo);
        
          Assert.IsTrue(jonWasSaved);
        }
        

        您可能还想传入用于检查 AuthetnicatedUser.Id 的任何服务的模拟版本

        HTH

        【讨论】:

          猜你喜欢
          • 2013-07-27
          • 1970-01-01
          • 1970-01-01
          • 2012-02-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多