泛型类型的单元测试

时间:2019-01-05 01:24:46

标签: c# unit-testing generics testing nunit

我想使用泛型来测试单元测试,而我正在努力寻找正确的方法。

我有这个

[TestCase(typeof(CalendarGeneralCsv), typeof(CalendarGeneralCsvMap), 121)]
public void ReadFromCsvFileWithConfigurationMapTest<T,Tmap>(T t, Tmap tmap, int totalRowsExptected)
{
   //Arrange

   //Act
    var records = csvService.ReadFileCsv<T, Tmap>(_csvToRead, ",") as IEnumerable<object>;

     var result = new List<object>(records);

     //Assert
     result.Should().NotBeNullOrEmpty();
     result.Should().HaveCount(totalRowsExptected);
}

此行中有错误

  var records = csvService.ReadFileCsv<T, Tmap>(_csvToRead, ",") as IEnumerable<object>;

说T和Tmap必须是引用类型。

4 个答案:

答案 0 :(得分:2)

或者,如果您不打算在测试中使用具有不同类型的多个 =IF(A2>0,Sheet2!$F$9,"") 属性,则无需为测试提供任何通用参数。您可以将类型明确传递给类型参数:

TestCase

但是,如果您确实想对多个类型/值运行相同的测试用例,则可以将实际的测试逻辑提取到通用方法中。然后,您可以为要测试的每组数据创建一个新测试,将类型显式传递给通用方法:

public void ReadFromCsvFileWithConfigurationMapTest()
{
    //Arrange

    //Act
    var records = csvService.ReadFileCsv<CalendarGeneralCsv, CalendarGeneralCsvMap>(_csvToRead, ",") as IEnumerable<object>;

    var result = new List<object>(records);

    //Assert
    result.Should().NotBeNullOrEmpty();
    result.Should().HaveCount(121);
}

答案 1 :(得分:2)

尽管已经有了一个可接受的答案(注意:自从我最初发布以来,它已经发生了变化),但我想提供一种使用反射的替代方法。将测试方法分为两种方法:跳板方法通用测试方法。有几个优点:

  • 通用测试方法看起来更像其他任何测试方法。它没有混入无关的反射。

  • 通用测试方法可以正常执行,这也是因为它没有混入无关的反射。

  • 对被测组件的更改更有可能在测试项目中触发编译器错误,因此您知道需要更新通用测试方法以及可能的跳板方法。另外,由于在何处引发了异常,因此在运行时更清楚地知道,这是由于支持反射而不是组件的使用方式。

  • springboard方法不需要知道有关被测组件的任何信息,只需知道如何调用通用测试方法即可。

  • 由于变化很小,因此可以轻松,一致地复制图案。

以下是基于问题和已接受答案的示例:

[TestCase(typeof(CalendarGeneralCsv), typeof(CalendarGeneralCsvMap), 121)]
[TestCase(typeof(CalendarCustomCsv), typeof(CalendarCustomCsvMap), 80)]
public void ReadFromCsvFileWithConfigurationMapTest(Type t, Type tmap, int totalRowsExpected)
{
    GetType().GetMethod(nameof(GenericReadFromCsvFileWithConfigurationMapTest))
        .MakeGenericMethod(t, tmap)                         // <-- Type parameters go here
        .Invoke(this, new object[] { totalRowsExpected });  // <-- inputs go here
}

public void GenericReadFromCsvFileWithConfigurationMapTest<T, Tmap>(int totalRowsExpected)
    where T : class
    where Tmap : class
{
    // Arrange

    // Act
    var records = csvService.ReadFileCsv<T, Tmap>(_csvToRead, ",") as IEnumerable<object>;

    // Assert
    records.Should().NotBeNull();

    var result = new List<object>(records);

    result.Should().NotBeNullOrEmpty();
    result.Should().HaveCount(totalRowsExptected);
}

景点

