C# NUnit TestCaseSource 传递参数

17
我有一个方法,可以生成一组测试用例!
public IEnumerable<ResultsOfCallMyMethod> PrepareTestCases(param1)
{
    foreach (string entry in entries)
    {
        yield return callMyMethod(param1);
    }
}

我应该如何将一个字符串类型的参数传递给我的PrepareTestCases()方法?

是否有以下方式可以实现:

[Test, Category("Integration"), TestCaseSource("PrepareTestCases", param1)]
public void TestRun(ResultsOfCallMyMethod testData)
{
    // do something!
}

1
https://dev59.com/fF_Va4cB1Zd3GeqPR0z9 - Khanh TO
3
这不是我要找的! - joesan
3个回答

26

我已在即将发布的nunit最新版本(3.2)中为此进行了更改。

https://github.com/nunit/nunit/blob/4f54fd7e86f659682e7a538dfe5abee0c33aa8b4/CHANGES.txt

  • TestCaseSourceAttribute现在可选地接受一个参数数组,该数组可以传递给源方法

现在可以像这样做:

[Test, Category("Integration"), TestCaseSource(typeof(MyDataSources),"PrepareTestCases", new object[] {param1})]
public void TestRun(ResultsOfCallMyMethod testData)
{
// do something!
}

private class MyDataSources
{
  public IEnumerable<ResultsOfCallMyMethod> PrepareTestCases(param1)
  {
    foreach (string entry in entries)
    {
        yield return callMyMethod(param1);
    }
  }
}

我在这里遇到了编译错误:Error CS0182: 属性参数必须是常量表达式、typeof表达式或属性参数类型的数组创建表达式。我错过了什么? - g.pickardou
3
没关系,我弄明白了:必须显式地编写 object[]。顺便说一下,尽管出现错误消息,也可以使用 nameof()。 - g.pickardou

12

如果您查看TestCaseSourceAttribute文档,您会发现没有任何方法可以将参数传递给返回测试用例的方法。

生成测试用例的方法应该是无参的

因此,假设您要避免代码重复并且需要重用相同的方法来生成几个测试用例列表,我建议您执行以下操作:

  1. Write parameterized method which actually generates test cases sets:
    (PrepareTestCases() already does that)

    public IEnumerable<ResultsOfCallMyMethod> PrepareTestCases(string param)
    {
        foreach (string entry in entries)
        {
            yield return CallMyMethod(param);
        }
    }
    
  2. Write parameterless wrappers which call test cases generator and pass desired parameter there:

    public IEnumerable<ResultsOfCallMyMethod> PrepareTestCases_Param1()
    {
        return PrepareTestCases("param1");
    }
    
    public IEnumerable<ResultsOfCallMyMethod> PrepareTestCases_Param2()
    {
        return PrepareTestCases("param2");
    }
    
  3. Write test methods and pass paremeterless wrappers there as test case sources:

    [TestCaseSource("PrepareTestCases_Param1")]
    public void TestRun1(ResultsOfCallMyMethod data)
    {
    }
    
    [TestCaseSource("PrepareTestCases_Param2")]
    public void TestRun2(ResultsOfCallMyMethod data)
    {
    }
    

2
我认为PrepareTestCases_Param1等需要是静态的:“在TestCaseSourceAttribute上指定的sourceName必须引用静态字段、属性或方法。” - oatsoda

1
在我的情况下,我想从CSV文件中加载数据,但是我无法将文件名传递给“datasource”。 经过一番努力,我得到了这个两分钱的解决方案。
首先,我继承了TestCaseSourceAttirbute。
/// <summary>
/// FactoryAttribute indicates the source to be used to provide test cases for a test method.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class TestCaseCsvAttribute : TestCaseSourceAttribute 
{
    public TestCaseCsvAttribute(Type mapped, Type config) : base(typeof(TestCsvReader<,>).MakeGenericType(mapped, config), "Data")
    { }
}

然后我创建了数据层,这里使用的是CSV读取器。

    /// <summary>
    /// Test data provider
    /// </summary>
    /// <typeparam name="T">Type to return in enumerable</typeparam>
    /// <typeparam name="C">Configuration type that provide Filenames</typeparam>
    public sealed class TestCsvReader<T, C>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="TestCsvReader{T, C}"/> class.
        /// </summary>
        public TestCsvReader()
        {
            this.Config = (C)Activator.CreateInstance<C>();
        }

        /// <summary>
        /// Gets or sets the configuration.
        /// </summary>
        /// <value>
        /// The configuration.
        /// </value>
        private C Config { get; set; }

        /// <summary>
        /// Gets the filename.
        /// </summary>
        /// <value>
        /// The filename.
        /// </value>
        /// <exception cref="System.Exception">
        /// </exception>
        private string Filename
        {
            get
            {
                try
                {
                    string result = Convert.ToString(typeof(C).GetProperty(string.Format("{0}Filename", typeof(T).Name)).GetValue(this.Config));
                    if (!File.Exists(result))
                        throw new Exception(string.Format("Unable to find file '{0}' specified in property '{1}Filename' in class '{1}'", result, typeof(C).Name));

                    return result;
                }
                catch
                {
                    throw new Exception(string.Format("Unable to find property '{0}Filename' in class '{1}'", typeof(T).Name, typeof(C).Name));
                }
            }
        }

        /// <summary>
        /// Yields values from source
        /// </summary>
        /// <returns></returns>
        public IEnumerable Data()
        {
            string file = this.Filename;

            T[] result = null;
            using (StreamReader reader = File.OpenText(file))
            {
                //TODO: do it here your magic
            }
            yield return new TestCaseData(result);
        }
}

然后我创建了一个类,其唯一的作用是包含文件路径属性。有一个关于属性的命名约定,即ClassTypeName +“Filename”。

public class Configurations
{
    public string ConflictDataFilename
    {
        get
        {
            return @"C:\test.csv";
        }
    }
}

此时只需要根据数据映射的类别和包含文件路径的类别进行相应的装饰即可。
[Test(Description="Try this one")]
[TestCaseCsv(typeof(ClassMappedToData), typeof(Configurations))]
public void Infinite(ClassMappedToData[] data)
{
}

希望这能有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接