设计一个可单元测试的类

发布于 2024-12-05 12:03:08 字数 2669 浏览 4 评论 0原文

我正在阅读 Apress ASP.NET MVC 3 书 并尝试确保为所有内容创建单元测试可能,但花了一天的大部分时间试图弄清楚为什么编辑无法保存(请参阅此所以问题)我想为此创建一个单元测试。

我已经发现我需要为以下类创建一个单元测试:

public class EFProductRepository : IProductRepository {
    private EFDbContext context = new EFDbContext();

    public IQueryable<Product> Products {
        get { return context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            context.Products.Add(product);
        }
        context.SaveChanges();
    }

    public void DeleteProduct(Product product) {
        context.Products.Remove(product);
        context.SaveChanges();
    }
}

public class EFDbContext : DbContext {
    public DbSet<Product> Products { get; set; }
}

我正在使用 Ninject.MVC3 和 Moq,并且之前已经创建了几个单元测试(在阅读前面提到的书时),所以我正在慢慢地了解它。我已经(希望正确)创建了一个构造函数方法,使我能够传入 _context

public class EFProductRepository : IProductRepository {
    private EFDbContext _context;

    // constructor
    public EFProductRepository(EFDbContext context) {
        _context = context;
    }

    public IQueryable<Product> Products {
        get { return _context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            _context.Products.Add(product);
        } else {
            _context.Entry(product).State = EntityState.Modified;
        }
        _context.SaveChanges();
    }

    public void DeleteProduct(Product product) {
        _context.Products.Remove(product);
        _context.SaveChanges();
    }
}

但这就是我开始遇到麻烦的地方......我相信我需要为 创建一个接口EFDbContext (见下文),因此我可以用模拟存储库替换它以进行测试,但它是基于类 DbContext 构建的:

public class EFDbContext : DbContext {
    public DbSet<Product> Products { get; set; }
}

来自 System.Data.Entity我不能一辈子工作知道如何为其创建接口...如果我创建以下接口,由于缺少来自 DbContext 类的方法 .SaveChanges() ,我会收到错误我无法像“EFDbContext”那样使用“DbContext”构建接口,因为它是一个类而不是接口...

using System;
using System.Data.Entity;
using SportsStore.Domain.Entities;

namespace SportsStore.Domain.Concrete {
    interface IEFDbContext {
        DbSet<Product> Products { get; set; }
    }
}

原始源代码可以从 此页面以防我在上面的代码片段中遗漏了一些内容(或者只是询问,我会添加它)。< /em>

我已经达到了我理解的极限,无论我搜索或阅读什么,我似乎都无法弄清楚如何克服这个问题。 请帮忙!

I am going though the Apress ASP.NET MVC 3 book and trying to ensure I create Unit Tests for everything possible but after spending a good part of a day trying to work out why edit's wouldn't save (see this SO question) I wanted to create a unit test for this.

I have worked out that I need to create a unit test for the following class:

public class EFProductRepository : IProductRepository {
    private EFDbContext context = new EFDbContext();

    public IQueryable<Product> Products {
        get { return context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            context.Products.Add(product);
        }
        context.SaveChanges();
    }

    public void DeleteProduct(Product product) {
        context.Products.Remove(product);
        context.SaveChanges();
    }
}

public class EFDbContext : DbContext {
    public DbSet<Product> Products { get; set; }
}

I am using Ninject.MVC3 and Moq and have created several unit tests before (while working though the previously mentioned book) so am slowly getting my head around it. I have already (hopefully correctly) created a constructor method to enable me to pass in _context:

public class EFProductRepository : IProductRepository {
    private EFDbContext _context;

    // constructor
    public EFProductRepository(EFDbContext context) {
        _context = context;
    }

    public IQueryable<Product> Products {
        get { return _context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            _context.Products.Add(product);
        } else {
            _context.Entry(product).State = EntityState.Modified;
        }
        _context.SaveChanges();
    }

    public void DeleteProduct(Product product) {
        _context.Products.Remove(product);
        _context.SaveChanges();
    }
}

BUT this is where I start to have trouble... I believe I need to create an Interface for EFDbContext (see below) so I can replace it with a mock repo for the tests BUT it is built on the class DbContext:

public class EFDbContext : DbContext {
    public DbSet<Product> Products { get; set; }
}

