NUnit TestCase с универсальными шаблонами - PullRequest
41 голосов
/ 02 марта 2010

Есть ли способ передать универсальные типы с помощью TestCase в тест в NUnit?

Это то, что я хотел бы сделать, но синтаксис неверен ...

[Test]
[TestCase<IMyInterface, MyConcreteClass>]
public void MyMethod_GenericCall_MakesGenericCall<TInterface, TConcreteClass>()
{
    // Arrange

    // Act
    var response = MyClassUnderTest.MyMethod<TInterface>();

    // Assert
    Assert.IsInstanceOf<TConcreteClass>(response);
}

Или, если нет, каков наилучший способ достижения той же функциональности (очевидно, у меня будет несколько TestCases в реальном коде)?

Обновите на другом примере...

Вот еще один пример с одним переданным универсальным типом ...

[Test]
[TestCase<MyClass>("Some response")]
public void MyMethod_GenericCall_MakesGenericCall<T>(string expectedResponse)
{
    // Arrange

    // Act
    var response = MyClassUnderTest.MyMethod<T>();

    // Assert
    Assert.AreEqual(expectedResponse, response);
}

Ответы [ 8 ]

43 голосов
/ 11 апреля 2017

Методы тестирования NUnit на самом деле могут быть общими, если аргументы универсального типа могут быть выведены из параметров:

[TestCase(42)]
[TestCase("string")]
[TestCase(double.Epsilon)]
public void GenericTest<T>(T instance)
{
    Console.WriteLine(instance);
}

NUnit Generic Test

Если обобщенные аргументы не могут быть выведены, исполнитель теста не будет иметь понятия, как разрешить аргументы типа:

[TestCase(42)]
[TestCase("string")]
[TestCase(double.Epsilon)]
public void GenericTest<T>(object instance)
{
    Console.WriteLine(instance);
}

NUnit Generic Test Fail

Но в этом случае вы можете реализовать пользовательский атрибут:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestCaseGenericAttribute : TestCaseAttribute, ITestBuilder
{
    public TestCaseGenericAttribute(params object[] arguments)
        : base(arguments)
    {
    }

    public Type[] TypeArguments { get; set; }

    IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
    {
        if (!method.IsGenericMethodDefinition)
            return base.BuildFrom(method, suite);

        if (TypeArguments == null || TypeArguments.Length != method.GetGenericArguments().Length)
        {
            var parms = new TestCaseParameters { RunState = RunState.NotRunnable };
            parms.Properties.Set("_SKIPREASON", $"{nameof(TypeArguments)} should have {method.GetGenericArguments().Length} elements");
            return new[] { new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parms) };
        }

        var genMethod = method.MakeGenericMethod(TypeArguments);
        return base.BuildFrom(genMethod, suite);
    }
}

Использование:

[TestCaseGeneric("Some response", TypeArguments = new[] { typeof(IMyInterface), typeof(MyConcreteClass) }]
public void MyMethod_GenericCall_MakesGenericCall<T1, T2>(string expectedResponse)
{
    // whatever
}

и аналогичная настройка для TestCaseSourceAttribute:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestCaseSourceGenericAttribute : TestCaseSourceAttribute, ITestBuilder
{
    public TestCaseSourceGenericAttribute(string sourceName)
        : base(sourceName)
    {
    }

    public Type[] TypeArguments { get; set; }

    IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
    {
        if (!method.IsGenericMethodDefinition)
            return base.BuildFrom(method, suite);

        if (TypeArguments == null || TypeArguments.Length != method.GetGenericArguments().Length)
        {
            var parms = new TestCaseParameters { RunState = RunState.NotRunnable };
            parms.Properties.Set("_SKIPREASON", $"{nameof(TypeArguments)} should have {method.GetGenericArguments().Length} elements");
            return new[] { new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parms) };
        }

        var genMethod = method.MakeGenericMethod(TypeArguments);
        return base.BuildFrom(genMethod, suite);
    }
}

Использование:

[TestCaseSourceGeneric(nameof(mySource)), TypeArguments = new[] { typeof(IMyInterface), typeof(MyConcreteClass) }]
19 голосов
/ 01 июня 2013

Мне довелось сделать что-то подобное сегодня, и я не был доволен использованием рефлексии.

Я решил вместо этого использовать [TestCaseSource], делегировав логику теста в качестве контекста теста универсальному классу тестирования,закрепленный на неуниверсальном интерфейсе и вызванный интерфейсом из отдельных тестов (мои реальные тесты имеют намного больше методов в интерфейсе и используют AutoFixture для настройки контекста):

class Sut<T>
{
    public string ReverseName()
    {
        return new string(typeof(T).Name.Reverse().ToArray());
    }
}

[TestFixture]
class TestingGenerics
{
    public IEnumerable<ITester> TestCases()
    {
        yield return new Tester<string> { Expectation = "gnirtS"};
        yield return new Tester<int> { Expectation = "23tnI" };
        yield return new Tester<List<string>> { Expectation = "1`tsiL" };
    }

    [TestCaseSource("TestCases")]
    public void TestReverse(ITester tester)
    {
        tester.TestReverse();
    }

    public interface ITester
    {
        void TestReverse();
    }

    public class Tester<T> : ITester
    {
        private Sut<T> _sut;

        public string Expectation { get; set; }

        public Tester()
        {
            _sut=new Sut<T>();
        }

