如何使用 ADO.NET 实体框架测试存储库模式?

发布于 2024-08-06 19:27:09 字数 1540 浏览 4 评论 0原文

在使用存储库模式时,我发现很难理解使用 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
    }

现在我创建一些测试:

  1. Can_Add_User
  2. Can_Add_Account 用于用户
  3. Can_Add_ShareSpace 用于帐户一个用户与另一个用户的关系。

我的问题是,在我用 FakeUserRepository 实现测试所有这些之后,我必须返回并在我的实际持久性数据集(ig SQL)上实现 IUserRepository,并且我必须再次实现代码,所以我的单元测试实际上并没有检查我在我的应用程序中实际使用的代码。

也许我错过了一些东西。

一如既往的感谢!

下面是我的持久数据访问存储库,它应该是正在测试的存储库(至少在我看来),但是我不应该测试连接到数据库的存储库:

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();
    }

While using Repository pattern I find it difficult to understand the reason of designing the software with TDD technique while in reality you will have to implement the Interface for your repository in your persistence dataset.

To make my point clear I will submit an example:

I have the following Interface on my domain model:

public interface IUserRepository
{
    IQueryable<User> FindAllUsers();
    void AddUser(User newUser);
    User GetUserByID(int userID);
    void Update(User userToUpdate);
}

I have the following implementation of the interface for testing purposes:

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
    }

Now I create a few tests:

  1. Can_Add_User
  2. Can_Add_Account for a User
  3. Can_Add_ShareSpace for a an account of a user with another user.

My question is, after I test all these with my FakeUserRepository implementation, I have to go back and implement the IUserRepository on my actual persistence dataset (i.g. SQL), and I have to implement again the code, so my unit testing is not actually checking the code that I am actually using on my application.

Maybe I am missing something.

Thanks as always!

Below then my Persistent data access repository which is the one that is supposed to be under test (by my mind at least), but then I should not test hooked to the database:

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();
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

孤独难免 2024-08-13 19:27:09

永远不要测试模拟。被测试的类应该始终是该类的真实实例,尽管您可以并且应该模拟它的任何依赖项,以便可以单独测试它。

Never test a mock. The class under test should always be a real instance of the class, though you can and should mock any of its dependencies so you can test it in isolation.

表情可笑 2024-08-13 19:27:09

我将解释我在做什么、为什么以及我得到了多少里程。

首先,关于您的存储库,我正在做完全您正在做的事情。尽管存在一些命名空间差异,这也是我所做的:

  • MyProject.Repositories.IUserRepository
  • MyProject.Repositories.Fake.UserRepository
  • MyProject.Repositories.SqlServer.UserRepository

使用我的 fake UserRepository,我也只是创建并填充一个私有 IEnumerable 集合(这是一个 List)。为什么我有这个?我使用这个存储库进行初始日常开发(因为它很快 -> 没有数据库访问==很快!)。然后我将假的存储库替换为 sql 存储库(即更改我的依赖项注入(哦哦!))。这就是这个类/命名空间存在的原因,而不是在我的单元测试中使用 Mocks 来处理“假”东西。 (这种情况会发生,但在不同的情况下)。

对于我的 sql server UserRepository,我使用 LinqToSql。关于你的问题,我使用 LinqToSql 是无关紧要的......它可以是任何其他数据库包装器。这里重要的是我正在与之集成的第三方东西


好的,所以从这里开始,我需要确保两件事

  1. 假 UserRepostiory 有效
  2. sql server UserRepository 有效。

首先,大多数人不会为虚假的东西创建单元测试。本来就是一坨假屎,何必浪费精力呢?确实如此——除了我在日常开发中使用了那块假屎(请参阅上面我对此的夸夸其谈)。所以我很快就做了一些基本的单元测试。注意:在我眼中这些都是单元测试,即使它们是存储库类。为什么?他们没有与第三方/基础设施集成

接下来(终于进入正题),我做了一个单独的测试类,它是集成测试。这是一个将与系统外部的东西集成的单元测试。它可能是真正的 Twitter API。它可能是真正的 S3 Amazon api。请注意,我使用了“真实”这个词。这就是关键,在这里。我正在与我之外的真实服务集成。因此->它很慢。任何时候我需要离开计算机获取一些数据,这都称为集成,您会自动假设(并期望)它会很慢。

所以在这里,我正在与数据库集成。

(Nae说,请不要厚颜无耻地建议您在同一台计算机上拥有数据库......您将离开您的应用程序“世界”)。

哇。这是一部战争与和平小说……是时候采取一些硬性行动了,公鸡打代码。
咱们拿来吧!

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 版本一起使用(或无论该版本是什么......我只是安装我们的工作已购买的副本)。

其次,每当类第一次初始化时(无论是一个测试还是多个测试),它都会创建上下文。就我而言,我的 Sql Server UserRepository 将使用 LinqToSql 上下文。 (您的将是 EF 上下文)。这是 TDD 的安排部分。

