我正在尝试在xunit.net中编写一个通用理论,该理论通过MemberDataAttribute
使用集合。请查看以下代码:
[Theory]
[MemberData("TestData")]
public void AddRangeTest<T>(List<T> items)
{
var sut = new MyCustomList<T>();
sut.AddRange(items);
Assert.Equal(items, sut);
}
public static readonly IEnumerable<object[]> TestData = new[]
{
new object[] { new List<int> { 1, 2, 3 } },
new object[] { new List<string> { "Foo", "Bar" } }
};
当我尝试执行此理论时,抛出以下异常:&#34; System.ArgumentException:类型&#39; List&#39; 1 [System.String] / List&#39; 1 [System.Int32]&#39;无法转换为类型&#39;列出&#39; [System.Object]&#39;。&#34; (我缩短并合并了两个例外的文本)。
我知道这可能与参数类型有关,因为它不是直接的泛型类型,而是使用嵌套泛型的类型。因此,我以下列方式改变了测试:
[Theory]
[MemberData("TestData")]
public void AddRangeTest2<T, TCollection>(TCollection items)
where TCollection : IEnumerable<T>
{
var sut = new MyCustomList<T>();
sut.AddRange(items);
Assert.Equal(items, sut);
}
在这种情况下,我引入了一个名为TCollection
的泛型,它被限制为IEnumerable<T>
个实例。当我执行此测试时,使用List<string>
运行,但使用List<int>
运行会产生以下异常:&#34; System.ArgumentException:GenericArguments [1],&#39;列表&#39; 1 [System.Int32]&#39;,on&#39; AddRangeTest2&#39;违反了类型&#39; TCollection&#39;。&#34; 的限制(再次,我将异常文本缩短到相关点)。
我的实际问题是:为什么在通用理论中可以使用List<string>
而不是List<int>
?这两种类型都满足{{1}的约束我认为是通用的。
如果我将TCollection
更改为List<int>
,那么一切正常 - 因此我认为它与值类型/参考类型和协变/反演有关。
我使用xunit 2.0.0-rc2虽然我也观察到了rc1的这种行为 - 因此我认为它与版本无关(甚至可能不是xunit的问题)。如果您想查看完整的源代码,可以从my dropbox下载(我使用VS 2013创建)。请将此代码视为MCVE。
非常感谢您的帮助!
关闭此问题后进行修改: Cannot convert from List<DerivedClass> to List<BaseClass>未回答我的问题,因为我根本没有使用基于继承的强制转换 - 而是相信xunit.net使用泛型这是通过反思解决的。
答案 0 :(得分:1)
无论你做什么,它仍然归结为在幕后进行IEnumerable<int>
到IEnumerable<object>
转换的事实。
您似乎假设通用参数是根据给定的具体输入填充的。
事实并非如此。泛型类型参数是从MemberData
中引用的变量推导出来的,而不是它所持有的实际内容,因此,您的测试方法将始终调用下一个签名:
AddRangeTest2<object, object[]>(object[] items)
上述签名支持隐式 引用类型的协方差(意思是:您可以将string[]
传递给此方法,但不能传递int[]
)。
Covariance for value types is not supported
<小时/> 这就是说,如果xUnit足够聪明,可以根据实际输入指定正确的泛型类型参数,那将会很酷。我自己没有使用xUnit的经验,也许已经有办法了吗?如果没有,请在codeplex中提供功能请求,或者自己做+拉请求;)