问题描述
在 xUnit 中,我可以有一个 Theory
测试,它以这种形式使用泛型:
In xUnit I can have a Theory
test that uses generics in this form:
[Theory]
[MemberData(SomeScenario)]
public void TestMethod<T>(T myType)
{
Assert.Equal(typeof(double), typeof(T));
}
public static IEnumerable<object[]> SomeScenario()
{
yield return new object[] { 1.23D };
}
这会给我作为 double
的通用 T
参数.是否可以使用 MemberData
为具有如下签名的测试指定泛型类型参数:
Which will give me the generic T
parameter as double
. Is it possible to use MemberData
to specify the generic type parameter for a test with a signature like:
[Theory]
[MemberData(SomeTypeScenario)]
public void TestMethod<T>()
{
Assert.Equal(typeof(double), typeof(T));
}
如果 MemberData
或任何其他提供的属性(我怀疑它不是)不可能,是否可以为 Xunit 创建一个可以实现此目的的属性?也许类似于在场景方法中指定类型并以类似于 Jon Skeet 在此处的回答的方式使用反射:Generics in C#, using变量的类型作为参数
If it is not possible with MemberData
or any other provided attribute (which I'm suspecting that it isn't), is it possible to create an attribute for Xunit that can achieve this? Maybe something along the lines of specifying Types in the Scenarios method and using reflection in a similar manner to Jon Skeet's answer here: Generics in C#, using type of a variable as parameter
推荐答案
您可以简单地包含 Type
作为输入参数.例如:
You can simply include Type
as an input parameter instead. E.g.:
[Theory]
[MemberData(SomeTypeScenario)]
public void TestMethod(Type type) {
Assert.Equal(typeof(double), type);
}
public static IEnumerable<object[]> SomeScenario() {
yield return new object[] { typeof(double) };
}
在 xunit 上没有必要使用泛型.
There is no need to go with generics on xunit.
编辑(如果你真的需要泛型)
1) 你需要继承 ITestMethod
来保存泛型方法信息,它还必须实现 IXunitSerializable
1) You need to subclass ITestMethod
to persist generic method info, it also has to implement IXunitSerializable
// assuming namespace Contosco
public class GenericTestMethod : MarshalByRefObject, ITestMethod, IXunitSerializable
{
public IMethodInfo Method { get; set; }
public ITestClass TestClass { get; set; }
public ITypeInfo GenericArgument { get; set; }
/// <summary />
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public GenericTestMethod()
{
}
public GenericTestMethod(ITestClass @class, IMethodInfo method, ITypeInfo genericArgument)
{
this.Method = method;
this.TestClass = @class;
this.GenericArgument = genericArgument;
}
public void Serialize(IXunitSerializationInfo info)
{
info.AddValue("MethodName", (object) this.Method.Name, (Type) null);
info.AddValue("TestClass", (object) this.TestClass, (Type) null);
info.AddValue("GenericArgumentAssemblyName", GenericArgument.Assembly.Name);
info.AddValue("GenericArgumentTypeName", GenericArgument.Name);
}
public static Type GetType(string assemblyName, string typeName)
{
#if XUNIT_FRAMEWORK // This behavior is only for v2, and only done on the remote app domain side
if (assemblyName.EndsWith(ExecutionHelper.SubstitutionToken, StringComparison.OrdinalIgnoreCase))
assemblyName = assemblyName.Substring(0, assemblyName.Length - ExecutionHelper.SubstitutionToken.Length + 1) + ExecutionHelper.PlatformSuffix;
#endif
#if NET35 || NET452
// Support both long name ("assembly, version=x.x.x.x, etc.") and short name ("assembly")
var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == assemblyName || a.GetName().Name == assemblyName);
if (assembly == null)
{
try
{
assembly = Assembly.Load(assemblyName);
}
catch { }
}
#else
System.Reflection.Assembly assembly = null;
try
{
// Make sure we only use the short form
var an = new AssemblyName(assemblyName);
assembly = System.Reflection.Assembly.Load(new AssemblyName { Name = an.Name, Version = an.Version });
}
catch { }
#endif
if (assembly == null)
return null;
return assembly.GetType(typeName);
}
public void Deserialize(IXunitSerializationInfo info)
{
this.TestClass = info.GetValue<ITestClass>("TestClass");
string assemblyName = info.GetValue<string>("GenericArgumentAssemblyName");
string typeName = info.GetValue<string>("GenericArgumentTypeName");
this.GenericArgument = Reflector.Wrap(GetType(assemblyName, typeName));
this.Method = this.TestClass.Class.GetMethod(info.GetValue<string>("MethodName"), true).MakeGenericMethod(GenericArgument);
}
}
2) 您需要为泛型方法编写自己的发现器,它必须是 IXunitTestCaseDiscoverer
2) You need to write your own discoverer for generic methods, it has to be subclass of IXunitTestCaseDiscoverer
// assuming namespace Contosco
public class GenericMethodDiscoverer : IXunitTestCaseDiscoverer
{
public GenericMethodDiscoverer(IMessageSink diagnosticMessageSink)
{
DiagnosticMessageSink = diagnosticMessageSink;
}
protected IMessageSink DiagnosticMessageSink { get; }
public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions,
ITestMethod testMethod, IAttributeInfo factAttribute)
{
var result = new List<IXunitTestCase>();
var types = factAttribute.GetNamedArgument<Type[]>("Types");
foreach (var type in types)
{
var typeInfo = new ReflectionTypeInfo(type);
var genericMethodInfo = testMethod.Method.MakeGenericMethod(typeInfo);
var genericTestMethod = new GenericTestMethod(testMethod.TestClass, genericMethodInfo, typeInfo);
result.Add(
new XunitTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(),
genericTestMethod));
}
return result;
}
}
3) 最后,您可以为泛型方法创建属性,并通过 XunitTestCaseDiscoverer
属性将其挂钩到您的自定义发现器
3) Finally you can make your attribute for generic methods and hook it to your custom discoverer by XunitTestCaseDiscoverer
attribute
// assuming namespace Contosco
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("Contosco.GenericMethodDiscoverer", "Contosco")]
public sealed class GenericMethodAttribute : FactAttribute
{
public Type[] Types { get; private set; }
public GenericMethodAttribute(Type[] types)
{
Types = types;
}
}
用法:
[GenericMethod(new Type[] { typeof(double), typeof(int) })]
public void TestGeneric<T>()
{
Assert.Equal(typeof(T), typeof(double));
}
这篇关于使用泛型的 xUnit 理论测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!