2010-03-02 5 views
28

Есть ли способ передать общие типы, используя TestCase для тестирования в NUnit?NUnit TestCase с generics

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

[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); 
} 
+0

Не могли бы вы уточнить, что вы хотите проверить? Из примера выше, похоже, вы пишете модульные тесты для инфраструктуры .NET, а не своего кода. –

+0

Извините, я старался сделать пример максимально простым, и, возможно, я слишком далеко зашел. Я пишу тесты для проверки того, что определенные классы были зарегистрированы против определенных интерфейсов в контейнере IoC. Я понимаю, что это толкает границы того, что следует проверять с логической точки зрения. Есть много других экземпляров, хотя я хотел бы протестировать передачу в разных типах общего метода. –

+2

NUnit TestCase можно, конечно, сокращать как NUTCase. –

ответ

17

я имел возможность сделать что-то подобное сегодня, и не был счастлив с помощью отражения.

Я решил использовать [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()); 
     } 

    } 
} 
4

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

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

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

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

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

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

Хотя он не использует TestCases, это представляется наиболее подходящим способом структурирования этого кода с точки зрения читаемости. Тогда недостатком является то, что один неудачный экземпляр приведет к сбою всего теста и, следовательно, не будет указывать на ошибку. –

+0

Чтобы точно определить ошибку, вы всегда можете вставить описание как другой параметр в метод MyMethod_GenericCall_MakesGenericCall и использовать его в методе Assert.AreEqual. –

6

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

+0

Почему я не подумал об этом? Очень умный @kvb. Благодаря! –

4

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

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); 
} 
+0

Да, в аналогичном ключе с ответом kvb это как можно ближе к атрибуту TestCase, а затем с помощью отражения для запуска теста. Как я сказал kvb, хотя, это немного наклоняет, чтобы понять, что тестируется. –

+0

@ Russell: Определенно. В моем фактическом коде я немного смягчил проблему удобочитаемости, используя четкие имена для используемых классов и методов. –

2

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

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

Благодаря

4

Вы можете сделать пользовательский 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); 
    } 
} 
16

методы испытаний 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) }] 
Смежные вопросы