【问题标题】:Unit Testing - Interfaces, Mocking and Dependancy Injection [closed]单元测试 - 接口、模拟和依赖注入
【发布时间】:2017-01-04 10:49:05
【问题描述】:

我试图了解单元测试的某些方面,因此我创建了一个具有以下架构的简单解决方案:

在业务层中,我创建了一个实现 IFizzBu​​zz 接口的 FizzBu​​zz 类 (FizzBu​​zzService)。

namespace SampleApp2017.Business
{
public class FizzBuzzService : IFizzBuzz
{
    public bool IsFizzBuzz(int num)
    {
        if (num % 3 == 0 || num % 6 == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public bool IsFizz(int num)
    {
       return (num % 3 == 0 ? true : false);
    }

    public bool IsBuzz(int num)
    {
        if (num % 6 == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}
}

(以及这里的接口细节供参考)

namespace SampleApp2017.Business
{
public interface IFizzBuzz
{

    bool IsFizzBuzz(int num);

    bool IsFizz(int num);

    bool IsBuzz(int num);
}
}

在这个示例解决方案中,WebUI 和 ConsoleApp 都使用依赖注入来使用 FizzBu​​zz 服务,我理解为什么这是必要的,但是当涉及到对服务进行单元测试时,我感到很困惑。

下面是我为 FizzBu​​zz 方法设置的测试:

  [TestMethod]
    public void fizzbuzz_should_return_true()
    {
        //Arrange
        //Bring in the business rules
        IFizzBuzz _fbservice = new FizzBuzzService();

        //Act
        /// Send 12 to service, should return true
        bool resultof12 = _fbservice.IsFizzBuzz(12);

        //Assert
        Assert.AreEqual(resultof12, true);
    }

我现在正在寻找有关运行单元测试的“最佳实践”的一些指导。

  1. 上面的测试看起来正常吗? (运行良好)
  2. 在单元测试中使用 DI 是否常见?我确定是在测试一个类的具体实现吗?
  3. 在单元测试中,我需要对接口进行编程吗?会有什么问题:

    FizzBu​​zzService _fbservice = new FizzBu​​zzService();

  4. 最后,如果我想在这个例子中模拟,它从哪里来,有什么优势?

【问题讨论】:

  • @Nkosi 根据您的建议更新

标签: c# unit-testing dependency-injection mocking


【解决方案1】:
  1. 可以,但建议为参数添加更多测试值。
    选择更具描述性的方法名称,根据当前名称我可以认为您的方法总是返回 true。
  2. 在您的情况下,您不需要 DI,因为您的实现没有其他依赖项。
    如果您在单元测试中谈论 DI 容器,那么我宁愿不使用它们并在测试中手动传递依赖项。
  3. 我认为最好测试这个类的实际实现。它向其他读者/开发人员提供有关您的测试的明确信息

    FizzBuzzService _fbservice = new FizzBuzzService(); //  better then IBuzzService
    
  4. 在当前示例中,您根本不需要 Mock。因为您的班级没有其他依赖项。
    当您需要提供某些逻辑不属于当前测试的逻辑时,请使用 Mocks。
    Mocks 可以作为构造函数参数传递,也可以作为方法参数传递,也可以作为当前类的属性。

回复关于嘲笑的评论:
您需要 Mock 是因为不与数据库交互。单元测试必须快,如果他们不快,开发人员将不会运行它们。因此,您不希望您的单元测试与数据库或其他 IO 设备通信。
您不需要模拟框架 - 您可以通过实现带有测试逻辑的接口来创建自己的模拟

public interface IUserDataService
{
    User GetById(int id);
}

public class BonusCalculator
{
    private readonly IUserdataService _dataService;
    public BonusCalculator(IUserDataService dataService)
    {
        _dataService = dataService;
    }

    public int CalculateBonusForUser(int userId)
    {
        const int BONUS = 100;
        var deservedBonus = 0;
        var user = _dataService.GetById(userId);

        if (user.IsDeservedBonus)
        {
            deservedBonus += BONUS;
        }

        return deservedBonus;
    }
}

现在你想测试一下,如果 UserDataService 返回的用户应得的奖励返回值将等于 100。

因此您可以创建自己的GetById 的“测试”实现

public class FakeUserDataService : IUserDataService
{
    public User DummyUser { get; set; }
    public User GetById(int id) => DummyUser;
}

然后你可以测试CalculateBonusForUser的逻辑而不用关心用户如何返回,因为这不关心这个类

public void CalculateBonusForUser_ShouldReturn100_WhenUserIsDeservedEqualsTrue()
{
    var user = new User { IsDeserved = true };
    var fakeDataService = new FakeUserDataService { DummyUser = user };
    var calculator = new BonusCalculator(fakeDataService);

    var actualBonus = calculator.CalculateBonusForUser(1);

    Assert(actualBonus, 100);
}

【讨论】:

  • 感谢@Fabio,关于上面的 4,这现在开始变得更有意义了,在我的其他解决方案中,我使用带有 UOW 的存储库模式与数据源进行交互。我是否正确地说我需要模拟与我的数据库交互的 UOW 接口才能测试诸如 _userService.GetUser(45) 之类的服务?
  • 检查更新的答案...
猜你喜欢
  • 1970-01-01
  • 2012-03-12
  • 1970-01-01
  • 2018-03-03
  • 2021-06-19
  • 1970-01-01
  • 2017-11-03
相关资源
最近更新 更多