from System.Data.Entity and I can't for the life of me work out how to create an interface for it... If I create the following interface I get errors due to lack of the method .SaveChanges() which is from the DbContext class and I can't build the interface using "DbContext" like the `EFDbContext is as it's a class not an interface...

using System;
using System.Data.Entity;
using SportsStore.Domain.Entities;

namespace SportsStore.Domain.Concrete {
    interface IEFDbContext {
        DbSet<Product> Products { get; set; }
    }
}

The original Source can be got from the "Source Code/Downloads" on this page encase I have missed something in the above code fragments (or just ask and I will add it).

I have hit the limit of what I understand and no mater what I search for or read I can't seem to work out how I get past this. Please help!

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

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

发布评论

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

评论(2

方觉久 2024-12-12 12:03:08

这里的问题是你抽象得不够。抽象/接口的要点是定义一个以技术无关的方式公开行为的契约。

换句话说,为 EFDbContext 创建接口是一个很好的第一步,但该接口仍然与具体实现 - DbSet (DbSet) 相关联。

解决此问题的快速方法是将此属性公开为 IDbSet 而不是 DbSet。理想情况下,您公开一些更抽象的东西,例如 IQueryable (尽管这不会为您提供 Add() 方法等)。越抽象,越容易被嘲笑。

然后,您需要履行您所依赖的“合同”的其余部分 - 即 SaveChanges() 方法。

您更新后的代码将如下所示:

public class EFProductRepository : IProductRepository {
    private IEFDbContext context;

    public EFProductRepository(IEFDbContext context) {
        this.context = context;
    }
    ...
}

public interface IEFDbContext {
   IDbSet<Product> Products { get; set; }
   void SaveChanges();
}

但是...您必须问的主要问题是:您要测试什么(相反,您要模拟/避免测试什么)?换句话说:您是在尝试验证应用程序在保存某些内容时的工作方式,还是在测试实际保存

如果您只是测试应用程序的工作方式而不关心实际保存到数据库,那么我会考虑在更高级别进行模拟 - IProductRepository。那么你根本就没有访问数据库。

如果您想确保您的对象实际上持久保存到数据库中,那么您应该访问 DbContext 并且毕竟不想模拟该部分。

就我个人而言,我认为这两种场景是不同的 - 并且同样重要 - 我为每个场景编写单独的测试:一个测试我的应用程序是否执行其应该执行的操作,另一个测试数据库交互有效。

The problem here is that you have not abstracted enough. The point of abstractions/interfaces is to define a contract that exposes behavior in a technology-agnostic way.

In other words, it is a good first step that you created an interface for the EFDbContext, but that interface is still tied to the concrete implementation - DbSet (DbSet).

The quick fix for this is to expose this property as IDbSet instead of DbSet. Ideally you expose something even more abstract like IQueryable (though this doesn't give you the Add() methods, etc.). The more abstract, the easier it is to mock.

Then, you're left with fulfilling the rest of the "contract" that you rely on - namely the SaveChanges() method.

Your updated code would look like this:

public class EFProductRepository : IProductRepository {
    private IEFDbContext context;

    public EFProductRepository(IEFDbContext context) {
        this.context = context;
    }
    ...
}

public interface IEFDbContext {
   IDbSet<Product> Products { get; set; }
   void SaveChanges();
}

BUT... the main question you have to ask is: what are you trying to test (conversely, what are you trying to mock out/avoid testing)? In other words: are you trying to validate how your application works when something is saved, or are you testing the actual saving.

If you're just testing how your application works and don't care about actually saving to the database, I'd consider mocking at a higher level - the IProductRepository. Then you're not hitting the database at all.

If you want to make sure that your objects actually get persisted to the database, then you should be hitting the DbContext and don't want to mock that part after all.

Personally, I consider both of those scenarios to be different - and equally important - and I write separate tests for each of them: one to test that my application does what it's supposed to do, and another to test that the database interaction works.

蓝色星空 2024-12-12 12:03:08

我猜您当前的代码看起来像这样(我放入接口中):

public class EFProductRepository : IProductRepository {
    private IEFDbContext _context;

    // constructor
    public EFProductRepository(IEFDbContext context) {
        _context = context;
    }

    public IQueryable<Product> Products {
        get { return _context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            _context.Products.Add(product);
        } else {
            _context.Entry(product).State = EntityState.Modified;
        }
        **_context.SaveChanges();**
    }

    public void DeleteProduct(Product product) {
        _context.Products.Remove(product);
        **_context.SaveChanges();**
    }
}

public class EFDbContext : DbContext, IEFDbContext {
    public DbSet<Product> Products { get; set; }
}

public interface IEFDbContext {
    DbSet<Product> Products { get; set; }
}

问题是 EFProductRepository 现在需要一个实现 IEFDbContext 接口的对象,但该接口没有定义我在星号之间放置的行中使用了 SaveChanges 方法,以便编译器开始抱怨。

IEFDbContext 接口上定义 SaveChanges 方法可以解决您的问题:

public interface IEFDbContext {
    DbSet<Product> Products { get; set; }

    void SaveChanges();
}

I guess your current code looks something like this (I put in the interface):

public class EFProductRepository : IProductRepository {
    private IEFDbContext _context;

    // constructor
    public EFProductRepository(IEFDbContext context) {
        _context = context;
    }

    public IQueryable<Product> Products {
        get { return _context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            _context.Products.Add(product);
        } else {
            _context.Entry(product).State = EntityState.Modified;
        }
        **_context.SaveChanges();**
    }

    public void DeleteProduct(Product product) {
        _context.Products.Remove(product);
        **_context.SaveChanges();**
    }
}

public class EFDbContext : DbContext, IEFDbContext {
    public DbSet<Product> Products { get; set; }
}

public interface IEFDbContext {
    DbSet<Product> Products { get; set; }
}

The problem is EFProductRepository now expects an object implementing the IEFDbContext interface, but this interface does not define the SaveChanges method used at the lines I put between the asteriskes so the compiler starts complaining.

Defining the SaveChanges method on the IEFDbContext interface solves your problem:

public interface IEFDbContext {
    DbSet<Product> Products { get; set; }

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