我喜欢AutoFixture,但是遇到了一些非常重复的"编排"代码,我觉得它应该能够处理 - 不知何故.
这是我的场景,使用IInterceptor
来自Castle Dynamic Proxy的实现进行说明.
首先是被测系统:
public class InterceptorA : IInterceptor
{
public void Intercept(IInvocation context)
{
object proxy = context.Proxy;
object returnValue = context.ReturnValue;
// Do something with proxy and returnValue
}
}
public class InterceptorB : IInterceptor
{
public void Intercept(IInvocation context)
{
object returnValue = context.ReturnValue;
// Do something with different returnValue
}
}
Run Code Online (Sandbox Code Playgroud)
现在进行一些简单的测试,利用xUnit的数据理论支持:
public class InterceptorATests
{
[Theory, CustomAutoData]
public void TestA1(InterceptorA sut, IInvocation context)
{
Mock.Get(context).Setup(c => c.Proxy).Returns("a");
Mock.Get(context).Setup(c => c.ReturnValue).Returns("b");
sut.Intercept(context);
// assert
}
}
public class InterceptorBTests
{
[Theory, CustomAutoData]
public void TestB1(InterceptorB sut, IInvocation context)
{
Mock.Get(context).Setup(c => c.ReturnValue).Returns("z");
sut.Intercept(context);
// assert
}
}
Run Code Online (Sandbox Code Playgroud)
我的CustomAutoData
属性,事实上确实定制AutoFixture,这样的注射情况IInvocation
是大多正确配置,但每IInterceptor
实现预期完全不同类型的Proxy
和ReturnValue
性质,每个测试必须设置这些自己.(因此Mock.Get(context).Setup(...)
电话.)
这是可以的,除了每次测试InterceptorATests
必须重复相同的几行排列,以及每次测试InterceptorBTests
.
有没有办法干净地删除重复的Mock.Get(...)
电话?有没有一种方法可以访问IFixture
给定测试类的实例?
你可以做很多事情 - 具体取决于你真正想要测试的是什么.
首先,我想指出的是,这个特定问题的大部分问题都源于IInvocation极其弱类型的API,以及Moq没有像我们通常实现属性那样实现属性的事实.
如果您不需要它们,请不要设置存根
首先,你不具备为代理服务器和返回值的属性设置返回值,如果你不需要它们.
AutoFixture.AutoMoq设置Mock<T>
实例的方式是它始终设置DefaultValue = DefaultValue.Mock
.由于这两种属性的返回类型是object
和object
有一个默认的构造函数,你会自动获得一个对象(实际上,一个ObjectProxy
)回来了.
换句话说,这些测试也通过了:
[Theory, CustomAutoData]
public void TestA2(InterceptorA sut, IInvocation context)
{
sut.Intercept(context);
// assert
}
[Theory, CustomAutoData]
public void TestB2(InterceptorB sut, IInvocation context)
{
sut.Intercept(context);
// assert
}
Run Code Online (Sandbox Code Playgroud)
直接指定ReturnValue
对于我的其余部分,我将假设您确实需要在测试中分配和/或读取属性值.
首先,您可以通过直接指定ReturnValue来减少繁重的Moq语法:
[Theory, Custom3AutoData]
public void TestA3(InterceptorA sut, IInvocation context)
{
context.ReturnValue = "b";
sut.Intercept(context);
// assert
Assert.Equal("b", context.ReturnValue);
}
[Theory, Custom3AutoData]
public void TestB3(InterceptorB sut, IInvocation context)
{
context.ReturnValue = "z";
sut.Intercept(context);
// assert
Assert.Equal("z", context.ReturnValue);
}
Run Code Online (Sandbox Code Playgroud)
但是,它只适用于ReturnValue
因为它是可写属性.它不适用于该Proxy
属性,因为它是只读的(它不会编译).
为了使这项工作,您必须指示Moq将IInvocation
属性视为"真实"属性:
public class Customization3 : CompositeCustomization
{
public Customization3()
: base(
new RealPropertiesOnInvocation(),
new AutoMoqCustomization())
{
}
private class RealPropertiesOnInvocation : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Register<Mock<IInvocation>>(() =>
{
var td = new Mock<IInvocation>();
td.DefaultValue = DefaultValue.Mock;
td.SetupAllProperties();
return td;
});
}
}
}
Run Code Online (Sandbox Code Playgroud)
注意来电SetupAllProperties
.
这是因为AutoFixture.AutoMoq通过将所有接口请求中继到对该接口的模拟请求 - 即请求IInvocation
转换为请求来工作Mock<IInvocation>
.
不要设置测试值; 读回来
最后,您应该问自己:我是否真的需要为这些属性分配特定值(例如"a","b"和"z").难道我不能让AutoFixture创建所需的值吗?如果我这样做,我是否需要明确指定它们?我不能只读回指定的值吗?
这可能是我称之为信号类型的一个小技巧.信号类型是指示值的特定角色的类.
为每个属性引入信号类型:
public class InvocationReturnValue
{
private readonly object value;
public InvocationReturnValue(object value)
{
this.value = value;
}
public object Value
{
get { return this.value; }
}
}
public class InvocationProxy
{
private readonly object value;
public InvocationProxy(object value)
{
this.value = value;
}
public object Value
{
get { return this.value; }
}
}
Run Code Online (Sandbox Code Playgroud)
(如果要求值始终为字符串,则可以将构造函数签名更改为需要string
而不是object
.)
冻结您关心的信号类型,以便在配置IInvocation实例时知道将重用相同的实例:
[Theory, Custom4AutoData]
public void TestA4(
InterceptorA sut,
[Frozen]InvocationProxy proxy,
[Frozen]InvocationReturnValue returnValue,
IInvocation context)
{
sut.Intercept(context);
// assert
Assert.Equal(proxy.Value, context.Proxy);
Assert.Equal(returnValue.Value, context.ReturnValue);
}
[Theory, Custom4AutoData]
public void TestB4(
InterceptorB sut,
[Frozen]InvocationReturnValue returnValue,
IInvocation context)
{
sut.Intercept(context);
// assert
Assert.Equal(returnValue.Value, context.ReturnValue);
}
Run Code Online (Sandbox Code Playgroud)
这种方法的好处是,在这些测试情况下,你不关心ReturnValue
或者Proxy
你可以省略这些方法的参数.
相应的Customization是前一个的扩展:
public class Customization4 : CompositeCustomization
{
public Customization4()
: base(
new RelayedPropertiesOnInvocation(),
new AutoMoqCustomization())
{
}
private class RelayedPropertiesOnInvocation : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Register<Mock<IInvocation>>(() =>
{
var td = new Mock<IInvocation>();
td.DefaultValue = DefaultValue.Mock;
td.SetupAllProperties();
td.Object.ReturnValue =
fixture.CreateAnonymous<InvocationReturnValue>().Value;
td.Setup(i => i.Proxy).Returns(
fixture.CreateAnonymous<InvocationProxy>().Value);
return td;
});
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,通过要求IFixture实例创建相应信号类型的新实例然后展开其值来分配每个属性的值.
这种方法可以概括,但这是它的要点.
归档时间: |
|
查看次数: |
551 次 |
最近记录: |