无法在没有标记太多虚拟方法的情况下看不到如何模拟或编写单元测试

时间:2018-02-02 21:54:24

标签: c# unit-testing moq

我们开始在我们的解决方案中添加单元测试,我正在使用Moq编写单元测试。

我在没有嘲笑的情况下编写了我的前10个测试并且很好,但是现在我已经达到了需要模拟的测试,我尝试测试的每个方法似乎都需要部分模拟(好吧;我测试的方法需要实际实现并且它调用的每个其他内部方法都需要伪造。)

问题是编写我的测试我必须对原始代码进行太多更改才能使其可测试。向每个类添加接口是一回事,从方法中删除依赖关系并将它们作为参数也是可以接受的(虽然我猜想会经常发生),但似乎我需要标记每个依赖方法我需要假装为虚拟。这对原始代码进行了太多改动,只是为了使其可测试。

因此;我的问题是,这是我应该如何做到这一点,还是有一种方法可以在没有这么多变化的情况下编写单元测试,并且还可以虚拟化方法?

这是我写的1单元测试的例子:

public class ContrastControlViewModel : ViewModelBase, IContrastControlViewModel
{
    public void UpdateCompositePropertyValues(String propertyName)
    {
        if (SelectedVideoProcessingSubstream != null)
        {
            switch (propertyName)
            {
                case "ContrastCurve":
                    if (_enableContrastControlUpdate)
                    {
                        UpdateContrastCurve();  // Only updates the local line graph display of the contrast controls
                        _enableContrastControlUpdate = false;
                    }
                    break;
                default:
                    break;
            }
        }
    }
    public IVideoProcessingSubstreamViewModel SelectedVideoProcessingSubstream
    {
        get {...}
        set {...}
    }
}

[TestClass]
public class ContrastControlViewModelTests
{
    [TestMethod]
    public void UpdateCompositePropertyValues()
    {
        //scenarios:                8 tests, could be separated
        foreach (String input_propertyName in new string[]{"ContrastCurve", "other"})
            foreach (var input_SelectedVideoProcessingSubstream in new IVideoProcessingSubstreamViewModel[] { null, (IVideoProcessingSubstreamViewModel)(new Mock<IVideoProcessingSubstreamViewModel>()).Object})
                foreach (bool input_enableContrastControlUpdate in new bool[]{ true, false})
                {
                    //Arrange:
                    bool UpdateContrastCurve_called = false;
                    var vm = new Mock<ContrastControlViewModel>(10, 10, 200, 200, 240, 240) { CallBase = true };
                    vm.SetupProperty(m => m.SelectedVideoProcessingSubstream);          //throws NotSupportedException
                    vm.SetupProperty(m => m._enableContrastControlUpdate);
                    vm.Setup(m => m.UpdateContrastCurve()).Callback(() => UpdateContrastCurve_called = true);

                    //Act:
                    vm.Object._enableContrastControlUpdate = input_enableContrastControlUpdate;
                    vm.Object.SelectedVideoProcessingSubstream = input_SelectedVideoProcessingSubstream;
                    vm.Object.UpdateCompositePropertyValues(input_propertyName);

                    //Assert:
                    Assert.IsTrue(UpdateContrastCurve_called == (input_propertyName == "ContrastCurve" 
                        && input_SelectedVideoProcessingSubstream != null && input_enableContrastControlUpdate == true));
                }         
    }
}

因此;现在我得到一个NotSupportedException,因为&#34; SelectedVideoProcessingSubstream&#34;必须虚拟伪造。如果我开始这样做,一切都会被标记为虚拟。后来,我甚至将其他类的方法标记为虚拟。

1 个答案:

答案 0 :(得分:0)

理想情况下,您不需要模拟测试中的课程。

模拟ContrastControlViewModel,以便您可以看到UpdateContrastCurve被调用是一种气味,它们是不同的关注点,不应该属于同一类。

我会这样重构:

  • 有一个传入接口的构造函数,它是包含UpdateContrastCurve方法的新类/服务的接口。

  • 在创建ContrastControlViewModel测试的实例时,将测试更改为模拟此新类/服务。

  • 断言在给定UpdateContrastCurve方法的参数的情况下调用了模拟方法UpdateCompositePropertyValues

这些类将更改为如下所示:

public class ContrastControlViewModel : ViewModelBase, IContrastControlViewModel
{
    private IUpdateContrastCurveService _updateContrastCurveService;

    public ContrastControlViewModel(IUpdateContrastCurveService updateContrastCurveService)
    {
        _updateContrastCurveService = updateContrastCurveService;
    }

    public void UpdateCompositePropertyValues(String propertyName)
    {
        if (SelectedVideoProcessingSubstream != null)
        {
            switch (propertyName)
            {
                case "ContrastCurve":
                    if (_enableContrastControlUpdate)
                    {
                        _updateContrastCurveService.UpdateContrastCurve();  // Only updates the local line graph display of the contrast controls
                        _enableContrastControlUpdate = false;
                    }
                    break;
                default:
                    break;
            }
        }
    }

    public IVideoProcessingSubstreamViewModel SelectedVideoProcessingSubstream {get;set;}
}

测试看起来像这样:

[TestClass]
public class ContrastControlViewModelTests
{
    [TestMethod]
    public void UpdateCompositePropertyValues_Should_update_contrast_curve_when_ContrastCurve_and_enabled()
    {
        //Arrange:
        IUpdateContrastCurveService updateContrastCurveService = new Mock<IUpdateContrastCurveService>();
        var vm = new ContrastControlViewModel(updateContrastCurveService.Object);
        var propertyName = "ContrastCurve";

        //Act:
        vm.UpdateCompositePropertyValues(propertyName);

        //Assert:
        Assert.IsTrue(updateContrastCurveService.Verify(x=>x.UpdateContrastCurve()));       
    }
}