C# 嵌套的必需对象

C# 嵌套的必需对象,c#,unit-testing,moq,C#,Unit Testing,Moq,我试图编写一个单元测试,但遇到了一个问题,即每个模拟对象都依赖于另外3个对象。这看起来像这样 var objC = new Mock<IObjectC>(IObjectG, IObjectH); var objB = new Mock<IObjectB>(IObjectE, IObjectF); var objA = new Mock<IObjectA>(IObjectB, IObjectC, IObjectD); var objC=newmock(IObj

我试图编写一个单元测试,但遇到了一个问题,即每个模拟对象都依赖于另外3个对象。这看起来像这样

var objC = new Mock<IObjectC>(IObjectG, IObjectH);
var objB = new Mock<IObjectB>(IObjectE, IObjectF);
var objA = new Mock<IObjectA>(IObjectB, IObjectC, IObjectD);
var objC=newmock(IObjectG,IObjectH);
var objB=新模拟(IObjectE,IObjectF);
var objA=新模拟(IObjectB、IObjectC、IObjectD);

我做错了什么

这可能表明您正在测试的代码设计中存在缺陷。这还不错,这是件好事。可能不是-问题可能是您试图通过测试完成什么

如果您在模拟
IObjectA
,为什么需要模拟它的依赖项?如果您正在测试的类依赖于
IObjectA
,那么
IObjectA
的实现是否有自己的依赖关系是否重要?依赖抽象的一个好处是类不必关心其依赖项的实现细节。换句话说,您的类应该关心的是
IObjectA
所做的事情。它不应该知道或关心
IObjectA
是否有依赖项,更不用说它们做什么了

如果您的类“知道”IObjectA表示一个具有自己依赖项的类,那么它实际上不仅仅依赖于接口。问题可能表明您应该重构类,使其仅依赖于接口,而不依赖于实现接口的类的依赖关系



如果
IObjectA
具有需要返回其他接口实现的属性或方法,则可以为这些接口创建模拟,然后配置IObjectA的mock以返回这些mock。

mock类型的目的是让我们可以编写测试,而不必处理复杂的依赖关系图,也不必担心与任何外部进程通信,因此我们可以专注于编写快速、确定的单元测试。这意味着,当您创建一个mock时,该mock的内部表示与您的测试无关;它只是一个代理对象,它取代了代码将在生产中使用的实际实现

这就是说,我们仍然需要能够配置这些模拟来展示我们想要的行为——例如,返回值或抛出异常——这就是为什么我们可以通过调用
Setup()
来配置它们的设置

现在,回到你的问题,我想知道你真正描述的是你想要调用一个mock来返回另一个mock的情况。这种情况可能发生在希望从模拟工厂返回模拟策略的场景中。为此,您必须设置工厂以返回策略。大概是这样的:

var factoryMock = new Mock<IFactory>();
var strategyMock = new Mock<IStrategy>();
var type = typeof(FakeConcreteStrategy);

factoryMock.Setup(x => x.Create(type)).Returns(strategyMock.Object);
我做错了什么

