【问题标题】:How can I mock a property of a custom Attribute in order to test it?如何模拟自定义属性的属性以对其进行测试?
【发布时间】:2018-08-01 12:21:08
【问题描述】:

我无法弄清楚如何测试需要模拟内部状态属性的自定义属性。 Attribute 增加了一个扩展方法。

Configuration 属性是静态属性,在实践中可以正常工作,但在运行并行测试时不起作用。

我所拥有的一些伪代码:

public class MyAttribute : Attribute
{
    // Store a complex object that cannot be passed into constructor
    private static IConfiguration _configuration;

    public static IConfiguration
    {
        get {
            if (_configuration == null)
                _configuration = new Configuration();
            return _configuration;
        }
        set {
            _configuration => value;
        }
    }

    public MyAttribute(string foo)
    {
        // foo is a simple type
    }
}

public static class MyExtensionClass
{
    public static Task<T> DoSomething<T>(this Delegate method, params object[] args)
    {
        DoSomethingWithConfiguration(MyAttribute.Configuration);

        // or if I make Configuration an instance property
        MethodInfo mi = method.GetMethodInfo();
        MyAttribute attr = mi.GetCustomAttribute<MyAttribute>();
        DoSomethingWithConfiguration(attr.Configuration);

        return ....
    }
}

一个示例测试:

public class TestClass
{
    [MyAttribute(foo="bar")]
    public int Add(int x, int y)
    {
        return x + y;
    }

    delegate int AddDelegate(int x, int y);

    [Fact]
    public void TestSomething()
    {
        Delegate d = new AddDelegate(Add);
        Mock<IConfiguration> mockConfig = new Mock<IConfiguration>();
        mockConfig.Setup(.....); // set up mocked interface
        // ** Need to inject mock into Attribute class somehow

        // ** This doesn't work with parallel test runs **
        MyAttribute.Configuration = mockConfig.Object;
        var result = d.DoSomething<int>(1, 2);
    }
}

如果我模拟 IConfiguration 并设置静态属性,那么当我的测试并行运行时,该值将被覆盖并且测试随机失败。我知道这是错误的做事方式。具体来说,Configuration 类间接连接到服务器(通过工厂方法),因此需要模拟它。 我想我需要在 MyAttribute 实例执行任何操作之前以某种方式获取它,但我不知道如何。我被困在如何重构它以使其可测试。

【问题讨论】:

    标签: c# unit-testing .net-core moq xunit.net


    【解决方案1】:

    当我的测试并行运行时,值被覆盖并且测试随机失败。

    发生这种情况是因为您的 IConfiguration 实例是静态的。这意味着MyAttribute 的任何实例都将与其他所有实例共享相同的状态,并且_configuration 的值将针对每个实例 设置为已设置的最后一个值on任何实例。就像电影汉兰达只能有一个

    private static IConfiguration _configuration;
    

    [我要]存储一个不能传入构造函数的复杂对象

    你不能。这是属性的限制,因为它们的目的是存储元数据,这些元数据被编译到程序集中。

    他们只能接受有限数量的类型 (reference):

    属性类的位置参数和命名参数的类型仅限于属性参数类型,它们是:

    • 以下类型之一:bool、byte、char、double、float、int、long、sbyte、short、string、uint、ulong、ushort。
    • 类型对象。
    • System.Type 类型。
    • 枚举类型,前提是它具有公共可访问性,并且它所嵌套的类型(如果有)也具有公共可访问性(第 17.2 节)。
    • 上述类型的一维数组。

    不具有这些类型之一的构造函数参数或公共字段不能用作属性规范中的位置参数或命名参数。

    因此,您尝试做的事情是不可能的。

    此外,根据Passive Attributes,制作包含行为的属性并不是很实用。

    这种方法的问题在于属性实例是由运行时创建的,因此您不能使用正确的依赖注入 (DI) 模式,例如构造函数注入。如果一个属性定义了行为(许多 Web API 属性都是这样做的),那么编写松散耦合代码的最常见尝试是诉诸静态服务定位器(一种反模式)。

    没有依赖注入模式,很难测试类内的逻辑。

    我不知道如何重构它以使其可测试。

    由于您正在尝试做一些甚至不可能的事情(即,将属性值设置为它不支持的类型),因此很难给您任何建设性的建议。

    我唯一能建议的是不要将属性用于标记以外的任何东西。您可以用它们装饰成员,并使用应用程序其他(可测试)部分的反射来读取它们的状态,以控制应用程序的行为,但不要将任何行为放入属性中。

    【讨论】:

    • 我通过为所有测试创建一个全局最小起订量设置并在Setup() 中进行具体说明,以便每个测试都与预期的测试完全匹配,从而使我的测试通过。现在所有测试都使用存储在静态属性中的相同模拟对象,我又回到了“绿色”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-05
    • 1970-01-01
    • 2016-10-26
    • 1970-01-01
    • 2014-11-01
    • 2019-12-17
    相关资源
    最近更新 更多