第三,我调用方法->这是 TDD 的Act部分。

最后,我检查是否得到了我所期望的结果 ->这是 TDD 的断言部分。


更新数据库怎么样?

只需遵循相同的模式,除非您可能希望将调用代码包装在事务中并将其回滚。否则你可能会得到 100 行可能相同的数据。这有什么缺点吗?任何identity字段都不会具有所有漂亮且漂亮的编号序列(因为回滚将“使用”该数字)。没有道理吗?不用担心。这是一个高级技巧,我想我应该把它扔进去来测试你,但这意味着要为这篇长得可怕的文章蹲下来。


所以..呃..是的。这就是我所做的。不知道这些论坛上的编程之神是否会翻转我的方式,但我有点喜欢它,我希望它可以帮助你。

HTH。

I'll explain what I'm doing, why and how much milage I get out it.

First, I'm doing exactly what you are doing, regarding your repositories. Despite some namespace differences, this is what I also do:

  • MyProject.Repositories.IUserRepository
  • MyProject.Repositories.Fake.UserRepository
  • MyProject.Repositories.SqlServer.UserRepository

With my fake UserRepository, I also just create and populate a private IEnumerable<User> collection (which is a List<User>). Why do I have this? I use this repository for my initial day to day development (because it's fast -> no db access == quick!). Then i swap over the fake respitories for the sql repositories (ie chage my dependency injection (oooohhh!)). This is why this class/namespace exists, as opposed to using Mocks in my unit test for 'fake' stuff. (That happens, but under different circumstances).

With my sql server UserRepository, I use LinqToSql. With regards to you question, it's irrelivant that I'm using LinqToSql ... it could be any other database wrapper. The important thing here is that there's a 3rd party something which i'm integrating with.


Ok, so from here, I need to make sure of two things

  1. The fake UserRepostiory works
  2. The sql server UserRepository works.

First up, most people don't create a unit test for a fake thing. It's a fake piece of turd, so why waste the energy? True --- except that I use that fake piece of turd in my day to day development (refer to my blarg about this, above). So i quickly whip up a few basic unit tests. NOTE: In my eyes these are unit tests, even though they are repository classes. Why? They aren't intergrating with a 3rd party/infrastructure.

Next (finally I get to the point), I do a seperate test class which is an Intergration Test. This is a unit test that will intergrate with something outside of the system. It could be the real Twitter api. It could be the real S3 Amazon api. Notice i used the word real. That's the key, here. I'm intergrating with a real service OUTSIDE of mine. As such -> it's slow. Anytime i need to leave my computer for some data, it's called intergrating and you automatically assume (and expect) it to be slow.

So here, i'm intergrating with a Database.

(Nae sayers, please don't Troll this with cheeky suggestions that you have the database on the same computer ... you're leaving your APPLICATION 'world').

Wow. this is some War-n-Peace novel .. time for some hard action, cock slappin code.
Lets bring it!

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);
        }
    }
}

Ok, lets run through this puppy.

First up, this is using Microsoft Unit Testing - built into VS2010 Beta2 or with the Team Foundation edition of VS2008 (or whatever that version is ... i just install the copy our work has purchased).

Second, whenever the class is first initialized (be it one test or many), it creates the context. In my case, my Sql Server UserRepository which will use a LinqToSql context. (Yours will be an EF context). This is the Arrange part of TDD.

Third, i call the method -> this is the Act part of TDD.

Last, I check if i got back what i expected -> this is the Assert part of TDD.


What about updating the DB?

Just follow the same pattern except you might want to wrap your calling code in a transaction and the roll it back. Otherwise u might get 100's of rows of data which could possibly be the same. Downside to this? Any identity fields will not have all nice and pretty numbering sequence (becuase the rollback will 'use' that number). Doesn't make sence? don't worry. that's an advanced tip i thought i'd throw in to test you out, but it means diddly squat for this hellishly long post.


so .. er.. yeah. that's what i do. Don't know if the Gods of Programming, on these forums, will flip and throw mud my way but I sorta like it and I'm hoping it might help ya.

HTH.

情绪失控 2024-08-13 19:27:09

您应该测试真实的类,而不是您为测试而创建的假类。使用接口的要点是它允许您模拟类,因此您可以在与其他协作者的测试中使用模拟版本。

为了测试该类,您应该能够传入模拟数据库,并断言当您调用 Repository 类上的方法时,您期望对数据库进行的调用实际上会发生。

以下是 C# 中模拟和测试的一个很好的介绍:

http: //refact.blogspot.com/2007/01/mock-objects-and-rhino.html

You should test the real class not a fake one you make for testing. The point of using an interface is that it allows you to mock out the class, so you can use the mock version in tests with other collaborators.

In order to test the class you should be able to pass in a mock database and assert that the calls you expected to be made in to the database actually happen when you call the methods on your Repository class.

Here is a good intro to mocking and testing in C#:

http://refact.blogspot.com/2007/01/mock-objects-and-rhino.html

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文