在存储库接口的各种实现上使用相同的测试套件

时间:2020-03-05 18:58:21  来源:igfitidea点击:

我一直在Robong Connery的Asp.net MVC店面中制作一个小的玩具Web应用程序。

我发现我有一个存储库接口,将其称为IFooRepository,带有方法,例如

IQueryable<Foo> GetFoo();
void PersistFoo(Foo foo);

我对此有三个实现:ISqlFooRepository,IFileFooRepostory和IMockFooRepository。

我也有一些测试用例。我想做的但尚未弄清楚该怎么做的方法是,针对这三个实现中的每个实现运行相同的测试用例,并对每种接口类型上的每个测试通过都打上绿色勾号。

例如

[TestMethod]
Public void GetFoo_NotNull_Test()
{
   IFooRepository repository = GetRepository();
   var results = repository. GetFoo();
   Assert.IsNotNull(results);
}

我希望该测试方法可以运行3次,并且环境有所不同,因此可以获取三种不同的存储库。目前,我有三个剪切粘贴的测试类,它们仅在私有帮助器方法IFooRepository GetRepository()的实现上有所不同。显然,这很臭。

但是,我不能仅仅通过合并剪切和粘贴的方法来删除重复项,因为它们必须存在,公开并标记为测试才能运行。

我正在使用Microsoft测试框架,如果可以的话,我希望继续使用它。但是,关于如何在MBUnit中执行此操作的建议也将引起一定的兴趣。

解决方案

回答

在MbUnit中,我们也许可以使用RowTest属性在测试中指定参数。

[RowTest]
[Row(new ThisRepository())]
[Row(new ThatRepository())]
Public void GetFoo_NotNull_Test(IFooRepository repository)
{
   var results = repository.GetFoo();
   Assert.IsNotNull(results);
}

回答

如果我们有3个复制和粘贴的测试方法,则应该能够重构(提取方法)以消除重复。

即这就是我的想法:

private IRepository GetRepository(RepositoryType repositoryType)
{
    switch (repositoryType)
    {   
          case RepositoryType.Sql:
          // return a SQL repository
          case RepositoryType.Mock:
          // return a mock repository
          // etc
    }
}

private void TestGetFooNotNull(RepositoryType repositoryType)
{
   IFooRepository repository = GetRepository(repositoryType);
   var results = repository.GetFoo();
   Assert.IsNotNull(results);
}

[TestMethod]
public void GetFoo_NotNull_Sql()
{
   this.TestGetFooNotNull(RepositoryType.Sql);
}

[TestMethod]
public void GetFoo_NotNull_File()
{
   this.TestGetFooNotNull(RepositoryType.File);
}

[TestMethod]
public void GetFoo_NotNull_Mock()
{
   this.TestGetFooNotNull(RepositoryType.Mock);
}

回答

创建一个包含测试的具体版本的抽象类和一个返回IFooRepository的抽象GetRepository方法。
创建从抽象类派生的三个类,每个类以返回适当的IFooRepository实现的方式实现GetRepository。
将所有三个类添加到测试套件中,就可以开始了。

为了能够有选择地为某些提供程序而不是其他提供程序运行测试,请考虑使用MbUnit的FixtureCategory属性对测试进行分类,建议类别为"快速","慢速"," db","重要"和"不重要"(最后两个是诚实的笑话!)

回答

[TestMethod]
public void GetFoo_NotNull_Test_ForFile()
{   
   GetFoo_NotNull(new FileRepository().GetRepository());
}

[TestMethod]
public void GetFoo_NotNull_Test_ForSql()
{   
   GetFoo_NotNull(new SqlRepository().GetRepository());
}

private void GetFoo_NotNull(IFooRepository repository)
{
  var results = repository. GetFoo();   
  Assert.IsNotNull(results);
}

回答

总结起来,有三种方法可供选择:

1)使测试成为一种可以调用通用方法的衬管(Rick的回答,也是Hallgrim)

2)使用MBUnit的RowTest功能使其自动化(由Jon Limjap回答)。我也会在这里使用一个枚举,例如

[RowTest]
[Row(RepositoryType.Sql)]
[Row(RepositoryType.Mock)]
public void TestGetFooNotNull(RepositoryType repositoryType)
{
   IFooRepository repository = GetRepository(repositoryType);
   var results = repository.GetFoo();
   Assert.IsNotNull(results);
}

3)使用基类,通过belugabob回答
我根据这个想法做了一个样本

public abstract class TestBase
{
    protected int foo = 0;

    [TestMethod]
    public void TestUnderTen()
    {
        Assert.IsTrue(foo < 10);
    }

    [TestMethod]
    public void TestOver2()
    {
        Assert.IsTrue(foo > 2);
    }
}

[TestClass]
public class TestA: TestBase
{
    public TestA()
    {
        foo = 4;
    }
}

[TestClass]
public class TestB: TestBase
{
    public TestB()
    {
        foo = 6;
    }
}

这将在两个测试类别中产生四个通过测试。
3的优点是:
1)最少的额外代码,最少的维护
2)如果需要的话,最少键入插入新存储库的操作将在一个地方完成,这与其他地方不同。

缺点是:
1)如果需要的话,不对提供程序进行测试的灵活性较小
2)更难阅读。