在存储库接口的各种实现上使用相同的测试套件
-
09-06-2019 - |
题
我一直在沿着 Rob Connery 的 Asp.net MVC 店面的路线用 C# 制作一个小玩具 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);
}
我希望这个测试方法运行三次,环境中存在一些变化,允许它获得三种不同类型的存储库。目前,我有三个剪切粘贴的测试类,它们的不同之处仅在于私有帮助器方法 IFooRepository GetRepository(); 的实现;显然,这很臭。
但是,我不能仅仅通过合并剪切和粘贴方法来删除重复,因为它们需要存在、公开并标记为测试才能运行。
我正在使用 Microsoft 测试框架,如果可以的话我更愿意继续使用它。但是,关于如何在 MBUnit 中执行此操作的建议也会引起一些兴趣。
解决方案
创建一个包含测试的具体版本的抽象类和一个返回 IFooRepository 的抽象 GetRepository 方法。创建从抽象类派生的三个类,每个类都以返回适当的 IFooRepository 实现的方式实现 GetRepository。将所有三个类添加到您的测试套件中,然后您就可以开始了。
为了能够有选择地为某些提供程序而不是其他提供程序运行测试,请考虑使用 MbUnit '[FixtureCategory]' 属性对您的测试进行分类 - 建议的类别为 '快速' '慢速' 'db' '重要' 和 '不重要' (最后两个是笑话 - 诚实!)
其他提示
在 MbUnit 中,您也许能够使用 RowTest 属性来指定测试的参数。
[RowTest]
[Row(new ThisRepository())]
[Row(new ThatRepository())]
Public void GetFoo_NotNull_Test(IFooRepository repository)
{
var results = repository.GetFoo();
Assert.IsNotNull(results);
}
如果您有 3 个复制和粘贴的测试方法,您应该能够重构(提取方法)它以消除重复。
IE。这就是我的想法:
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);
}
[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)更难阅读。