我正在密切使用NUnit TestCase属性。对于我的一些测试,使用20多个TestCase属性进行注释,定义了20多个测试用例。但是,我想测试所有20个测试用例,并说出额外的值,可能是1或0.这对我来说意味着不同的测试用例。可以使用ValuesAttribute:
轻松实现我目前的状态:
[TestCase(10, "Hello", false)] // 1
[TestCase(33, "Bye", true)] // 2
// imagine 20+ testcase here)]
[TestCase(55, "CUL8R", true)] // 20+
public void MyTest(int number, string text, bool result)
我想做类似的事情(我不能做的事情)。
[TestCase(10, "Hello", false)] // 1
[TestCase(33, "Bye", true)] // 2
// imagine 20+ testcase here)]
[TestCase(55, "CUL8R", true)] // 20+
public void MyTest([Values(0,1)] int anyName, int number, string text, bool result)
为什么我要这样做?因为这40多个组合意味着不同的测试用例。不幸的是,NUnit不允许同时使用[TestCase]和[Values]属性,测试运行器希望完全与TestCaseAttribute中列出的参数数量相同。 (我可以理解建筑师,但仍然...) 我唯一能想到的就是:
[TestCase(1, 10, "Hello", false] // 1
[TestCase(1, 33, "Bye", true] // 2
// imagine 20+ testcase here]
[TestCase(1, 55, "CUL8R", true] // 20
[TestCase(0, 10, "Hello", false] // 21
[TestCase(0, 33, "Bye", true] // 22
// imagine 20+ testcase here]
[TestCase(0, 55, "CUL8R", true] // 40
public void MyTest(int anyName, int number, string text, bool result)
所以我最终被迫犯了复制和粘贴的罪,我复制了TestCases,现在我有40+。必须有某种方式......如果不仅仅是(0,1)值的范围,而是0,1,2,3。我们以80多个复制的测试用例结束了吗?
错过了什么?
提前谢谢
答案 0 :(得分:3)
这是我的第一页搜索结果,尽管年代久远,但我希望它比现有解决方案轻一些。
在测试用例中使用另一个ValuesAttribute
或ValueSourceAttribute
可以进行组合,但是诀窍是在使用它们时将一组链接值作为单个案例使用-每个仅影响一个命名参数。
属性需要编译时常量输入。这样可以创建一个文字数组,但是如果您的值具有不同的类型,则必须使该数组成为常见的基本类型,例如object
。您还必须按索引访问项目。我喜欢简短的,显而易见的单元测试;解析数组会使测试看起来很繁琐。
一种简洁的解决方案是将ValueSource
属性与提供元组的静态方法一起使用。该方法可以紧接测试方法之前,并且比使用TestCase
属性稍微冗长。使用问题中的代码:
public static (int, string, bool)[] Mytest_TestCases()
{
return new[] {
(10, "Hello", false),
(33, "Bye", true),
(55, "CUL8R", true)
};
}
[Test]
public void Mytest(
[Values(0,1)] int anyName,
[ValueSource(nameof(Mytest_TestCases))] (int number, string text, bool result) testCase)
ValueSource
之后的参数是一个名为testCase
的元组,其内容被命名为与原始测试用例参数匹配。要在测试中引用这些值,请在其前面加上元组的名称(例如testCase.result
而不只是result
)。
如此处所述,将运行六项测试-针对anyName
和testCase
的每种可能组合进行一次测试。
我不介意元组中的简单数据,例如本例,但我走得更远,并定义了一个非常简单的类来代替元组。用法基本相同,只是您没有在参数中命名成员。
public class Mytest_TestCase
{
public Mytest_TestCase(int number, string text, bool result)
{
Number = number;
Text = text;
Result = result;
}
public int Number;
public string Text;
public bool Result;
}
public static Mytest_TestCase[] Mytest_TestCases()
{
return new[] {
new Mytest_TestCase(10, "Hello", false),
new Mytest_TestCase(33, "Bye", true),
new Mytest_TestCase(55, "CUL8R", true)
};
}
[Test]
public void Mytest(
[Values(0,1)] int anyName,
[ValueSource(nameof(Mytest_TestCases))] Mytest_TestCase testCase)
可以将测试用例类的定义移至测试类的底部或单独的文件中。就个人而言,我更喜欢将两者都放在测试类的底部-当您想在测试旁边看到它时,可以随时查看定义。
答案 1 :(得分:1)
首先,提供测试数据的类本身就是测试代码,因此是第一类代码,应该这样维护。
如果你给这个类一个有意义的名字,我认为它可以是可读的和可维护的,或许更多,因为你的测试数据在一个地方。
这就是我实施你的案子的方式:
创建一个类,我们称之为具有int,string和bool属性的TestCaseGenerator。它看起来像这样:
public class TestCaseGenerator : IEnumerable {
#region Attributes
internal static List<TestCase> TestCases { get; set; }
internal int myInt { get; set; }
internal string myString { get; set; }
internal bool myBoolean { get; set; }
#endregion
static TestCaseGenerator() {
var json = <jsonRepresentationOfMyTestData>
TestCases = JsonConvert.DeserializeObject<List<TestCase>>(json);
}
public IEnumerator GetEnumerator() {
return TestCases != null
? TestCases.Select(x => new TestCase(x.myInt, x.myString, x.myBool) ?? "null")).GetEnumerator()
: null;
}
}
构建包含您的测试的类:
public class MyTestClass {
[TestCaseSource(typeof(TestCaseGenerator))]
public void MyTest(TestCase case) {
// Do useful things with case.myInt, case.myString and case.myBool
}
}
一旦你有了这个基础设施,就没有什么可说的,TestCaseGenerator.GetEnumerator()会在一个TestCase对象列表上返回一个枚举器,但是不能以返回两个较短列表的组合列表的方式编写。
创建一个组合的可枚举(借用此post)将如下所示:
class Program {
static void Main(string[] args) {
var firstArray = new object[][] { new object[] { 1, "A", false },
new object[] { 2, "B", false },
new object[] { 3, "C", false },
new object[] { 4, "D", true },
new object[] { 5, "E", true }
};
var secondArray = new object[] { 6, 7 };
var joiner = new Joiner();
IEnumerable<IEnumerable<object>> result = joiner.Join(firstArray.ToList(), secondArray.ToList());
//result = new object[] { new object[] { 6, 1, "A", false },
// new object[] { 7, 1, "A", false },
// new object[] { 6, 2, "B", false },
// new object[] { 7, 2, "B", false },
// new object[] { 6, 3, "C", false },
// new object[] { 7, 3, "C", false },
// new object[] { 6, 4, "D", true },
// new object[] { 7, 4, "D", true },
// new object[] { 6, 5, "E", true },
// new object[] { 7, 5, "E", true }
// };
}
}
public class Joiner
{
public IEnumerable<IEnumerable<object>> Join(IEnumerable<IEnumerable<object>> source1, IEnumerable<object> source2) {
foreach (IEnumerable<object> s1 in source1) {
foreach (object s2 in source2) {
yield return (new[] { s2 }).Concat(s1).ToArray();
}
}
}
}