它使用GetType()是因为它正在寻找相同类型(测试类)的方法。这样可以减少变化,从而可以更轻松地复制图案。

通用测试方法具有不同的名称(只要是不同的名称都无所谓),因此GetMethod调用不需要指定参数类型。该名称应只有一种方法,并且该方法是公共的,因此也不需要BindingFlags。另外,您可以将其设为私有,只需添加BindingFlags.NonPublic | BindingFlags.Instance注意:并非所有框架版本都带有BindingFlags重载。如果要将其设为私有,则必须找到其他选择。

通用测试方法需要包括约束。这使约束成为测试的正式组成部分。如果不满足约束,则反射将在运行时以任何一种方式失败,但是如果将约束放在通用测试方法上,则很可能从一开始就编写更好的测试。您提到TTmap必须是引用类型,因此它们都包含在上面。

最后,正如您已经指出的那样,您的跳板能够定义多个测试用例,因此我在上面包括了另一个日历和映射。

答案 2 :(得分:2)

我通常不会在已经有几个答案并且一个被接受的地方回答,但是它们似乎都是基于测试方法不能通用的假设。他们绝对可以。我的记忆告诉我,这曾经是有据可查的,但是现在似乎不再存在了-或者我的记忆不对-这说明了为什么您可能认为不可能。

通用解决方案在这里可能不是最佳解决方案,但尝试似乎很有趣,可能会更好,或者阐明为什么接受的解决方案更好。目前为止,我只能提供已经提供的信息,但是如果jolynice可以合作,也许我们可以学到一些东西。 :-)

所以...这是解决方案的初步照片,如果有更多信息返回,我将对其进行编辑。

该问题的原始解决方案导致错误,因为未满足通用方法ReadFileCsv<T, Tmap>(...)中的约束。我们不知道它们是什么,但是由于错误,它们包括T : classTmap : class。因此,正确答案的第一步是在测试方法本身上重现被调用方法的所有约束。

[TestCase(typeof(CalendarGeneralCsv), typeof(CalendarGeneralCsvMap), 121)]
public void ReadFromCsvFileWithConfigurationMapTest<T,Tmap>(int totalRowsExptected)
    where T : class
    where Tmap : class
{
   //Arrange

   //Act
    var records = csvService.ReadFileCsv<T, Tmap>(_csvToRead, ",") as IEnumerable<object>;

     var result = new List<object>(records);

     //Assert
     result.Should().NotBeNullOrEmpty();
     result.Should().HaveCount(totalRowsExptected);
}

答案 3 :(得分:1)

考虑使用反射来调用被测通用主题

[TestCase(typeof(CalendarGeneralCsv), typeof(CalendarGeneralCsvMap), 121)]
public void ReadFromCsvFileWithConfigurationMapTest(Type t, Type tmap, int totalRowsExptected) {
    //Arrange

    //...

    var serviceType = csvService.GetType();
    var method = serviceType.GetMethod("ReadFileCsv");
    var genericMethod = method.MakeGenericMethod(t, tmap);

    var parameters = new object[] { _csvToRead, "," };

    //Act
    var records = genericMethod.Invoke(csvService, parameters) as IEnumerable<object>;
    //Above same as csvService.ReadFileCsv<T, Tmap>(_csvToRead, ",") as IEnumerable<object>;

    //Assert
    records.Should().NotBeNull();

    var result = new List<object>(records);

    result.Should().NotBeNullOrEmpty();
    result.Should().HaveCount(totalRowsExptected);
}

使用csvService,通过GetType()获取类型

var serviceType = csvService.GetType();

以访问其会员信息。

找到要通过名称调用的成员

var method = serviceType.GetMethod("ReadFileCsv");

,并将提供的类型参数用作通用参数

var genericMethod = method.MakeGenericMethod(t, tmap);

可以使用传递的参数在服务实例上调用通用成员。

 var records = genericMethod.Invoke(csvService, new object[] { _csvToRead, "," }) as IEnumerable<object>;