        public void TestReverse()
        {
            Assert.AreEqual(Expectation,_sut.ReverseName());
        }

    }
}
6 голосов
/ 02 марта 2010

Атрибуты в C # не могут быть общими, поэтому вы не сможете делать все так, как вам хочется. Возможно, проще всего было бы поместить атрибуты TestCase в вспомогательный метод, который использует отражение для вызова реального метода. Нечто подобное может работать (обратите внимание, не проверено):

    [TestCase(typeof(MyClass), "SomeResponse")]
    public void TestWrapper(Type t, string s)
    {
        typeof(MyClassUnderTest).GetMethod("MyMethod_GenericCall_MakesGenericCall").MakeGenericMethod(t).Invoke(null, new [] { s });
    }
5 голосов
/ 15 ноября 2016

Вы можете сделать собственный GenericTestCaseAttribute

[Test]
[GenericTestCase(typeof(MyClass) ,"Some response", TestName = "Test1")]
[GenericTestCase(typeof(MyClass1) ,"Some response", TestName = "Test2")]
public void MapWithInitTest<T>(string expectedResponse)
{
    // Arrange

    // Act
    var response = MyClassUnderTest.MyMethod<T>();

    // Assert
    Assert.AreEqual(expectedResponse, response);
}

Вот реализация GenericTestCaseAttribute

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class GenericTestCaseAttribute : TestCaseAttribute, ITestBuilder
{
    private readonly Type _type;
    public GenericTestCaseAttribute(Type type, params object[] arguments) : base(arguments)
    {
        _type = type;
    }

    IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
    {
        if (method.IsGenericMethodDefinition && _type != null)
        {
            var gm = method.MakeGenericMethod(_type);
            return BuildFrom(gm, suite);
        }
        return BuildFrom(method, suite);
    }
}
5 голосов
/ 02 марта 2010

Сначала начните с теста - даже при тестировании. Что ты хочешь делать? Вероятно, что-то вроде этого:

[Test]
public void Test_GenericCalls()
{
    MyMethod_GenericCall_MakesGenericCall<int>("an int response");
    MyMethod_GenericCall_MakesGenericCall<string>("a string response");
      :
}

Тогда вы можете просто сделать свой тест простым старым функциональным тестом. Нет маркера [Test].

public void MyMethod_GenericCall_MakesGenericCall<T>(string expectedResponse)
{
    // Arrange

    // Act
    var response = MyClassUnderTest.MyMethod<T>();

    // Assert
    Assert.AreEqual(expectedResponse, response);
}
4 голосов
/ 02 марта 2010

Я делал нечто подобное на прошлой неделе. Вот что я закончил:

internal interface ITestRunner
{
    void RunTest(object _param, object _expectedValue);
}

internal class TestRunner<T> : ITestRunner
{
    public void RunTest(object _param, T _expectedValue)
    {
        T result = MakeGenericCall<T>();

        Assert.AreEqual(_expectedValue, result);
    }
    public void RunTest(object _param, object _expectedValue)
    {
        RunTest(_param, (T)_expectedValue);
    }
}

А потом сам тест:

[Test]
[TestCase(typeof(int), "my param", 20)]
[TestCase(typeof(double), "my param", 123.456789)]
public void TestParse(Type _type, object _param, object _expectedValue)
{
    Type runnerType = typeof(TestRunner<>);
    var runner = Activator.CreateInstance(runnerType.MakeGenericType(_type));
    ((ITestRunner)runner).RunTest(_param, _expectedValue);
}
2 голосов
/ 06 декабря 2010

Как может быть тестирование с универсальными функциями, которые возвращают объекты? Пример:

public Empleado TestObjetoEmpleado(Empleado objEmpleado) 
{
    return objEmpleado; 
}

Спасибо

1 голос
/ 28 сентября 2018

Я немного изменил атрибут TestCaseGenericAttribute, который кто-то разместил здесь:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class GenericTestCaseAttribute : TestCaseAttribute, ITestBuilder
{
    public GenericTestCaseAttribute(params object[] arguments)
        : base(arguments)
    {
    }

    IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
    {
        if (!method.IsGenericMethodDefinition) return base.BuildFrom(method, suite);
        var numberOfGenericArguments = method.GetGenericArguments().Length;
        var typeArguments = Arguments.Take(numberOfGenericArguments).OfType<Type>().ToArray();

        if (typeArguments.Length != numberOfGenericArguments)
        {
            var parms = new TestCaseParameters { RunState = RunState.NotRunnable };
            parms.Properties.Set("_SKIPREASON", $"Arguments should have {typeArguments} type elements");
            return new[] { new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parms) };
        }

        var genMethod = method.MakeGenericMethod(typeArguments);
        return new TestCaseAttribute(Arguments.Skip(numberOfGenericArguments).ToArray()).BuildFrom(genMethod, suite);
    }
}

Эта версия ожидает один список всех параметров, начиная с параметров типа, начиная с параметров типа. Использование:

    [Test]
    [GenericTestCase(typeof(IMailService), typeof(MailService))]
    [GenericTestCase(typeof(ILogger), typeof(Logger))]
    public void ValidateResolution<TQuery>(Type type)
    {
        // arrange
        var sut = new AutoFacMapper();

        // act
        sut.RegisterMappings();
        var container = sut.Build();

        // assert
        var item = sut.Container.Resolve<TQuery>();
        Assert.AreEqual(type, item.GetType());
    }
...