有没有办法将NUnit TestCaseAttribute与ValuesAttribute一起使用?

时间:2014-05-31 21:53:40

标签: c# nunit

我正在密切使用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多个复制的测试用例结束了吗?

错过了什么?

提前谢谢

2 个答案:

答案 0 :(得分:3)

这是我的第一页搜索结果,尽管年代久远,但我希望它比现有解决方案轻一些。

在测试用例中使用另一个ValuesAttributeValueSourceAttribute可以进行组合,但是诀窍是在使用它们时将一组链接值作为单个案例使用-每个仅影响一个命名参数。

属性需要编译时常量输入。这样可以创建一个文字数组,但是如果您的值具有不同的类型,则必须使该数组成为常见的基本类型,例如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)。

如此处所述,将运行六项测试-针对anyNametestCase的每种可能组合进行一次测试。

我不介意元组中的简单数据,例如本例,但我走得更远,并定义了一个非常简单的类来代替元组。用法基本相同,只是您没有在参数中命名成员。

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)

首先,提供测试数据的类本身就是测试代码,因此是第一类代码,应该这样维护。

如果你给这个类一个有意义的名字,我认为它可以是可读的和可维护的,或许更多,因为你的测试数据在一个地方。

这就是我实施你的案子的方式:

  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;
        }
     }
    
  2. 构建包含您的测试的类:

    public class MyTestClass {
        [TestCaseSource(typeof(TestCaseGenerator))]
        public void MyTest(TestCase case) {
            // Do useful things with case.myInt, case.myString and case.myBool
        }
    }
    
  3. 一旦你有了这个基础设施,就没有什么可说的,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();
                }
            }
        }
    }