基于Entity Framework 4.1实现一个适用于测试的MockDbContext(上)
2011-07-22 14:46
344 查看
声明:我最近在微软加拿大开发中心工作,这是我个人的博客,跟公司没有关系。如果你在我的博客里看到我推荐微软的产品,就权当广告好了。
我们在作一些CRUD相关的单元测试的时候,通常不会真的连接到数据库,而是写一个Mock的Repository,把Entity放到一个集合啊或者Hash table啊什么的里面。
最近用Entity Framework 4.1写点小项目,在写一个Mock的Repository的时候还走了些弯路,费了一些时间,在此把过程写出来,希望能帮大家节省一点时间。
项目中用的是Model First,在Database First模型中应该也适用,先设计完数据库模型,生成数据库和两个.tt文件——MTBDbContext.Context.tt和MTBDbContext.tt。第一个文件里是一个DbContext类,担任数据持久化操作;第二个文件里是实体类。这两个文件原来与数据库模型是在同一个项目中的,做了一些小动作,把它们分开了^_^。
![](http://images.cnblogs.com/cnblogs_com/xiaomi7732/201107/201107221442494337.png)
根据这样的结构,写MockDbContext的思路是这样子的:以添加一个实体类为例,我们通常会写这样的代码:
[/code]
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
如果直接调用EF4.1的DbContext,在调用 DbContext里的SaveChanges(),数据就会被固化到数据库里。但我们相信,只要数据能够在本地保存,通过DbContext,它就一定会存到数据库里,因此,测试时没有必要把数据库写到数据库中去,只要在本地进行验证。
综上,我们需要的MockDbContext只要满足两个条件:第一,SaveChanges()不把数据固化到数据库,而是存在本地;第二,可以在本地作数据验证。
我们首先来满足第一个条件。打开DbContext看一下:
[/code]
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
这个类信息不多,因此推测逻辑都在其基类DbContext里。因此,在Object Browser里打开DbContext看了一下:
[/code]
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
里面有一个SaveChanges()方法,而且还是虚的。这样,事情就简单了(起初是这样认为的,也是从这里开始走的弯路):重写一下SaveChanges()方法,让它什么都不做,它不就不会把数据存回数据库了吗?OK,第一个条件基本达成。
第二个条件,本地验证。由于实体类对象都保存在DbSet<T>里,而DbSet<T>里有一个ObservableCollection<T>类型的Local属性保存的正是本地实体类对象(这个理解有问题的)。这样,事情就好办了,我们写测试验证的时候直接验证这里的结果就行了。
按着这个思路,复制粘贴了一份MTBDbContext.Context.tt,改名为MockMTBDbContext.Context.tt并且放到了对应的单元测试项目中。然后,稍微修改了一下模板:添加了一些using的命名空间,改了生存的类名和构造函数名,然后就是重点添加一个什么都不做的SaveChanges()的重写方法——保存,自动生成代码如下:
[/code]
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
接下来,就写了一个测试代码来看看一个基本操作:
[/code]
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
跑Case,成功Pass。哈。没想到这么轻松。
但是…
但是…
CRUD四项操作中,CUD都可以测试,R(Retrieve)的时候,却怎么也得不到结果……添加的记录在Local可以看到,但是.ToList()的时候怎么都是null。
=====
下篇解释了错误的原因,并且重新设计了一个接口以完成目标。点击继续...
我们在作一些CRUD相关的单元测试的时候,通常不会真的连接到数据库,而是写一个Mock的Repository,把Entity放到一个集合啊或者Hash table啊什么的里面。
最近用Entity Framework 4.1写点小项目,在写一个Mock的Repository的时候还走了些弯路,费了一些时间,在此把过程写出来,希望能帮大家节省一点时间。
项目中用的是Model First,在Database First模型中应该也适用,先设计完数据库模型,生成数据库和两个.tt文件——MTBDbContext.Context.tt和MTBDbContext.tt。第一个文件里是一个DbContext类,担任数据持久化操作;第二个文件里是实体类。这两个文件原来与数据库模型是在同一个项目中的,做了一些小动作,把它们分开了^_^。
![](http://images.cnblogs.com/cnblogs_com/xiaomi7732/201107/201107221442494337.png)
根据这样的结构,写MockDbContext的思路是这样子的:以添加一个实体类为例,我们通常会写这样的代码:
public void AddBatch(Handbook handbook)
[code]{
dbContext.Set<Handbook>().Add(handbook);
dbContext.SaveChanges();
}
[/code]
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
如果直接调用EF4.1的DbContext,在调用 DbContext里的SaveChanges(),数据就会被固化到数据库里。但我们相信,只要数据能够在本地保存,通过DbContext,它就一定会存到数据库里,因此,测试时没有必要把数据库写到数据库中去,只要在本地进行验证。
综上,我们需要的MockDbContext只要满足两个条件:第一,SaveChanges()不把数据固化到数据库,而是存在本地;第二,可以在本地作数据验证。
我们首先来满足第一个条件。打开DbContext看一下:
public partial class MTBContainer : DbContext
[code]{
public MTBContainer()
: base("name=MTBContainer")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Handbook> Handbooks1 { get; set; }
public DbSet<Trip> Trips { get; set; }
// … more DbSet<T>...
}
[/code]
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
这个类信息不多,因此推测逻辑都在其基类DbContext里。因此,在Object Browser里打开DbContext看了一下:
public class DbContext : IDisposable, IObjectContextAdapter
[code]{
protected DbContext();
//… more constructors
// Other code ...
public virtual int SaveChanges();
public DbSet<TEntity> Set<TEntity>() where TEntity : class;
public DbSet Set(Type entityType);
//...
}
[/code]
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
里面有一个SaveChanges()方法,而且还是虚的。这样,事情就简单了(起初是这样认为的,也是从这里开始走的弯路):重写一下SaveChanges()方法,让它什么都不做,它不就不会把数据存回数据库了吗?OK,第一个条件基本达成。
第二个条件,本地验证。由于实体类对象都保存在DbSet<T>里,而DbSet<T>里有一个ObservableCollection<T>类型的Local属性保存的正是本地实体类对象(这个理解有问题的)。这样,事情就好办了,我们写测试验证的时候直接验证这里的结果就行了。
按着这个思路,复制粘贴了一份MTBDbContext.Context.tt,改名为MockMTBDbContext.Context.tt并且放到了对应的单元测试项目中。然后,稍微修改了一下模板:添加了一些using的命名空间,改了生存的类名和构造函数名,然后就是重点添加一个什么都不做的SaveChanges()的重写方法——保存,自动生成代码如下:
...
[code]public partial class MockMTBContainer : DbContext, ITestableDbContext
{
public MockMTBContainer()
: base("name=MTBContainer")
{
}
public override int SaveChanges()
{
//Do nothing
return 0;
}
...
public DbSet<Handbook> Handbooks1 { get; set; }
public DbSet<Trip> Trips { get; set; }
…
}
[/code]
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
接下来,就写了一个测试代码来看看一个基本操作:
/// <summary>
[code]///A test for Add
///</summary>
[TestMethod()]
public void AddTest()
{
var mockDbContext = new MockMTBContainer();
BizHandbook target = new BizHandbook(mockDbContext); // use mockDbContext here.
Handbook handbook = new Handbook();
target.Add(handbook);
Assert.AreEqual<int>(1, mockDbContext.Handbooks1.Local.Count); // see whether the entity object's added
}
[/code]
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
跑Case,成功Pass。哈。没想到这么轻松。
但是…
但是…
CRUD四项操作中,CUD都可以测试,R(Retrieve)的时候,却怎么也得不到结果……添加的记录在Local可以看到,但是.ToList()的时候怎么都是null。
=====
下篇解释了错误的原因,并且重新设计了一个接口以完成目标。点击继续...
相关文章推荐
- 基于Entity Framework 4.1实现一个适用于单元测试的MockDbContext(下)
- 基于mina实现一个简单数据采集中间件的多客户端在线测试程序
- 基于Java web服务器简单实现一个Servlet容器
- 50行代码实现的一个最简单的基于 DirectShow 的视频播放器
- 基于非阻塞socket的多线程服务器的实现------一个服务器如何与多个客户端进行通信?
- 用tensorflow实现的,基于mnist数据集上的一个简易模型
- 软件测试(3)-基于等价类划分的一个小例子
- 实现一个无刷新的基于ajax的简易聊天室
- 实现一个基于Ajax的调查程序
- 实现一个基于Ajax的调查程序
- 设计模式-代理类proxy:一个接口多个实现类(基于spring框架)
- WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序)
- Effective C++:条款38:通过一个复杂的模具has-a要么“基于一些实现”
- 使用cvsnt与wincvs实现cvs的架设(最近笔者想架设一个版本管理器,现在仍然在测试中)
- 快速构建基于代码级性能测试方法的一种思路和简单实现
- 网络编程学习笔记二(实现一个基于简单TCP的用户注册程序)
- WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序)
- 基于python实现一个简单的神经网络
- 基于servlet实现微信页面授权(经过测试)
- 基于jquery实现一个滚动的分步注册向导-附源码