您正在违反并创建具有多个组件的系统。如果您坚持这条法律,并创建一个只调用以下成员的方法:

  • 对象本身
  • 方法的参数
  • 在方法中创建的任何对象
  • 对象的任何直接属性/字段
  • 这样就不会有复杂测试设置的问题

    <强>前:考虑与客户端钱包对话的ATM类:

    public void ProcessPayment(Person client, decimal amount)
    {
        var wallet = client.Wallet;
        if (wallet.TotalAmount() < amount)
           throw new BlahBlahException();
    
        wallet.Remove(amount);
    }
    
    不仅代码变得简单易读,而且测试设置也得到简化:

    [Test]
    public void AtmShouldChargeClientWhenItHasEnoughMoney()
    {
        var clientMock = new Mock<IClient>();
        clientMock.Setup(c => c.TryCharge(10)).Returns(true);
        var atm = new Atm();
    
        atm.ProcessPayment(clientMock.Object, 10);
        clientMock.VerifyAll();
    }    
    
    [测试]
    当没有足够的钱时,在MShouldChargeClients上的公共无效()
    {
    var clientMock=new Mock();
    clientMock.Setup(c=>c.TryCharge(10)).Returns(true);
    var atm=新的atm();
    atm.ProcessPayment(clientMock.Object,10);
    clientMock.VerifyAll();
    }    
    
    请注意,不再涉及真正的类。我们用抽象的IClient依赖替换了Person依赖。如果某些东西在亲自实现时被破坏,它将不会影响ATM测试


    当然,您应该对Person类进行单独的测试,以检查它是否与wallet正确交互:

    [Test]
    public void PersonShouldNotBeChargedWhenThereIsNotEnoughMoneyInWallet()
    {
        var walletMock = new Mock<IWallet>(MockBehavior.Strict);
        walletMock.Setup(w => w.GetTotalAmount()).Returns(5);
        var person = new Person(walletMock.Object);
    
        person.TryCharge(10).Should().BeFalse();
        walletMock.VerifyAll();
    }
    
    [Test]
    public void PersonShouldBeChargedWhenThereIsEnoughMoneyInWallet()
    {
        var walletMock = new Mock<IWallet>(MockBehavior.Strict);
        walletMock.Setup(w => w.GetTotalAmount()).Returns(15);
        walletMock.Setup(w => w.Remove(10));
        var person = new Person(walletMock.Object);
    
        person.TryCharge(10).Should().BeTrue();
        walletMock.VerifyAll();
    }
    
    [测试]
    当钱包中没有零钱时,不得向公众作废人员收费()
    {
    var walletMock=新模拟(MockBehavior.Strict);
    Setup(w=>w.GetTotalAmount())。返回(5);
    var person=新人员(walletMock.Object);
    person.TryCharge(10.Should().BeFalse();
    walletMock.VerifyAll();
    }
    [测试]
    当钱包中没有钱时,应向公众作废人员收取费用()
    {
    var walletMock=新模拟(MockBehavior.Strict);
    Setup(w=>w.GetTotalAmount())。返回(15);
    walletMock.Setup(w=>w.Remove(10));
    var person=新人员(walletMock.Object);
    person.TryCharge(10.Should().BeTrue();
    walletMock.VerifyAll();
    }
    

    好处-您可以在不破坏ATM功能和测试的情况下更改Person类的实现。例如,您可以从钱包切换到信用卡,或者在钱包为空时检查信用卡。

    这只是模拟设置,您能否提供正在测试的代码示例并描述预期行为?
    public void ProcessPayment(IClient client, decimal amount)
    {
        if (!client.TryCharge(amount))
           throw new BlahBlahException();
    }
    
    [Test]
    public void AtmShouldChargeClientWhenItHasEnoughMoney()
    {
        var clientMock = new Mock<IClient>();
        clientMock.Setup(c => c.TryCharge(10)).Returns(true);
        var atm = new Atm();
    
        atm.ProcessPayment(clientMock.Object, 10);
        clientMock.VerifyAll();
    }    
    
    [Test]
    public void PersonShouldNotBeChargedWhenThereIsNotEnoughMoneyInWallet()
    {
        var walletMock = new Mock<IWallet>(MockBehavior.Strict);
        walletMock.Setup(w => w.GetTotalAmount()).Returns(5);
        var person = new Person(walletMock.Object);
    
        person.TryCharge(10).Should().BeFalse();
        walletMock.VerifyAll();
    }
    
    [Test]
    public void PersonShouldBeChargedWhenThereIsEnoughMoneyInWallet()
    {
        var walletMock = new Mock<IWallet>(MockBehavior.Strict);
        walletMock.Setup(w => w.GetTotalAmount()).Returns(15);
        walletMock.Setup(w => w.Remove(10));
        var person = new Person(walletMock.Object);
    
        person.TryCharge(10).Should().BeTrue();
        walletMock.VerifyAll();
    }