在存储库接口的各种实现上使用相同的测试套件
我一直在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)更难阅读。