如何测试需要工厂的类

本文关键字:工厂 何测试 测试 | 更新日期: 2023-09-27 18:28:43

我正在尝试测试一个需要工厂(Func<T>(的类,并且我正在使用最小起订量和AutoFixture。

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

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

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");
}

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

如何测试需要工厂的类

由于AutoFixture能够创建匿名委托,当被要求创建SystemUnderTest的匿名实例时,它还会自动提供一个匿名Func<IMyClass>委托,该委托又在调用时返回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 配置为用作自动模拟容器,例如使用 AutoMoqCustomization ,那些匿名的IMyClass实例也将恰好是 Moq 代理:

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

但是,此信息虽然有用,但在特定情况下并没有真正帮助您,因为您需要掌握测试中由 Func<IMyClass> Factory 方法返回的模拟对象,以便配置它们的行为并就它们的交互方式做出一些断言。

在我看来,最好的解决方案是将工厂方法的实现从Func<IMyClass>更改为接口。通过这种方式,您可以创建一个假工厂,当序列中多次调用 Create 方法时,该工厂返回 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

请注意,ReturnsInOrder 是一种自定义扩展方法,它在连续多次调用存根方法时,使用 Moq 中的 Callback 方法从存根方法返回不同的值。

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

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