C# 如何使用Moq测试方法调用顺序

C# 如何使用Moq测试方法调用顺序,c#,unit-testing,moq,C#,Unit Testing,Moq,目前我有: [Test] public void DrawDrawsAllScreensInTheReverseOrderOfTheStack() { // Arrange. var screenMockOne = new Mock<IScreen>(); var screenMockTwo = new Mock<IScreen>(); var screens = new List<IS

目前我有:

    [Test]
    public void DrawDrawsAllScreensInTheReverseOrderOfTheStack() {
        // Arrange.
        var screenMockOne = new Mock<IScreen>();
        var screenMockTwo = new Mock<IScreen>();
        var screens = new List<IScreen>();
        screens.Add(screenMockOne.Object);
        screens.Add(screenMockTwo.Object);
        var stackOfScreensMock = new Mock<IScreenStack>();
        stackOfScreensMock.Setup(s => s.ToArray()).Returns(screens.ToArray());
        var screenManager = new ScreenManager(stackOfScreensMock.Object);
        // Act.
        screenManager.Draw(new Mock<GameTime>().Object);
        // Assert.
        screenMockOne.Verify(smo => smo.Draw(It.IsAny<GameTime>()), Times.Once(),
            "Draw was not called on screen mock one");
        screenMockTwo.Verify(smo => smo.Draw(It.IsAny<GameTime>()), Times.Once(), 
            "Draw was not called on screen mock two");
    }
[测试]
公共作废图纸所有屏幕显示在Stack()的上方{
//安排。
var screenMockOne=new Mock();
var screenMockTwo=新模拟();
var screens=新列表();
screens.Add(screenMockOne.Object);
screens.Add(screenMockTwo.Object);
var stackOfScreensMock=新Mock();
stackOfScreensMock.Setup(s=>s.ToArray()).Returns(screens.ToArray());
var screenManager=新的screenManager(stackOfScreensMock.Object);
//表演。
screenManager.Draw(新的Mock().Object);
//断言。
screenMockOne.Verify(smo=>smo.Draw(It.IsAny()),Times.Once(),
“未在屏幕上调用Draw模拟一”);
screenMockTwo.Verify(smo=>smo.Draw(It.IsAny()),Times.Once(),
“在模拟二号屏幕上未进行抽签”);
}
但我在生产代码中绘制对象的顺序并不重要。我可以先做一个或两个,没关系。但是,这很重要,因为提取顺序很重要

您(使用Moq)如何确保方法按特定顺序调用

编辑

我摆脱了那个测试。draw方法已从我的单元测试中删除。我只需要手动测试它的工作情况。相反的顺序被带到一个单独的测试类中,在那里它被测试过,所以它并不都是坏的


感谢您提供有关他们正在研究的功能的链接。我当然希望它很快被添加,非常方便。

看看这个,它可能会解决您的问题。

看起来它目前还没有实现。看见讨论这个问题

<>你可能会考虑修改你的测试。我通常认为测试顺序会导致脆弱的测试,因为它经常测试实现细节


编辑:我不确定这是否解决了OP的问题。Lucero的答案可能更有用。

否则,您可以使用回调函数并递增/存储callIndex值。

我最近创建了Moq.Sequences,它提供了在Moq中检查顺序的功能。您可能需要阅读我的,其中描述了以下内容:

  • 支持方法调用、属性 二传手和接球手
  • 允许您指定 特定通话的时间应为 预料之中
  • 提供允许您执行以下操作的循环: 对定期组的组调用
  • 允许您指定号码 循环次数应为预期次数
  • 预期要调用的调用 按顺序可以相互混合 以任何顺序进行的呼叫
  • 多线程支持
典型用法如下所示:

[Test]
public void Should_show_each_post_with_most_recent_first_using_sequences()
{
    var olderPost = new Post { DateTime = new DateTime(2010, 1, 1) };
    var newerPost = new Post { DateTime = new DateTime(2010, 1, 2) };
    var posts = new List<Post> { newerPost, olderPost };

    var mockView = new Mock<BlogView>();

    using (Sequence.Create())
    {
        mockView.Setup(v => v.ShowPost(newerPost)).InSequence();
        mockView.Setup(v => v.ShowPost(olderPost)).InSequence();

        new BlogPresenter(mockView.Object).Show(posts);
    }
}
[测试]
public void应使用序列()显示每个帖子和最近的帖子
{
var olderPost=newpost{DateTime=newdatetime(2010,1,1)};
var newerPost=newpost{DateTime=newdatetime(2010,1,2)};
var posts=新列表{newerPost,olderPost};
var mockView=new Mock();
使用(Sequence.Create())
{
mockView.Setup(v=>v.ShowPost(newerPost)).InSequence();
mockView.Setup(v=>v.ShowPost(olderPost)).InSequence();
新的BlogPresenter(mockView.Object).Show(posts);
}
}

