Параметр передачи С# NUnit TestCaseSource

У меня есть следующий метод, который генерирует набор тестовых случаев!

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

Как передать параметр, который имеет тип string в качестве параметра для моего метода PrepareTestCases()?

Есть ли способ сделать следующее:

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

Ответ 1

Если вы посмотрите TestCaseSourceAttribute doc, вы увидите, что нет способа передать этот параметр методу, возвращающему тестовые примеры.

Метод, который генерирует тестовые примеры, должен быть без параметров.

Итак, предполагая, что вы избегаете дублирования кода, и вам нужно повторно использовать один и тот же метод для создания нескольких списков тестовых примеров, я бы рекомендовал вам сделать следующее:

  • Напишите параметризованный метод, который на самом деле генерирует набор тестовых примеров:
    (PrepareTestCases() уже делает это)

    public IEnumerable<ResultsOfCallMyMethod> PrepareTestCases(string param)
    {
        foreach (string entry in entries)
        {
            yield return CallMyMethod(param);
        }
    }
    
  • Записывайте безпараметрические обертки, которые вызывают генератор тестовых случаев и передают требуемый параметр:

    public IEnumerable<ResultsOfCallMyMethod> PrepareTestCases_Param1()
    {
        return PrepareTestCases("param1");
    }
    
    public IEnumerable<ResultsOfCallMyMethod> PrepareTestCases_Param2()
    {
        return PrepareTestCases("param2");
    }
    
  • Напишите методы тестирования и передайте безрадиционные обертки там в качестве источников тестового случая:

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

Ответ 2

Я внес изменения для этой версии в последней версии 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);
    }
  }
}

Ответ 3

В моем случае я хотел бы загрузить данные из CSV файла, но я не смог передать имя файла в "источник данных". После битвы немного я прихожу к этому двухцентровому решению.

Сначала я унаследовал 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)
{
}

Надеюсь, это поможет немного.