对 Autofac 模块进行单元测试以达到 100% 的代码覆盖率
Unit test over Autofac Module to reach 100% code coverage
我们有一个进行复杂计算的核心库,我们认为它很关键,我们希望该库的代码覆盖率达到 100%。我们现在有 96%,这很好,但我们无法获得 100%,因为这个 class:
public class IoCModule : Autofac.Module
{
protected override void Load(Autofac.ContainerBuilder builder)
{
builder.RegisterType<SomeMathServiceA>().As<ISomeMathServiceA>();
builder.RegisterType<SomeMathServiceB>().As<ISomeMathServiceB>();
//... more registrations
}
}
我不知道如何测试它,或者我们是否真的需要测试它。
我尝试了一个单元测试,它采用这个模块并创建一个 IContainer
并解决了每个寄存器依赖关系,但是一些服务访问数据库和配置文件,在这种情况下模拟起来非常复杂。
完成!!!!
免责声明:class 级单元测试不合理
(作者)
我猜你所说的单元测试是指 "class level unit tests",其中单元是 class。如果你想测试 IoCModule
你应该使用 component/library 级别测试,你可以测试整个库是否正常工作。这(应该)包括 IoCModule
- 以及库中的所有其他内容。使用此级别的测试通常无法达到 100% 的分支覆盖率,但此级别的测试 + class 级别单元测试的组合可以实现非常好的测试可靠性。
我还想说,达到 80% 的综合覆盖率比只进行 class 级单元测试要好。虽然每个 class 本身都可以根据测试准确工作,但整体可能无法按预期工作。这就是您应该执行组件级测试的原因。
如何验证类型是否已注册:
现在,如果您仍然坚持要执行测试,请不要再犹豫了,您可以这样做:
public class MyModuleTest
{
private IContainer container;
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
var containerBuilder = new ContainerBuilder();
// register module to test
containerBuilder.RegisterModule<MyModule>();
// don't start startable components -
// we don't need them to start for the unit test
this.container = containerBuilder.Build(
ContainerBuildOptions.IgnoreStartableComponents);
}
[TestCaseSource(typeof(TypesExpectedToBeRegisteredTestCaseSource))]
public void ShouldHaveRegistered(Type type)
{
this.container.IsRegistered(type).Should().BeTrue();
}
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
this.container.Dispose();
}
private class TypesExpectedToBeRegisteredTestCaseSource : IEnumerable<object[]>
{
private IEnumerable<Type> Types()
{
// whatever types you're registering..
yield return typeof(string);
yield return typeof(int);
yield return typeof(float);
}
public IEnumerator<object[]> GetEnumerator()
{
return this.Types()
.Select(type => new object[] { type })
.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
这给出了如下测试输出:
所以每种类型都是单独报告的。
哇,这很简单 - 那又是什么问题?
现在在上面的示例中,您可以看到 single
(=float
) 的测试正在通过。现在看模块:
public class MyModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<float>();
}
}
当我们实际尝试通过以下方式解决 float
时:
container.Resolve<float>();
事情是这样的:
Autofac.Core.DependencyResolutionException : No constructors on type 'System.Single' can be found with the constructor finder 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'.
当然,我们可以调整测试以执行 Resolve(Type t)
而不是使用 IsRegistered(Type t)
- 但是还有很多其他方法可以使测试通过 - 但实施失败。例如:
- 绑定类型使用
builder.RegisterInstance<IFoo>(null)
- 更改 lifetime/scopes 使其不再正常工作。
终于找到测试方法了。 autofac 模块有一个注册组件的方法 Configure 。我是这样做的:
public class CheckRegistrations
{
[Test]
public void Should_Have_Register_Types()
{
//Arrange
var typesToCheck = new List<Type>
{
typeof (ISomeMathServiceA),
typeof (ISomeMathServiceB)
};
//Act
var typesRegistered = this.GetTypesRegisteredInModule(new IoCModule());
//Arrange
Assert.AreEqual(typesToCheck.Count, typesRegistered.Count());
foreach (var typeToCheck in typesToCheck)
{
Assert.IsTrue(typesRegistered.Any(x => x == typeToCheck), typeToCheck.Name + " was not found in module");
}
}
private IEnumerable<Type> GetTypesRegisteredInModule(Module module)
{
IComponentRegistry componentRegistry = new ComponentRegistry();
module.Configure(componentRegistry);
var typesRegistered =
componentRegistry.Registrations.SelectMany(x => x.Services)
.Cast<TypedService>()
.Select(x => x.ServiceType);
return typesRegistered;
}
}
我们有一个进行复杂计算的核心库,我们认为它很关键,我们希望该库的代码覆盖率达到 100%。我们现在有 96%,这很好,但我们无法获得 100%,因为这个 class:
public class IoCModule : Autofac.Module
{
protected override void Load(Autofac.ContainerBuilder builder)
{
builder.RegisterType<SomeMathServiceA>().As<ISomeMathServiceA>();
builder.RegisterType<SomeMathServiceB>().As<ISomeMathServiceB>();
//... more registrations
}
}
我不知道如何测试它,或者我们是否真的需要测试它。
我尝试了一个单元测试,它采用这个模块并创建一个 IContainer
并解决了每个寄存器依赖关系,但是一些服务访问数据库和配置文件,在这种情况下模拟起来非常复杂。
完成!!!!
免责声明:class 级单元测试不合理
(作者)
我猜你所说的单元测试是指 "class level unit tests",其中单元是 class。如果你想测试 IoCModule
你应该使用 component/library 级别测试,你可以测试整个库是否正常工作。这(应该)包括 IoCModule
- 以及库中的所有其他内容。使用此级别的测试通常无法达到 100% 的分支覆盖率,但此级别的测试 + class 级别单元测试的组合可以实现非常好的测试可靠性。
我还想说,达到 80% 的综合覆盖率比只进行 class 级单元测试要好。虽然每个 class 本身都可以根据测试准确工作,但整体可能无法按预期工作。这就是您应该执行组件级测试的原因。
如何验证类型是否已注册:
现在,如果您仍然坚持要执行测试,请不要再犹豫了,您可以这样做:
public class MyModuleTest
{
private IContainer container;
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
var containerBuilder = new ContainerBuilder();
// register module to test
containerBuilder.RegisterModule<MyModule>();
// don't start startable components -
// we don't need them to start for the unit test
this.container = containerBuilder.Build(
ContainerBuildOptions.IgnoreStartableComponents);
}
[TestCaseSource(typeof(TypesExpectedToBeRegisteredTestCaseSource))]
public void ShouldHaveRegistered(Type type)
{
this.container.IsRegistered(type).Should().BeTrue();
}
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
this.container.Dispose();
}
private class TypesExpectedToBeRegisteredTestCaseSource : IEnumerable<object[]>
{
private IEnumerable<Type> Types()
{
// whatever types you're registering..
yield return typeof(string);
yield return typeof(int);
yield return typeof(float);
}
public IEnumerator<object[]> GetEnumerator()
{
return this.Types()
.Select(type => new object[] { type })
.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
这给出了如下测试输出:
所以每种类型都是单独报告的。
哇,这很简单 - 那又是什么问题?
现在在上面的示例中,您可以看到 single
(=float
) 的测试正在通过。现在看模块:
public class MyModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<float>();
}
}
当我们实际尝试通过以下方式解决 float
时:
container.Resolve<float>();
事情是这样的:
Autofac.Core.DependencyResolutionException : No constructors on type 'System.Single' can be found with the constructor finder 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'.
当然,我们可以调整测试以执行 Resolve(Type t)
而不是使用 IsRegistered(Type t)
- 但是还有很多其他方法可以使测试通过 - 但实施失败。例如:
- 绑定类型使用
builder.RegisterInstance<IFoo>(null)
- 更改 lifetime/scopes 使其不再正常工作。
终于找到测试方法了。 autofac 模块有一个注册组件的方法 Configure 。我是这样做的:
public class CheckRegistrations
{
[Test]
public void Should_Have_Register_Types()
{
//Arrange
var typesToCheck = new List<Type>
{
typeof (ISomeMathServiceA),
typeof (ISomeMathServiceB)
};
//Act
var typesRegistered = this.GetTypesRegisteredInModule(new IoCModule());
//Arrange
Assert.AreEqual(typesToCheck.Count, typesRegistered.Count());
foreach (var typeToCheck in typesToCheck)
{
Assert.IsTrue(typesRegistered.Any(x => x == typeToCheck), typeToCheck.Name + " was not found in module");
}
}
private IEnumerable<Type> GetTypesRegisteredInModule(Module module)
{
IComponentRegistry componentRegistry = new ComponentRegistry();
module.Configure(componentRegistry);
var typesRegistered =
componentRegistry.Registrations.SelectMany(x => x.Services)
.Cast<TypedService>()
.Select(x => x.ServiceType);
return typesRegistered;
}
}