【问题标题】:xUnit theory test using generics使用泛型的 xUnit 理论测试
【发布时间】:2017-01-26 23:51:17
【问题描述】:

在 xUnit 中,我可以有一个 Theory 测试,它以这种形式使用泛型:

[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 为具有如下签名的测试指定泛型类型参数:

[Theory]
[MemberData(SomeTypeScenario)]
public void TestMethod<T>()
{
    Assert.Equal(typeof(double), typeof(T));
}

如果 MemberData 或任何其他提供的属性(我怀疑它不是)不可能,是否可以为 Xunit 创建一个可以实现此目的的属性?也许类似于在 Scenarios 方法中指定类型并以类似于 Jon Skeet 的回答的方式使用反射:Generics in C#, using type of a variable as parameter

【问题讨论】:

  • NUnit 的类型推断技巧也适用于 xunit:stackoverflow.com/a/43339950/3205
  • @skolima:请提供这个作为答案。我差点错过了这个很棒的功能!

标签: c# generics attributes xunit


【解决方案1】:

您可以简单地将Type 作为输入参数包含在内。例如:

[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 上没有必要使用泛型。

编辑(如果你真的需要泛型)

1) 你需要继承ITestMethod 来保存泛型方法信息,它还必须实现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的子类

// 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 属性将其挂钩到您的自定义发现器

// 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));
}

【讨论】:

  • 我在问题中给出的例子是一个简单的例子。如果我想测试一个只接受泛型参数的泛型方法,例如:public int MyMethod&lt;T&gt;(string someInput)?我可以调用这个方法,使用反射传入Type,但这会变得混乱。
  • 我在Xunit.Sdk.LongLivedMarshalByRefObject 上遇到了程序集引用错误。我错过了什么吗?
  • @Ayb4btu - 你可以用 MarshalByRefObject 代替
  • Xunit.LongLivedMarshalByRefObject 可用,但 Visual Studio 测试资源管理器似乎找不到测试。一切都将 Contosco 作为命名空间。可能是 xUnit 版本或 dotnet core(我正在使用)问题?
  • 我收到消息:System.InvalidOperationException:不能对 ContainsGenericParameters 为 true 的类型或方法执行后期绑定操作。堆栈跟踪: RuntimeMethodInfo.ThrowNoInvokeException() RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] 参数, CultureInfo 文化) RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] 参数, CultureInfoculture) MethodBase.Invoke(Object obj, Object[] 参数)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-17
  • 2022-12-10
  • 1970-01-01
  • 2019-11-21
  • 2015-11-20
相关资源
最近更新 更多