有没有更好的方法将动态输入在线传递给 DataTestMethod? IE。如何以编程方式为数据驱动测试创建测试输入
Is there a better way to pass dynamic inputs in-line to a DataTestMethod? I.e. How to programmatically create test inputs for a data-driven test
多年来我一直在寻找这个,我想我终于在 "MSTest V2" 中找到了一个真正的方法(意思是 .netcore 附带的那个,并且只有正确处理在 Visual Studio 2017 年)。请参阅我的解决方案答案。
这为我解决的问题是我的输入数据不容易序列化,但我的逻辑需要用其中的许多输入进行测试。这样做更好的原因有很多,但这对我来说是个障碍;我被迫进行一个巨大的单元测试,其中有一个 for 循环遍历我的输入。直到现在。
因此新的 DataTestMethodAttribute class 是可覆盖的,它允许覆盖具有此签名的方法:
public override TestResult[] Execute(ITestMethod testMethod);
一旦我发现这一点,就很容易了:我只需推导、找出我的输入,然后在我的 Execute 方法中循环遍历它们。不过,为了使其易于重复使用,我又走了几步。
因此,首先是一个覆盖 Execute 方法的基础 class,并公开一个抽象的 GetTestInputs() 方法,其中 returns 一个 IEnumerable。您可以从中派生任何可以实现该方法的类型。
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 方法吗?
您现在可以使用 DynamicDataAttribute:
[DynamicData(nameof(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/
中有更多细节
多年来我一直在寻找这个,我想我终于在 "MSTest V2" 中找到了一个真正的方法(意思是 .netcore 附带的那个,并且只有正确处理在 Visual Studio 2017 年)。请参阅我的解决方案答案。
这为我解决的问题是我的输入数据不容易序列化,但我的逻辑需要用其中的许多输入进行测试。这样做更好的原因有很多,但这对我来说是个障碍;我被迫进行一个巨大的单元测试,其中有一个 for 循环遍历我的输入。直到现在。
因此新的 DataTestMethodAttribute class 是可覆盖的,它允许覆盖具有此签名的方法:
public override TestResult[] Execute(ITestMethod testMethod);
一旦我发现这一点,就很容易了:我只需推导、找出我的输入,然后在我的 Execute 方法中循环遍历它们。不过,为了使其易于重复使用,我又走了几步。
因此,首先是一个覆盖 Execute 方法的基础 class,并公开一个抽象的 GetTestInputs() 方法,其中 returns 一个 IEnumerable。您可以从中派生任何可以实现该方法的类型。
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 方法吗?
您现在可以使用 DynamicDataAttribute:
[DynamicData(nameof(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/
中有更多细节