【问题标题】:How do I test a class that takes a factory?如何测试需要工厂的课程?
【发布时间】:2012-03-23 12:53:27
【问题描述】:

我正在尝试测试一个采用工厂 (Func<T>) 的类,并且我正在使用 Moq 和 AutoFixture。

设置“环境”以查看工厂是否已使用以及在返回的实例上使用了多少次以及使用了哪些方法的最佳方法是什么?

目前我正在模拟TInjecting 一个Func<T>,它记录所有返回的Mock 实例:

public class SystemUnderTest {
    public SystemUnderTest(Func<IMyClass> c)
    {
        try {
            var c1 = c();
            c1.Name="n1";
            c1.Send();
        }
        catch(Exception){
            var c2 = c();
            c2.Name="n2";
            c2.Send();
        }
    }
}
private Mock<IMyClass> MockFactory()
{
   var m = new Mock<IMyClass>();
   m.SetupProperty(mc=>mc.Name);
   _returnedStubs.Add(m);
   return m;
}  
[Test]
public void TestSomething()
{
    var fixture = new Fixture();
    fixture.Inject(()=>MockFactory().Object)
    var sut = fixture.CreateAnonymous<SystemUnderTest>();
    Assert.That(_returnedStubs.Count,Is.Equal(1));
    _returnedStubs[0].Verify(m=>m.Send(),Times.Exactly(1));
    _returnedStubs[0].Verify(m=>m.Name = "n1");
}

但这对我来说有点可疑/丑陋。而且我很确定测试类中的实例变量是一件危险的事情

【问题讨论】:

  • 虽然我意识到这可能是为了将问题减少到可管理的大小,但我想知道您正在尝试做什么? MyClass 是一个具体的类吗?为什么创建多少次很重要?
  • 这很有趣,因为IMyClass 是一个用于收集数据并将其发送到网络服务的类。但是,如果 IMyClass 的第一个实例的数据收集引发异常,则使用特定消息创建另一个实例,我想验证:“当 instance1 引发异常时:1) instance2 被创建,2) instance2 得到第一个异常的消息,以及 3) 不应创建更多实例"
  • 我明白了。但是,您在 IMyClass API 中有时间耦合,这使得测试变得更加困难。如果您更改 API 以启用 c1.Send("n1"); 之类的功能,则测试起来会更容易......这条特定的反馈确实与 AutoFixture 无关,但它也会让这变得更容易。

标签: c# moq autofixture


【解决方案1】:

由于 AutoFixture 是 able to create anonymous delegates,当被要求创建 SystemUnderTest 的匿名实例时,它还会自动提供匿名的 Func&lt;IMyClass&gt; 委托,该委托在调用时又会返回 IMyClass 的匿名实例。

这意味着,在这种情况下:

public class SystemUnderTest
{
    public SystemUnderTest(Func<IMyClass> c)
    {
        try
        {
            var c1 = c();
            // ...
        }
        catch (Exception)
        {
            var c2 = c();
            // ...
        }
    }
}

以下代码:

var fixture = new Fixture();
var sut = fixture.CreateAnonymous<SystemUnderTest>();

将为c1c2 变量分配IMyClass 的匿名实例。此外,如果您将 AutoFixture 配置为 work as an auto-mocking container,例如使用 AutoMoqCustomization,则 IMyClass 的匿名实例也将恰好是 Moq 代理:

var fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
var sut = fixture.CreateAnonymous<SystemUnderTest>();

然而,这些信息虽然有用,但在您的特定情况下并不能真正帮助您,因为您需要掌握@987654336 返回的模拟对象 @factory 测试中的方法,以便配置它们的行为并对它们的交互方式做出一些断言。

在我看来,最好的解决方案是将工厂方法的实现Func&lt;IMyClass&gt;改为接口。这样,您可以创建一个假工厂,当Create 方法is invoked multiple times in a sequence 时返回IMyClass 接口的不同模拟

所以,给出这个例子:

public interface IFactory<T>
{
    T Create();
}

public class SystemUnderTest
{
    public SystemUnderTest(IFactory<IMyClass> factory)
    {
        try
        {
            var c1 = factory.Create();
            // ...
        }
        catch (Exception)
        {
            var c2 = factory.Create();
            // ...
        }
    }
}

您可以按如下方式设置测试场景:

    // Given
    var c1 = new Mock<IMyClass>();
    var c2 = new Mock<IMyClass>();
    // TODO: setup the behavior of the mock objects
    var factory = new Mock<IFactory<IMyClass>>();
    factory.Setup(s => s.Create()).ReturnsInOrder(c1.Object, c2.Object);

    // When
    var fixture = new Fixture();
    fixture.Inject(() => factory.Object)
    var sut = fixture.CreateAnonymous<SystemUnderTest>();

    // Then
    // TODO: verify the expectations on the mock objects

请注意,ReturnsInOrdercustom extension method,它在 Moq 中使用 Callback 方法在连续多次调用存根方法时返回不同的值。

【讨论】:

    【解决方案2】:

    最好的方法之一是创建自己的函数并传递它,然后在那里设置期望或状态。

    int numberOfTimesUsed = 0;
    myObject.Func = (x) => 
    { 
       numberOfTimesUsed++;
       Assert.IsNotNull(x); // checks if x passed was not null
    };
    
    ...
    
    Assert.AreEqual(2, numberOfTimesUsed); // checks if called twice
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-02
      • 2022-07-27
      • 1970-01-01
      • 2011-02-28
      • 1970-01-01
      • 2014-11-17
      相关资源
      最近更新 更多