我有多个实现同一接口的类,我应该如何编写单元测试来验证每个类是否正确实现了接口,并将代码重复降至最低(DRY)?
作为我的意思的一个例子,下面是一个非常基本的库,包含IDeleter
的两个实现:Deleter1
和Deleter2
。两者都通过在其关联的IRepository
上调用Delete
来实现方法Delete
。
using Microsoft.Practices.Unity;
namespace TestMultiple
{
public interface IRepository
{
void Delete(string id);
}
public abstract class Baseclass
{
protected abstract IRepository GenericRepository { get; }
public void Delete(string id)
{
GenericRepository.Delete(id);
}
}
public interface IDeleter
{
void Delete(string id);
}
public interface IRepository1 : IRepository
{
}
public abstract class RepositoryBase
{
public void Delete(string id)
{
}
}
public class Repository1 : RepositoryBase, IRepository1
{
}
public class Deleter1 : Baseclass, IDeleter
{
protected override IRepository GenericRepository { get { return Repository; } }
[Dependency]
public IRepository1 Repository { get; set; }
}
public interface IRepository2 : IRepository
{
}
public class Repository2 : RepositoryBase, IRepository2
{
}
public class Deleter2 : Baseclass, IDeleter
{
protected override IRepository GenericRepository { get { return Repository; } }
[Dependency]
public IRepository2 Repository { get; set; }
}
}
对于这两个类Deleter1
和Deleter2
,我编写了两个相应的单元测试类,如下面的代码片段所示。测试检查相同的行为,即在底层存储库上调用Delete
。对于IDeleter
的所有实现,是否有更好的方法来实现相同的测试?例如,我应该为TestDeleter1
和TestDeleter2
编写一个包含常见测试方法(如TestDelete
)的基类吗?
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Practices.Unity;
using Moq;
namespace TestMultiple.Tests
{
[TestClass]
public class TestDeleter1
{
[TestMethod]
public void TestDelete()
{
var mockRepo = new Mock<IRepository1>();
var container = new UnityContainer().RegisterInstance<IRepository1>(mockRepo.Object);
var deleter = container.Resolve<Deleter1>();
deleter.Delete("id");
mockRepo.Verify(r => r.Delete("id"));
}
}
[TestClass]
public class TestDeleter2
{
[TestMethod]
public void TestDelete()
{
var mockRepo = new Mock<IRepository2>();
var container = new UnityContainer().RegisterInstance<IRepository2>(mockRepo.Object);
var deleter = container.Resolve<Deleter2>();
deleter.Delete("id");
mockRepo.Verify(r => r.Delete("id"));
}
}
}
EDIT:请随意提及可能有助于解决此类问题的单元测试框架,尽管我更喜欢NUnit。
在我所知道的断言接口上常见行为的框架中,没有简单的方法来编写测试。你能做的最好的事情就是编写测试和辅助方法,就像你在测试一个抽象类一样,然后将真实类型插入到派生的测试类中。
例如,您可以创建一个DeleterTests
类,它为接口提供测试:
public abstract class DeleterTests<TRepository> where TRepository : IRepository
{
[TestMethod]
public void TestDelete()
{
var mockRepo = new Mock<TRepository>();
var container = new UnityContainer();
container.RegisterInstance<TRepository>(mockRepo.Object);
var deleter = this.CreateDeleter(container);
deleter.Delete("id");
mockRepo.Verify(r => r.Delete("id"));
}
protected abstract IDeleter CreateDeleter(IUnityContainer container);
}
然后,从这个类继承任何实现IDeleter
的东西,根据需要实现抽象的CreateDeleter
方法:
public class Deleter1Tests : DeleterTests<IRepository1>
{
protected override IDeleter CreateDeleter(IUnityContainer container)
{
return container.Resolve<Deleter1>();
}
}
public class Deleter2Tests: DeleterTests<IRepository2>
{
protected override IDeleter CreateDeleter(IUnityContainer container)
{
return container.Resolve<Deleter2>();
}
}
如果您需要以不同的方式组合实例,您可以以任何方式实现抽象CreateDeleter
。
您应该为每个类编写单元测试,而不必真正担心其他实现。如果你觉得你一次又一次地写同样的测试,那很可能是因为你的生产代码是不干燥的——而不是你的测试代码。正如其他人所指出的那样;如果不同的实现在comon中有很多,那么一些共同的抽象祖先可能是个好主意。
如果所有的类都应该以相同的方式实现IDeleter接口,那么基类就不应该是抽象的。在基类中实现IDeleter接口,这样所有子类都从基类继承相同的实现。如果存在需要不同实现的边缘情况,则该类可以覆盖基类的实现。