在坚持DRY的同时,我应该如何为接口的多个实现编写单元测试



我有多个实现同一接口的类,我应该如何编写单元测试来验证每个类是否正确实现了接口,并将代码重复降至最低(DRY)?

作为我的意思的一个例子,下面是一个非常基本的库,包含IDeleter的两个实现:Deleter1Deleter2。两者都通过在其关联的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; }
}
}

对于这两个类Deleter1Deleter2,我编写了两个相应的单元测试类,如下面的代码片段所示。测试检查相同的行为,即在底层存储库上调用Delete。对于IDeleter的所有实现,是否有更好的方法来实现相同的测试?例如,我应该为TestDeleter1TestDeleter2编写一个包含常见测试方法(如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接口,这样所有子类都从基类继承相同的实现。如果存在需要不同实现的边缘情况,则该类可以覆盖基类的实现。

最新更新