如何避免或UnitTest意外使用具体的单例类型,而不是StructureMap的抽象

本文关键字:类型 单例 抽象 StructureMap UnitTest 何避免 意外 | 更新日期: 2023-09-27 18:00:15

我最近在代码中遇到了以下错误,我花了很长时间进行调试。我想基于它的接口注入一个实例,如下所示:

MovementController(IMotorController motorController)

然而,我不小心使用了这样的混凝土类型:

MovementController(MotorController motorController)

该项目仍然构建并运行良好,直到我尝试从MovementController实例访问motorController。由于IMotorController的底层实现访问硬件,因此它必须是一个singleton或my-locks代码。然而,由于我有其他带有注入IMotorController的类,我现在在对象图中有两个实例MotorController,它们都通过串行连接访问硬件。这在运行时导致了一个低得多的错误,我花了很长时间进行调试并找到真正的原因。

我如何避免这种类型的错误,或者为我的StructureMap注册表编写一个单元测试来捕捉这种微妙的错误?

如何避免或UnitTest意外使用具体的单例类型,而不是StructureMap的抽象

您可以使用NDepend等静态分析工具轻松检查这一点。使用它,您只需查找作为控制器的类型,然后检查它们的构造函数,并在发现任何不是接口类型的构造函数参数时发出警告。


为了完善Steve的答案,您可以编写一个代码规则,如下所示:(使用NDepend,代码规则是前缀为warnif count > 0的C#LINQ查询)

// <Name>Don't use MotorController, use IMotorController instead</Name>
warnif count > 0
from m in Application.Methods 
where m.IsUsing ("NamespaceA.MotorController ") &&
      m.ParentType.FullName != "NamespaceB.ClassThatCanUseMotorController "
select m

如果存在零个或多个ClassThatCanUseMotorController,则可以很容易地细化该规则。

最安全的解决方案是在运行时检查是否只创建了一个MotorController实例。例如,您可以使用静态计数器变量计算MotorController的实例数

public class MotorController : IMotorController
{
    private static bool instantiated;
    public MotorController(...)
    {
        if (instantiated)
            throw new InvalidOperationException(
                "MotorController can only be instantiated once.")
        ...
        instantiated = true;
    }
    ...
}

我通常会考虑这种糟糕的设计,因为类是否被用作单例是只有依赖注入框架才应该关心的问题。还要注意,这不是线程安全的。

好的。因此,我为单元测试想出的解决方案是,获取所有实现IMotorController的实例,并断言它们的计数等于1:

 var motorControllerInstances = container.GetAllInstances<IMotorController>().Select(x => x); // cast enumerable to List using Linq
 Assert.True(motorControllerInstances.Count == 1);

不确定这是最优雅的方式,但它似乎有效。

更新1:这段代码并没有抓住我的错误。我仍在为我的问题寻找一个正确的答案。

更新2:我越来越近了。如果您不小心注册了相应接口的具体类型,这至少会被捕获。然而,它似乎并没有检查它的实例是否真的被构建。

    var allInterfaceInstances = dicFixture.result.Model.GetAllPossible<IMotorController>();
    Assert.True(allInterfaceInstance.Count() == 1);

SOLID中尝试遵守D

依赖反转原理,其中一个人应该"依赖抽象。不要依赖具体

对于一个项目。在这种情况下,Asp.Net-MVC5,我想要一种方法来确保所有控制器(MVC和WebAPI2)都遵循这种模式,它们不依赖于具体化。

最初的想法来自我读过的一篇文章,其中创建了一个单元测试来扫描所有控制器,以确保它们定义了明确的授权。我在检查所有控制器是否都有依赖于抽象的构造函数时应用了类似的想法。

[TestClass]
public class ControllerDependencyTests : ControllerUnitTests {
    [TestMethod]
    public void All_Controllers_Should_Depend_Upon_Abstractions() {
        var controllers = UnitTestHelper.GetAssemblySources() //note this is custom code to get the assemblies to reflect.
            .SelectMany(assembly => assembly.GetTypes())
            .Where(t => typeof(IController).IsAssignableFrom(t) || typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(t));
        var constructors = controllers
            .SelectMany(type => type.GetConstructors())
            .Where(constructor => {
                var parameters = constructor.GetParameters();
                var result = constructor.IsPublic
                    && parameters.Length > 0
                    && parameters.Any(arg => arg.ParameterType.IsClass && !arg.ParameterType.IsAbstract);
                return result;
            });
        // produce a test failure error mssage if any controllers are uncovered
        if (constructors.Any()) {
            var errorStrings = constructors
                .Select(c => {
                    var parameters = string.Join(", ", c.GetParameters().Select(p => string.Format("{0} {1}", p.ParameterType.Name, p.Name)));
                    var ctor = string.Format("{0}({1})", c.DeclaringType.Name, parameters);
                    return ctor;
                }).Distinct();
            Assert.Fail(String.Format("'nType depends on concretion instead of its abstraction.'n{0} Found :'n{1}",
                            errorStrings.Count(),
                            String.Join(Environment.NewLine, errorStrings)));
        }
    }
}

下面举一个例子。(注意,我将其调整为MVC)

public class MovementController : Controller {
    public MovementController(MotorController motorController) {
        //...
    }
}
public interface IMotorController {
    //...
}
public class MotorController : IMotorController {
    //...
}

单元测试将失败。。。

Result Message: Assert.Fail failed. 
Type depends on concretion instead of its abstraction.
1 Found :
MovementController(MotorController motorController)

这对我很有效,因为我有一个IControllerApiController的通用类型。

这项测试还有改进的余地,但对你来说应该是一个很好的起点。