Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/296.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用最小起订量来验证通话顺序是否正确_C#_Unit Testing_Nunit_Moq_Sequential - Fatal编程技术网

C# 使用最小起订量来验证通话顺序是否正确

C# 使用最小起订量来验证通话顺序是否正确,c#,unit-testing,nunit,moq,sequential,C#,Unit Testing,Nunit,Moq,Sequential,我需要测试以下方法: CreateOutput(IWriter writer) { writer.Write(type); writer.Write(id); writer.Write(sender); // many more Write()s... } 我已经创建了一个Moq'dIWriter,我想确保以正确的顺序调用Write()方法 我有以下测试代码: var mockWriter = new Mock<IWriter>(MockBehav

我需要测试以下方法:

CreateOutput(IWriter writer)
{
    writer.Write(type);
    writer.Write(id);
    writer.Write(sender);

    // many more Write()s...
}
我已经创建了一个Moq'd
IWriter
,我想确保以正确的顺序调用
Write()
方法

我有以下测试代码:

var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
var sequence = new MockSequence();
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedType));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedId));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedSender));
var mockWriter=newmock(MockBehavior.Strict);
var sequence=新的MockSequence();
mockWriter.InSequence(sequence).Setup(x=>x.Write(expectedType));
mockWriter.InSequence(sequence).Setup(x=>x.Write(expectedId));
mockWriter.InSequence(sequence).Setup(x=>x.Write(expectedSender));
但是,在
CreateOutput()
中对
Write()
的第二次调用(用于写入
id
值)抛出一个
MockException
,消息为“IWriter.Write()调用失败,模拟行为严格。模拟上的所有调用都必须具有相应的设置。”

我还发现很难找到任何确定的、最新的Moq序列文档/示例

我是做错了什么,还是不能用同样的方法设置序列?
如果没有,我是否可以使用其他方法(最好使用Moq/NUnit)?

我怀疑expectedId不是您所期望的

然而,我可能只编写我自己的IWriter实现来验证在这种情况下。。。可能要容易得多(而且以后更容易更改)

对不起,没有直接的最低起订量建议。我喜欢它,但还没有这样做


您是否需要在每次安装结束时添加.Verify()?(尽管我担心这确实是一个猜测)。

我已经设法获得了我想要的行为,但它需要从下载第三方库

然后可以使用以下方法测试序列:

var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
using (Sequence.Create())
{
    mockWriter.Setup(x => x.Write(expectedType)).InSequence();
    mockWriter.Setup(x => x.Write(expectedId)).InSequence();
    mockWriter.Setup(x => x.Write(expectedSender)).InSequence();
}
var mockWriter=newmock(MockBehavior.Strict);
使用(Sequence.Create())
{
mockWriter.Setup(x=>x.Write(expectedType)).InSequence();
mockWriter.Setup(x=>x.Write(expectedId)).InSequence();
mockWriter.Setup(x=>x.Write(expectedSender)).InSequence();
}
我添加这个作为一个答案,部分是为了帮助记录这个解决方案,但我仍然对是否可以单独使用Moq 4.0实现类似的功能感兴趣

我不确定Moq是否仍在开发中,但是修复
MockSequence
的问题,或者在Moq中包含Moq sequences扩展将是一件好事。

当出现错误时。它肯定会在Moq库的后续版本中修复(您也可以通过更改
Moq.MethodCall.Matches
实现手动修复)

如果只想使用Moq,则可以通过回调验证方法调用顺序:

int callOrder = 0;
writerMock.Setup(x => x.Write(expectedType)).Callback(() => Assert.That(callOrder++, Is.EqualTo(0)));
writerMock.Setup(x => x.Write(expectedId)).Callback(() => Assert.That(callOrder++, Is.EqualTo(1)));
writerMock.Setup(x => x.Write(expectedSender)).Callback(() => Assert.That(callOrder++, Is.EqualTo(2)));

最近,我为Moq整合了两个特性:VerifyInSequence()和VerifyNotInSequence()。即使是松散的模拟,它们也能工作。但是,这些仅在moq存储库中可用:

并等待更多的意见和测试,然后再决定是否可以将其纳入官方的最低起订量。但是,没有什么可以阻止您将源代码作为ZIP下载,将其构建到dll中并进行尝试。使用这些功能,您需要的序列验证可以这样编写:

var mockWriter = new Mock<IWriter>() { CallSequence = new LooseSequence() }; //perform the necessary calls mockWriter.VerifyInSequence(x => x.Write(expectedType)); mockWriter.VerifyInSequence(x => x.Write(expectedId)); mockWriter.VerifyInSequence(x => x.Write(expectedSender)); var mockWriter=new Mock(){CallSequence=new LooseSequence()}; //执行必要的呼叫 mockWriter.VerifyInSequence(x=>x.Write(expectedType)); mockWriter.VerifyInSequence(x=>x.Write(expectedId)); mockWriter.VerifyInSequence(x=>x.Write(expectedSender)); (请注意,您可以根据需要使用其他两个序列。松散序列将允许在要验证的序列之间进行任何调用。StrictSequence不允许这样做,StrictAnytimeSequence与StrictSequence类似(已验证的调用之间没有方法调用),但允许在序列之前进行任意数量的调用

如果您决定尝试此实验功能,请就以下内容发表评论:


谢谢!

我编写了一个扩展方法,它将根据调用顺序进行断言

