C# Moq:如何模拟特性和副作用?

C# Moq:如何模拟特性和副作用?,c#,unit-testing,testing,mocking,moq,C#,Unit Testing,Testing,Mocking,Moq,这是一个测试场景 当孩子超过3岁时,IsAdult==true 我要用最小起订量测试。但是失败,因为Age总是根据设置返回2 我如何测试这个?嘲笑是个坏主意吗 不打算使用调试方法或暴力 public class Kid { public virtual float Age { get; private set; } = 0; public bool IsAdult { get; private set; } private float _ageIncreasePerHou

这是一个测试场景

当孩子超过3岁时,
IsAdult==true

我要用最小起订量测试。但是失败,因为
Age
总是根据设置返回
2

我如何测试这个?嘲笑是个坏主意吗

不打算使用调试方法或暴力

public class Kid
{
    public virtual float Age { get; private set; } = 0;
    public bool IsAdult { get; private set; }
    private float _ageIncreasePerHour = 1;

    public void OnHourPass()
    {
        Age += _ageIncreasePerHour;

        if (Age >= 3)
        {
            IsAdult = true;
        }
    }

    public void Debug_SetAge(float newAge)
    {
        Age = newAge;
    }
}

public class Test_Kid
{
    //fail. how can i mocking?
    [Test]
    public void WhenAgeOver3_Kid_IsAdult_UseMocking()
    {
        var mockKid = new Mock<Kid> { CallBase = true };
        mockKid.Setup(x => x.Age).Returns(2);
        Assert.AreEqual(2, mockKid.Object.Age);
        mockKid.Object.OnHourPass();
        Assert.AreEqual(3, mockKid.Object.Age); // but 2.
        Assert.AreEqual(true, mockKid.Object.IsAdult); // but false.
    }

    //success. but not intended. Debug-Method is anti pattern!
    [Test]
    public void WhenAgeOver3_Kid_IsAdult_UseDebugMethod()
    {
        var newKid = new Kid();
        newKid.Debug_SetAge(2);
        newKid.OnHourPass();
        Assert.AreEqual(true, newKid.IsAdult);
    }

}
公共类儿童
{
公共虚拟浮点年龄{get;private set;}=0;
公共布尔IsAdult{get;私有集;}
私人浮动_ageIncreasePerHour=1;
在HourPass()上的公共void
{
年龄+=\u年龄每小时增加一次;
如果(年龄>=3)
{
IsAdult=true;
}
}
公共无效调试设置(浮动新设置)
{
年龄=新年龄;
}
}
公开班级考试
{
//失败。我怎么能嘲笑你呢?
[测试]
当管理者3(你的孩子)是成年人(你使用嘲弄)时公共无效
{
var mockKid=newmock{CallBase=true};
mockKid.Setup(x=>x.Age).Returns(2);
AreEqual(2,mockKid.Object.Age);
mockKid.Object.OnHourPass();
Assert.AreEqual(3,mockKid.Object.Age);//但是2。
Assert.AreEqual(true,mockKid.Object.IsAdult);//但是false。
}
//成功。但不是有意的。调试方法是反模式的!
[测试]
当管理者3_Kid_IsAdult_UseDebugMethod()时公共无效
{
var newKid=newKid();
newKid.Debug_设置(2);
newKid.OnHourPass();
断言.AreEqual(对,newKid.IsAdult);
}
}

Moq无法与
年龄
属性的私有setter一起工作,因此在使用Moq时会遇到困难,因为如果您试图设置属性的setter来记录行为(副作用),它将引发异常

但是,您可以避免完全使用Moq,并将值设置为测试主题的包装器

使用
PrivateObject.SetProperty
可以设置测试的初始值,然后根据需要执行测试

假定

public class Kid {
    public virtual float Age { get; private set; }
    public bool IsAdult { get; private set; }
    private float _ageIncreasePerHour = 1;

    public void OnHourPass() {
        Age += _ageIncreasePerHour;
        if (Age >= 3) {
            IsAdult = true;
        }
    }
}
测试看起来是这样的

public void _WhenAgeOver3_Kid_IsAdult() {
    //Arrange
    var initialAge = 2;
    var expectedAge = initialAge + 1;
    var kid = new Kid();
    var wrapper = new PrivateObject(kid);            
    wrapper.SetProperty("Age", initialAge);
    Assert.AreEqual(initialAge, kid.Age);

    //Act
    kid.OnHourPass();

    //Assert
    Assert.AreEqual(expectedAge, kid.Age); // should be 3.
    Assert.AreEqual(true, kid.IsAdult); // should be true.
}
如果您能够更改主题,另一个选项是重构主题,在其构造函数中使用可选参数来设置初始
Age

public class Kid {

    public Kid(int initialAge = 0) {
        Age = initialAge;
    }

    public virtual float Age { get; private set; }
    public bool IsAdult { get; private set; }
    private float _ageIncreasePerHour = 1;

    public void OnHourPass() {
        Age += _ageIncreasePerHour;
        if (Age >= 3) {
            IsAdult = true;
        }
    }
}
这将允许对测试进行重构,以及

public void _WhenAgeOver3_Kid_IsAdult() {
    //Arrange
    var initialAge = 2;
    var expectedAge = initialAge + 1;
    var kid = new Kid(initialAge);
    Assert.AreEqual(initialAge, kid.Age);

    //Act
    kid.OnHourPass();

    //Assert
    Assert.AreEqual(expectedAge, kid.Age); // should be 3.
    Assert.AreEqual(true, kid.IsAdult); // should be true.
}

你为什么要嘲笑我?你不能创建一个实例,调用
OnHourPass()
直到年龄超过3岁,然后断言
isadalt
?这只是一个例子。在我的代码中,被测试类有许多依赖项。我同意你的想法,(直到年龄满足条件为止),但在一些更复杂的情况下,可能会很困难。。。