单元测试 - 接口,模拟和依赖注入

时间:2017-01-04 10:49:06

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

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

enter image description here

在Business层中,我创建了一个实现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. 最后,如果我想在这个例子中模拟,它会在哪里出现,有什么优势?

1 个答案:

答案 0 :(得分:1)

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

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

  5. 回复关于嘲笑的评论:
       您需要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);
    }