public static class MockExtensions
{
  public static void ExpectsInOrder<T>(this Mock<T> mock, params Expression<Action<T>>[] expressions) where T : class
  {
    // All closures have the same instance of sharedCallCount
    var sharedCallCount = 0;
    for (var i = 0; i < expressions.Length; i++)
    {
      // Each closure has it's own instance of expectedCallCount
      var expectedCallCount = i;
      mock.Setup(expressions[i]).Callback(
        () =>
          {
            Assert.AreEqual(expectedCallCount, sharedCallCount);
            sharedCallCount++;
          });
    }
  }
}
公共静态类扩展
{
public static void ExpectsInOrder(此模拟,参数表达式[]表达式),其中T:class
{
//所有闭包都具有相同的sharedCallCount实例
var sharedCallCount=0;
for(var i=0;i
{
AreEqual(expectedCallCount、sharedCallCount);
sharedCallCount++;
});
}
}
}
它利用了闭包对作用域变量的工作方式。由于sharedCallCount只有一个声明,所有闭包都将引用同一个变量。使用expectedCallCount,循环的每次迭代都会实例化一个新实例(而不是在闭包中简单地使用i)。这样,每个闭包都有一个i的副本,作用域仅限于其自身,以便在调用表达式时与sharedCallCount进行比较

这里是扩展的一个小单元测试。请注意,此方法是在安装部分调用的,而不是在断言部分调用的

[TestFixture]
public class MockExtensionsTest
{
  [TestCase]
  {
    // Setup
    var mock = new Mock<IAmAnInterface>();
    mock.ExpectsInOrder(
      x => x.MyMethod("1"),
      x => x.MyMethod("2"));

    // Fake the object being called in order
    mock.Object.MyMethod("1");
    mock.Object.MyMethod("2");
  }

  [TestCase]
  {
    // Setup
    var mock = new Mock<IAmAnInterface>();
    mock.ExpectsInOrder(
      x => x.MyMethod("1"),
      x => x.MyMethod("2"));

    // Fake the object being called out of order
    Assert.Throws<AssertionException>(() => mock.Object.MyMethod("2"));
  }
}

public interface IAmAnInterface
{
  void MyMethod(string param);
}
[TestFixture]
公共类MockExtensionsTest
{
[测试用例]
{
//设置
var mock=new mock();
模拟预期订单(
x=>x.MyMethod(“1”),
x=>x.MyMethod(“2”);
//伪造按顺序调用的对象
mock.Object.MyMethod(“1”);
mock.Object.MyMethod(“2”);
}
[测试用例]
{
//设置
var mock=new mock();
模拟预期订单(
x=>x.MyMethod(“1”),
x=>x.MyMethod(“2”);
//假装正在调用的对象出现故障
Assert.Throws(()=>mock.Object.MyMethod(“2”);
}
}
公共接口接口
{
void MyMethod(字符串参数);
}

最简单的解决方案是使用:

var expectedParameters=new队列(new[]{expectedType,expectedId,expectedSender});
mockWriter.Setup(x=>x.Write(expectedType))
.Callback((字符串s)=>Assert.AreEqual(例如
var expectedParameters = new Queue<string>(new[]{expectedType,expectedId,expectedSender});
mockWriter.Setup(x => x.Write(expectedType))
          .Callback((string s) => Assert.AreEqual(expectedParameters.Dequeue(), s));
public interface IWriter
    {
    void WriteA ();
    void WriteB ();
    void WriteC ();
    }
var writer = new Mock<IWriter> ();

new SUT (writer.Object).Run ();

Assert.Equal (
    writer.Invocations.Select (invocation => invocation.Method.Name),
    new[]
        {
        nameof (IWriter.WriteB),
        nameof (IWriter.WriteA),
        nameof (IWriter.WriteC),
        });
    expected
["WriteB", "WriteA", "WriteC"]
    but was
["WriteA", "WriteB"]
public static void VerifyInvocations<T>(this Mock<T> mock, params Expression<Action<T>>[] expressions) where T : class
{
    Assert.AreEqual(mock.Invocations.Count, expressions.Length,
        $"Number of invocations did not match expected expressions! Actual invocations: {Environment.NewLine}" +
        $"{string.Join(Environment.NewLine, mock.Invocations.Select(i => i.Method.Name))}");

    for (int c = 0; c < mock.Invocations.Count; c++)
    {
        IInvocation expected = mock.Invocations[c];
        MethodCallExpression actual = expressions[c].Body as MethodCallExpression;

        // Verify that the same methods were invoked
        Assert.AreEqual(expected.Method, actual.Method, $"Did not invoke the expected method at call {c + 1}!");

        // Verify that the method was invoked with the correct arguments
        CollectionAssert.AreEqual(expected.Arguments.ToList(),
            actual.Arguments
                .Select(arg =>
                {
                    // Expressions treat the Argument property as an Expression, do this to invoke the getter and get the actual value.
                    UnaryExpression objectMember = Expression.Convert(arg, typeof(object));
                    Expression<Func<object>> getterLambda = Expression.Lambda<Func<object>>(objectMember);
                    Func<object> objectValueGetter = getterLambda.Compile();
                    return objectValueGetter();
                })
                .ToList(),
            $"Did not invoke step {c + 1} method '{expected.Method.Name}' with the correct arguments! ");
    }
}
//arrange
var someServiceToTest = new SomeService();

var expectedCallOrder = new List<string>
{
    "WriteA",
    "WriteB",
    "WriteC"
};
var actualCallOrder = new List<string>();

var mockWriter = new Mock<IWriter>();
mockWriter.Setup(x => x.Write("A")).Callback(() => { actualCallOrder.Add("WriteA"); });
mockWriter.Setup(x => x.Write("B")).Callback(() => { actualCallOrder.Add("WriteB"); });
mockWriter.Setup(x => x.Write("C")).Callback(() => { actualCallOrder.Add("WriteC"); });

//act
someServiceToTest.CreateOutput(_mockWriter.Object);

//assert
Assert.AreEqual(expectedCallOrder, actualCallOrder);