C# 人们是如何使用EntityFramework6进行单元测试的呢?
我只是从单元测试和TDD开始。我以前涉猎过,但现在我决心将其添加到我的工作流程中,并编写更好的软件 我昨天问了一个问题,其中包括这个,但这似乎是一个问题本身。我已经坐下来开始实现一个服务类,我将使用它从控制器中抽象出业务逻辑,并使用EF6映射到特定的模型和数据交互 问题是,我已经封锁了我自己,因为我不想将EF抽象到存储库中(对于特定的查询,它仍然可以在服务之外使用,等等),并且想测试我的服务(将使用EF上下文) 我想问题是,这样做有意义吗?如果是这样的话,鉴于IQueryable造成的抽象漏洞和许多关于单元测试的伟大文章并不简单,因为Linq提供者在处理特定数据库的内存实现时存在差异,人们在野外是如何做的 我想测试的代码似乎很简单。(这只是试图理解我在做什么的伪代码,我想使用TDD驱动创建) 上下文C# 人们是如何使用EntityFramework6进行单元测试的呢?,c#,entity-framework,unit-testing,entity-framework-6,C#,Entity Framework,Unit Testing,Entity Framework 6,我只是从单元测试和TDD开始。我以前涉猎过,但现在我决心将其添加到我的工作流程中,并编写更好的软件 我昨天问了一个问题,其中包括这个,但这似乎是一个问题本身。我已经坐下来开始实现一个服务类,我将使用它从控制器中抽象出业务逻辑,并使用EF6映射到特定的模型和数据交互 问题是,我已经封锁了我自己,因为我不想将EF抽象到存储库中(对于特定的查询,它仍然可以在服务之外使用,等等),并且想测试我的服务(将使用EF上下文) 我想问题是,这样做有意义吗?如果是这样的话,鉴于IQueryable造成的抽象漏洞和
public interface IContext
{
IDbSet<Product> Products { get; set; }
IDbSet<Category> Categories { get; set; }
int SaveChanges();
}
public class DataContext : DbContext, IContext
{
public IDbSet<Product> Products { get; set; }
public IDbSet<Category> Categories { get; set; }
public DataContext(string connectionString)
: base(connectionString)
{
}
}
公共接口IContext
{
IDbSet产品{get;set;}
IDbSet类别{get;set;}
int SaveChanges();
}
公共类DataContext:DbContext、IContext
{
公共IDbSet产品{get;set;}
公共IDbSet类别{get;set;}
公共数据上下文(字符串连接字符串)
:基本(连接字符串)
{
}
}
服务
public class ProductService : IProductService
{
private IContext _context;
public ProductService(IContext dbContext)
{
_context = dbContext;
}
public IEnumerable<Product> GetAll()
{
var query = from p in _context.Products
select p;
return query;
}
}
公共类ProductService:IPProductService
{
私有IContext_上下文;
公共产品服务(IContext dbContext)
{
_context=dbContext;
}
公共IEnumerable GetAll()
{
var query=来自_context.Products中的p
选择p;
返回查询;
}
}
目前我的心态是做几件事:
有人在没有回购协议的情况下真的这么做并取得成功吗?我不会对我不拥有的代码进行单元测试。你在这里测试什么,MSFT编译器可以工作 也就是说,要使此代码可测试,几乎必须将数据访问层与业务逻辑代码分离。我所做的就是把我所有的EF东西放在一个(或多个)DAO或DAL类中,这个类也有相应的接口。然后我编写我的服务,将DAO或DAL对象作为依赖项(最好是构造函数注入)注入,作为接口引用。现在,需要测试的部分(您的代码)可以通过模拟DAO接口并将其注入单元测试中的服务实例来轻松测试
//this is testable just inject a mock of IProductDAO during unit testing
public class ProductService : IProductService
{
private IProductDAO _productDAO;
public ProductService(IProductDAO productDAO)
{
_productDAO = productDAO;
}
public List<Product> GetAllProducts()
{
return _productDAO.GetAll();
}
...
}
//这是可测试的,只需在单元测试期间插入IPProductDAO的模拟即可
公共类ProductService:IPProductService
{
私有IPProductDao_productDAO;
公共产品服务(IPProductDAO productDAO)
{
_productDAO=productDAO;
}
公共列表GetAllProducts()
{
return_productDAO.GetAll();
}
...
}
<>我会认为现场数据访问层是集成测试的一部分,而不是单元测试。我以前见过一些人对hibernate访问数据库的次数进行验证,但他们参与的项目涉及其数据存储中的数十亿条记录,这些额外的访问非常重要。如果你想单元测试代码,那么你需要隔离你想测试的代码(在本例中是你的服务)来自外部资源(如数据库)。您可能可以使用某种存储库模式来实现这一点,但是更常见的方法是抽象出您的EF实现,例如使用某种存储库模式。如果没有这种隔离,您编写的任何测试都将是集成测试,而不是单元测试 至于测试EF代码——我为我的存储库编写自动集成测试,在初始化过程中将不同的行写入数据库,然后调用我的存储库实现以确保它们的行为符合预期(例如,确保结果被正确筛选,或以正确的顺序排序)
这些是集成测试而不是单元测试,因为测试依赖于数据库连接的存在,并且目标数据库已经安装了最新的模式。这是我非常感兴趣的主题。有许多纯粹主义者说你不应该测试像EF和NHibernate这样的技术。他们是对的,他们已经经过了严格的测试,正如前面的回答所说,花大量时间测试你不拥有的东西通常是毫无意义的 但是,您确实拥有下面的数据库在我看来,这就是这种方法失败的地方,您不需要测试EF/NH是否正确地完成了他们的工作。您需要测试映射/实现是否与数据库一起工作。在我看来,这是您可以测试的系统中最重要的部分之一 但是严格地说,我们正在从单元测试领域转移到集成测试领域,但原则保持不变 你需要做的第一件事就是能够模仿你的DAL,这样你的BLL c
[Test]
public void LoadUser()
{
this.RunTest(session => // the NH/EF session to attach the objects to
{
var user = new UserAccount("Mr", "Joe", "Bloggs");
session.Save(user);
return user.UserID;
}, id => // the ID of the entity we need to load
{
var user = LoadMyUser(id); // load the entity
Assert.AreEqual("Mr", user.Title); // test your properties
Assert.AreEqual("Joe", user.Firstname);
Assert.AreEqual("Bloggs", user.Lastname);
}
}
[SetUp]
public void Setup()
{
this.SetupTest(session => // the NH/EF session to attach the objects to
{
var user = new UserAccount("Mr", "Joe", "Bloggs");
session.Save(user);
this.UserID = user.UserID;
});
}
[TearDown]
public void TearDown()
{
this.TearDownDatabase();
}
[Test]
public void TestTitle()
{
var user = LoadMyUser(this.UserID); // load the entity
Assert.AreEqual("Mr", user.Title);
}
[Test]
public void TestFirstname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Joe", user.Firstname);
}
[Test]
public void TestLastname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Bloggs", user.Lastname);
}
public TestProperties : SingleSetup
{
public int UserID {get;set;}
public override DoSetup(ISession session)
{
var user = new User("Joe", "Bloggs");
session.Save(user);
this.UserID = user.UserID;
}
[Test]
public void TestLastname()
{
var user = LoadMyUser(this.UserID); // load the entity
Assert.AreEqual("Bloggs", user.Lastname);
}
[Test]
public void TestFirstname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Joe", user.Firstname);
}
}
public TestProperties : SetupPerTest
{
[Test]
public void EnsureCorrectReferenceIsLoaded()
{
int friendID = 0;
this.RunTest(session =>
{
var user = CreateUserWithFriend();
session.Save(user);
friendID = user.Friends.Single().FriendID;
} () =>
{
var user = GetUser();
Assert.AreEqual(friendID, user.Friends.Single().FriendID);
});
}
[Test]
public void EnsureOnlyCorrectFriendsAreLoaded()
{
int userID = 0;
this.RunTest(session =>
{
var user = CreateUserWithFriends(2);
var user2 = CreateUserWithFriends(5);
session.Save(user);
session.Save(user2);
userID = user.UserID;
} () =>
{
var user = GetUser(userID);
Assert.AreEqual(2, user.Friends.Count());
});
}
}
Use TransactionScope in the tests methods to avoid changes in the database.
[TestClass]
public class NameValueTest
{
[TestMethod]
public void Edit()
{
NameValueController controller = new NameValueController();
using(var ts = new TransactionScope()) {
Assert.IsNotNull(controller.Edit(new Models.NameValue()
{
NameValueId = 1,
name1 = "1",
name2 = "2",
name3 = "3",
name4 = "4"
}));
//no complete, automatically abort
//ts.Complete();
}
}
[TestMethod]
public void Create()
{
NameValueController controller = new NameValueController();
using (var ts = new TransactionScope())
{
Assert.IsNotNull(controller.Create(new Models.NameValue()
{
name1 = "1",
name2 = "2",
name3 = "3",
name4 = "4"
}));
//no complete, automatically abort
//ts.Complete();
}
}
}
public class FeatureService {
private readonly IMediator _mediator;
public FeatureService(IMediator mediator) {
_mediator = mediator;
}
public async Task ComplexBusinessLogic() {
// retrieve relevant objects
var results = await _mediator.Send(new GetRelevantDbObjectsQuery());
// normally, this would have looked like...
// var results = _myDbContext.DbObjects.Where(x => foo).ToList();
// perform business logic
// ...
}
}
public class GetRelevantDbObjectsQuery : IRequest<DbObject[]> {
// no input needed for this particular request,
// but you would simply add plain properties here if needed
}
public class GetRelevantDbObjectsEFQueryHandler : IRequestHandler<GetRelevantDbObjectsQuery, DbObject[]> {
private readonly IDbContext _db;
public GetRelevantDbObjectsEFQueryHandler(IDbContext db) {
_db = db;
}
public DbObject[] Handle(GetRelevantDbObjectsQuery message) {
return _db.DbObjects.Where(foo => bar).ToList();
}
}
[TestClass]
public class FeatureServiceTests {
// mock of Mediator to handle request/responses
private Mock<IMediator> _mediator;
// subject under test
private FeatureService _sut;
[TestInitialize]
public void Setup() {
// set up Mediator mock
_mediator = new Mock<IMediator>(MockBehavior.Strict);
// inject mock as dependency
_sut = new FeatureService(_mediator.Object);
}
[TestCleanup]
public void Teardown() {
// ensure we have called or expected all calls to Mediator
_mediator.VerifyAll();
}
[TestMethod]
public void ComplexBusinessLogic_Does_What_I_Expect() {
var dbObjects = new List<DbObject>() {
// set up any test objects
new DbObject() { }
};
// arrange
// setup Mediator to return our fake objects when it receives a message to perform our query
// in practice, I find it better to create an extension method that encapsulates this setup here
_mediator.Setup(x => x.Send(It.IsAny<GetRelevantDbObjectsQuery>(), default(CancellationToken)).ReturnsAsync(dbObjects.ToArray()).Callback(
(GetRelevantDbObjectsQuery message, CancellationToken token) => {
// using Moq Callback functionality, you can make assertions
// on expected request being passed in
Assert.IsNotNull(message);
});
// act
_sut.ComplexBusinessLogic();
// assertions
}
}
- MyProject
- Features
- MyFeature
- Queries
- Commands
- Services
- DependencyConfig.cs (Ninject feature modules)
return new MyContext(@"Server=(localdb)\mssqllocaldb;Database=EFProviders.InMemory;Trusted_Connection=True;");
public virtual DbSet<Branch> Branches { get; set; }
public virtual DbSet<Warehouse> Warehouses { get; set; }
internal static Db Bootstrap(bool onlyMockPassedTables = false, List<Branch> branches = null, List<Products> products = null, List<Warehouses> warehouses = null)
{
if (onlyMockPassedTables == false) {
branches ??= new List<Branch> { MakeBranch() };
warehouses ??= new List<Warehouse>{ MakeWarehouse() };
}
branches?.ForEach(b => {
b.Warehouse = warehouses.FirstOrDefault(w => w.ID == b.WarehouseID);
});
warehouses?.ForEach(w => {
w.Branches = branches.Where(b => b.WarehouseID == w.ID);
});
var context = new Db(new DbContextOptionsBuilder<Db>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options);
context.Branches.AddRange(branches);
context.Warehouses.AddRange(warehouses);
context.SaveChanges();
return context;
}
internal const int BranchID = 1;
internal const int WarehouseID = 2;
internal static Branch MakeBranch(int id = BranchID, string code = "The branch", int warehouseId = WarehouseID) => new Branch { ID = id, Code = code, WarehouseID = warehouseId };
internal static Warehouse MakeWarehouse(int id = WarehouseID, string code = "B", string name = "My Big Warehouse") => new Warehouse { ID = id, Code = code, Name = name };
[Test]
[TestCase(new string [] {"ABC", "DEF"}, "ABC", ExpectedResult = 1)]
[TestCase(new string [] {"ABC", "BCD"}, "BC", ExpectedResult = 2)]
[TestCase(new string [] {"ABC"}, "EF", ExpectedResult = 0)]
[TestCase(new string[] { "ABC", "DEF" }, "abc", ExpectedResult = 1)]
public int Given_SearchingForBranchByName_Then_ReturnCount(string[] codesInDatabase, string searchString)
{
// Arrange
var branches = codesInDatabase.Select(x => UnitTestHelpers.MakeBranch(code: $"qqqq{x}qqq")).ToList();
var db = UnitTestHelpers.Bootstrap(branches: branches);
var service = new BranchService(db);
// Act
var result = service.SearchByName(searchString);
// Assert
return result.Count();
}