从最初的帖子中,我可以假设测试方法执行以下操作调用:

var screenOne = new Screen(...);
var screenTwo = new Screen(...);
var screens = new []{screenOne, screenTwo};
var screenManager = new ScreenManager(screens);
screenManager.Draw();
其中“Draw”方法实现如下所示:

public class ScreenManager
{
    public void Draw()
    {
        _screens[0].Draw();
        _screens[1].Draw();
    }
}
从我的角度来看,如果调用顺序非常重要,那么应该在系统中引入额外的结构(描述序列)

最简单的实现:每个屏幕都应该知道其后续元素,并在自己绘制后调用其绘制方法:

// 1st version
public class Screen(Screen screenSubSequent)
{
    private Screen _screenNext;
    public Screen(Screen screenNext)
    {
        _screenNext=screenNext;
    }
    public void Draw()
    {
        // draw himself
        if ( _screenNext!=null ) _screenNext.Draw();
    }
}

public class ScreenManager
{
    public void Draw()
    {
        _screens[0].Draw();
    }
}

static void Main()
{
    var screenOne = new Screen(null, ...);
    var screenTwo = new Screen(screenOne, ...);
    var screens = new []{screenOne, screenTwo};
    var screenManager = new ScreenManager(screens);
}
从这一点来看,每个屏幕元素都应该对另一个屏幕元素有所了解。这并不总是好的。如果是这样:您可以创建一些类,如“ScreenDrawer”。此对象将存储自己的屏幕和后续屏幕(可能从screen类继承他)。使用其他世界:“ScreenDrawer”类描述系统结构。下面是一个最简单的实现场景:

// 2nd version
public class ScreenDrawer
{
    private Screen _screenNext;
    public ScreenDrawer(Screen screenNext, ...) : base (...)
    {
        _screenNext=screenNext;
    }
    public void Draw()
    {
        // draw himself
        if ( _screenNext!=null ) _screenNext.Draw();
    }
}

public class ScreenManager
{
    public void Draw()
    {
        _screens[0].Draw();
    }
}

static void Main()
{
    var screenOne = new ScreenDrawer(null, ...);
    var screenTwo = new ScreenDrawer(screenOne, ...);
    var screens = new []{screenOne, screenTwo};
    var screenManager = new ScreenManager(screens);
}
第二个方法引入了额外的继承,但不要求Screen类了解其子序列元素

总结:这两种方法都进行子顺序调用,不需要“顺序”测试。相反,它们需要测试当前“屏幕”是否调用另一个,以及“屏幕管理器”是否调用顺序中第一个元素的“绘图”方法

这种方法:

  • 更易测试(可以使用大多数测试框架实现,而无需支持“序列测试”)
  • 更稳定(没有人可以轻易地更改序列:hi不仅需要更新源代码,还需要更新一些测试)
  • 更面向对象(您使用的是对象,而不是像“序列”这样的抽象实体)
  • 结果是:更加可支持

  • 谢谢。

    使用Moq回调的简单解决方案:

        [TestMethod]
        public void CallInOrder()
        {
            // Arrange
            string callOrder = "";
    
            var service = new Mock<MyService>();
            service.Setup(p=>p.FirstCall()).Returns(0).CallBack(()=>callOrder += "1");
            service.Setup(p=>p.SecondCall()).Returns(0).CallBack(()=>callOrder += "2");
    
            var sut = new Client(service);
    
            // Act
            sut.DoStuff();
    
            // Assert
            Assert.AreEqual("12", callOrder);
        }
    
    [TestMethod]
    公共无效Callinoder()
    {
    //安排
    字符串callOrder=“”;
    var service=newmock();
    service.Setup(p=>p.FirstCall()).Returns(0).CallBack(()=>callOrder+=“1”);
    service.Setup(p=>p.SecondCall()).Returns(0).CallBack(()=>callOrder+=“2”);
    var sut=新客户(服务);
    //表演
    sut.DoStuff();
    //断言
    断言.AreEqual(“12”,callOrder);
    }
    
    在我的情况下无法实现此功能,我无法使用队列交换数组。不过现在没关系。请参阅编辑。断言/验证顺序可能会导致脆弱的测试,但这不是因为它正在测试实现细节。如果使用模拟,尤其是使用控制反转(依赖项注入)的类模式,您已经在测试实现细节;这就是重点。我要说的是,测试顺序不应该是测试中常见的模式,但在一些有效的情况下,没有其他方法可以驱动co