有没有更好的方法将动态输入内联传递给DataTestMethod?即如何以编程方式为数据驱动的测试创建测试输入

时间:2017-06-27 20:55:26

标签: c# .net-core mstest data-driven-tests

我多年来一直在寻找这个,我想我终于在" MSTest V2" (意思是.netcore附带的那个,并且在Visual Studio 2017中才真正正确处理)。请参阅我的答案以获得我的解决方案。

这对我来说解决的问题是我的输入数据不容易序列化,但我的逻辑需要使用许多这些输入进行测试。这样做有很多理由为什么这样做更好,但这对我来说是个噱头;我被迫进行了一次巨大的单元测试,并通过我的输入进行for循环。到现在为止。

2 个答案:

答案 0 :(得分:10)

您现在可以使用DynamicDataAttribute:

[DynamicData("TestMethodInput")]
[DataTestMethod]
public void TestMethod(List<string> list)
{
    Assert.AreEqual(2, list.Count);
}

public static IEnumerable<object[]> TestMethodInput
{
    get
    {
        return new[]
        {
            new object[] { new List<string> { "one" } },
            new object[] { new List<string> { "one", "two" } },
            new object[] { new List<string> { "one", "two", "three" } }
        };
    }
}

https://dev.to/frannsoft/mstest-v2---new-old-kid-on-the-block

有一个很好的缩写

https://blogs.msdn.microsoft.com/devops/2017/07/18/extending-mstest-v2/

有更多血腥的细节

答案 1 :(得分:4)

因此新的DataTestMethodAttribute类是可覆盖的,它允许使用此签名覆盖方法:

public override TestResult[] Execute(ITestMethod testMethod);

一旦我发现了这一点,很简单:我只是派生出来,弄清楚我的输入,然后在我的Execute方法中循环遍历它们。我走了几步,为了让它易于重复使用。

因此,首先是一个覆盖该Execute方法的基类,并公开一个返回IEnumerable的抽象GetTestInputs()方法。您可以从中派生任何可以实现该方法的类型。

public abstract class DataTestMethodWithProgrammaticTestInputs : DataTestMethodAttribute
{
    protected Lazy<IEnumerable> _items;

    public DataTestMethodWithProgrammaticTestInputs()
    {
        _items = new Lazy<IEnumerable>(GetTestInputs, true);
    }

    protected abstract IEnumerable GetTestInputs();

    public override TestResult[] Execute(ITestMethod testMethod)
    {
        var results = new List<TestResult>();
        foreach (var testInput in _items.Value)
        {
            var result = testMethod.Invoke(new object[] { testInput });
            var overriddenDisplayName = GetDisplayNameForTestItem(testInput);
            if (!string.IsNullOrEmpty(overriddenDisplayName))
                result.DisplayName = overriddenDisplayName;
            results.Add(result);
        }
        return results.ToArray();
    }

    public virtual string GetDisplayNameForTestItem(object testItem)
    {
        return null;
    }
}

接下来,我创建了一个派生类型,它使用反射来实例化一个类型,然后在创建的实例上调用属性的get方法。此类型可以直接用作属性,但是从中派生,实现GetDisplayNameForTestItem方法,并且绑定到特定类型是一个好主意,特别是如果您有多个测试使用相同的数据。

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DataTestMethodWithTestInputsFromClassPropertyAttribute : DataTestMethodWithProgrammaticTestInputs
{
    private Type _typeWithIEnumerableOfDataItems;
    private string _nameOfPropertyWithData;

    public DataTestMethodWithTestInputsFromClassPropertyAttribute(
        Type typeWithIEnumerableOfDataItems,
        string nameOfPropertyWithData)
        : base()
    {
        _typeWithIEnumerableOfDataItems = typeWithIEnumerableOfDataItems;
        _nameOfPropertyWithData = nameOfPropertyWithData;
    }

    protected override IEnumerable GetTestInputs()
    {
        object instance;
        var defaultConstructor = _typeWithIEnumerableOfDataItems.GetConstructor(Type.EmptyTypes);
        if (defaultConstructor != null)
            instance = defaultConstructor.Invoke(null);
        else
            instance = FormatterServices.GetUninitializedObject(_typeWithIEnumerableOfDataItems);

        var property = _typeWithIEnumerableOfDataItems.GetProperty(_nameOfPropertyWithData);
        if (property == null)
            throw new Exception($"Failed to find property named {_nameOfPropertyWithData} in type {_typeWithIEnumerableOfDataItems.Name} using reflection.");
        var getMethod = property.GetGetMethod(true);
        if (property == null)
            throw new Exception($"Failed to find get method on property named {_nameOfPropertyWithData} in type {_typeWithIEnumerableOfDataItems.Name} using reflection.");
        try
        {
            return getMethod.Invoke(instance, null) as IEnumerable;
        }
        catch (Exception ex)
        {
            throw new Exception($"Failed when invoking get method on property named {_nameOfPropertyWithData} in type {_typeWithIEnumerableOfDataItems.Name} using reflection.  Exception was {ex.ToString()}");
        }
    }
}

最后,这是一个正在使用的派生属性类型的示例,可以很容易地用于许多测试:

[TestClass]
public class MyTestClass
{
    public class MyTestInputType{public string Key; public Func<string> F; }
    public IEnumerable TestInputs 
    {
        get
        {
            return new MyTestInputType[] 
            { 
                new MyTestInputType(){ Key = "1", F = () => "" }, 
                new MyTestInputType() { Key = "2", F = () => "2" } 
            };
        }
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public class DataTestMethodWithTestInputsFromThisTestProjectAttribute : DataTestMethodWithTestInputsFromClassPropertyAttribute
    {
        public DataTestMethodWithTestInputsFromThisTestProjectAttribute() 
            : base(typeof(MyTestClass), nameof(MyTestClass.TestInputs)) { }

        public override string GetDisplayNameForTestItem(object testItem)
        {
            var asTestInput = testItem as MyTestInputType;
            if (asTestInput == null)
                return null;
            return asTestInput.Key;
        }
    }

    [DataTestMethodWithTestInputsFromThisTestProject]
    public void TestMethod1(MyTestInputType testInput)
    {
         Assert.IsTrue(testInput.Key == testInput.F());
    }

    [DataTestMethodWithTestInputsFromThisTestProject]
    public void TestMethod2(MyTestInputType testInput)
    {
        Assert.IsTrue(string.IsNullOrEmpty(testInput.F()));
    }
}

就是这样。任何人都有更好的mstest方法吗?