在使用Repository模式时,我发现很难理解使用TDD技术设计软件的原因,而实际上您必须在持久性数据集中为您的存储库实现接口。
为了明确我的观点,我将提交一个例子:
我的域模型上有以下界面:
public interface IUserRepository
{
IQueryable<User> FindAllUsers();
void AddUser(User newUser);
User GetUserByID(int userID);
void Update(User userToUpdate);
}
我有以下用于测试目的的接口实现:
public class FakeUserRepository : IUserRepository
{
private IList<User> _repository;
public FakeUserRepository()
{
_repository = new List<User>();
... //create all users for testing purposes
}
public IQueryable<User> FindAllUsers()
{
return _repository.AsQueryable<User>(); //returns all users
}
现在我创建了一些测试:
我的问题是,在用FakeUserRepository实现测试所有这些后,我必须返回并在我的实际持久性数据集(ig SQL)上实现IUserRepository,我必须再次实现代码,所以我的单元测试是实际上没有检查我在我的应用程序中实际使用的代码。
也许我错过了什么。
一如既往地谢谢!
下面是我的Persistent数据访问存储库,它应该是一个应该被测试的存储库(至少在我看来),但是我不应该测试钩住数据库:
public class SQLUserRepository : IUserRepository
{
private BusinessDomainModel.EntityModel.BusinessHelperAccountDBEntities _repository;
public SQLUserRepository()
{
_repository = new BusinessHelperAccountDBEntities();
}
#region IUserRepository Members
public IQueryable<User> FindAllUsers()
{
return _repository.UserSet.AsQueryable();
}
答案 0 :(得分:12)
永远不要测试模拟。被测试的类应该始终是该类的真实实例,尽管您可以并且应该模拟其任何依赖项,以便您可以单独测试它。
答案 1 :(得分:5)
我会解释我正在做什么,为什么以及我有多大的意义。
首先,我正在做完全您正在做的事情,关于您的存储库。尽管存在一些名称空间差异,但我也是这样做的:
使用 fake UserRepository,我还只创建并填充private IEnumerable<User>
集合(List<User>
)。为什么我这个?我使用此存储库进行初始日常开发(因为它很快 - &gt;没有数据库访问==快!)。然后我交换假的respitories为sql存储库(即chage我的依赖注入(oooohhh!))。这就是为什么这个类/命名空间存在,而不是在我的单元测试中使用Mocks来实现'假'的东西。 (这种情况会发生,但在不同的情况下)。
使用我的sql server UserRepository,我使用LinqToSql。关于你的问题,我使用LinqToSql是不可能的...它可能是任何其他数据库包装器。这里重要的是有一个第三方某些东西我整合。
好的,所以从这里开始,我需要确定两件事
首先,大多数人都没有为假货创建单元测试。这是一块假的粪便,为什么要浪费能量呢?没错 - 除了我在我的日常开发中使用那块假粪便(请参阅我上面提到的关于此的瑕疵)。所以我很快就开始进行一些基本的单元测试。注意:在 my eyes 中,这些是单元测试,即使它们是repository
类。为什么?他们不是与第三方/基础设施进行整合。
接下来(最后我说到了),我做了一个单独的测试课,这是一个综合测试。这是一个单元测试,可以与系统外部的东西集成。它可能是真正的Twitter api。它可能是真正的S3 Amazon api。请注意,我使用了 real 这个词。这是关键所在。我正在与我的外面的真实服务融为一体。因此 - &gt;它很慢。任何时候我需要离开我的计算机获取一些数据,它被称为 intergrating ,你自动假设(并期望)它很慢。
所以在这里,我正在与数据库整合。
(Nae说道,请不要因为你在同一台计算机上拥有数据库的厚颜无耻的建议而拖拽这个......你要离开你的应用'世界'。
哇。这是一些War-n-Peace小说..时间为一些硬性动作,公鸡slappin代码。 让我们来吧!
namespace MyProject.Tests.Repositories.SqlServer
{
// ReSharper disable InconsistentNaming
[TestClass]
public class UserRepositoryTests : TestBase
{
[ClassInitialize]
public static void ClassInitialize(TestContext testContext)
{
// Arrange.
// NOTE: this method is inherited from the TestBase abstract class.
// Eg. protected IUserRepository =
// new MyProject.Respositories.SqlServer
// .UserRespository(connectionString);
InitializeSqlServerTestData();
}
[TestMethod]
public void GetFirst20UsersSuccess()
{
// Act.
var users = _users.GetUsers()
.Take(20)
.ToList();
// Assert.
Assert.IsNotNull(users);
Assert.IsTrue(users.Count() > 0);
}
}
}
好的,让我们来看看这只小狗。
首先,这是使用Microsoft单元测试 - 内置到VS2010 Beta2或VS2008的Team Foundation版本(或者任何版本的......我只是安装我们的工作已经购买的副本)。
其次,无论何时首次初始化类(无论是一次测试还是多次),都会创建context
。就我而言,我的Sql Server UserRepository将使用LinqToSql上下文。 (你的将是一个EF背景)。这是TDD的 Arrange 部分。
第三,我称之为方法 - &gt;这是TDD的 Act 部分。
最后,我检查一下我的预期 - &gt;这是TDD的 Assert 部分。
如何更新数据库?
只需遵循相同的模式,除非您可能希望将您的调用代码包装在事务中并将其回滚。否则你可能得到100行的数据可能是相同的。对此有何不妥?任何identity
字段都不会具有所有漂亮且漂亮的编号序列(因为回滚将“使用”该数字)。不明白吗?别担心这是一个高级提示,我以为我会投入测试你,但这意味着蹲下这个非常长篇大论的帖子。
所以......呃..是的。我就是做这个的。不知道在这些论坛上,编程之神是否会翻转并且让我的方式变得泥泞但是我有点喜欢它,我希望它可以帮助你。
HTH。
答案 2 :(得分:3)
你应该测试真正的类,而不是你为测试做的假的。使用接口的关键在于它允许您模拟该类,因此您可以在与其他协作者的测试中使用模拟版本。
为了测试该类,您应该能够传入一个模拟数据库,并断言当您调用Repository类上的方法时,实际上会发生您希望进入数据库的调用。
这是C#中模拟和测试的一个很好的介绍:
http://refact.blogspot.com/2007/01/mock-objects-and-rhino.html