惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
MyScale Blog
MyScale Blog
P
Proofpoint News Feed
S
Security @ Cisco Blogs
N
News and Events Feed by Topic
H
Hacker News: Front Page
Attack and Defense Labs
Attack and Defense Labs
S
Secure Thoughts
Microsoft Security Blog
Microsoft Security Blog
N
Netflix TechBlog - Medium
U
Unit 42
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
Google Online Security Blog
Google Online Security Blog
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
L
LINUX DO - 热门话题
T
Tenable Blog
博客园 - 叶小钗
D
DataBreaches.Net
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
人人都是产品经理
人人都是产品经理
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
量子位
P
Proofpoint News Feed
H
Help Net Security
Blog — PlanetScale
Blog — PlanetScale
宝玉的分享
宝玉的分享
Recorded Future
Recorded Future
The Register - Security
The Register - Security
F
Fortinet All Blogs
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
S
Schneier on Security
V
Vulnerabilities – Threatpost
雷峰网
雷峰网
Microsoft Azure Blog
Microsoft Azure Blog
G
GRAHAM CLULEY
G
Google Developers Blog
月光博客
月光博客
V
V2EX
T
Troy Hunt's Blog
A
Arctic Wolf

博客园 - Saar

LightSwitch 2011 实践之个人汽车仓库 —— 起步 让我们更快一点的Visual Studio的技巧们 实战 WPF Jump List 编程 实战Bing MAP REST服务的封装、调用 选好 Async 函数的返回类型 用WCF + Entity Framework 序列化时的陷阱 大家都来搭架构 —— VS 2010 Layer Diagram (层图) 基本使用 基于Entity Framework 4.1实现一个适用于单元测试的MockDbContext(下) 利用IProgress轻松搞定进度条编程 C# 5.0功能之Async一瞥 C#不是C++ 之 委托(Delegate) C#不是C++ 之 泛型(Generic) Silverlight 学习笔记——MVVM模式实现主从数据显示 Silverlight 学习笔记——用MediaElement制作在线音乐播放器 Silverlight 学习笔记——应用程序模型 Visual Studio 2010 部分新功能概览-QS, HR, GFU Silverlight学习笔记——依赖属性和路由事件 Silverlight 学习笔记——布局 Visual Studio 2010 部分新功能概览-Dynamic & Named / Optional Parameters
基于Entity Framework 4.1实现一个适用于测试的MockDbContext(上)
Saar · 2011-07-22 · via 博客园 - Saar

声明:我最近在微软加拿大开发中心工作,这是我个人的博客,跟公司没有关系。如果你在我的博客里看到我推荐微软的产品,就权当广告好了。

我们在作一些CRUD相关的单元测试的时候,通常不会真的连接到数据库,而是写一个Mock的Repository,把Entity放到一个集合啊或者Hash table啊什么的里面。
最近用Entity Framework 4.1写点小项目,在写一个Mock的Repository的时候还走了些弯路,费了一些时间,在此把过程写出来,希望能帮大家节省一点时间。
项目中用的是Model First,在Database First模型中应该也适用,先设计完数据库模型,生成数据库和两个.tt文件——MTBDbContext.Context.tt和MTBDbContext.tt。第一个文件里是一个DbContext类,担任数据持久化操作;第二个文件里是实体类。这两个文件原来与数据库模型是在同一个项目中的,做了一些小动作,把它们分开了^_^。

image

根据这样的结构,写MockDbContext的思路是这样子的:以添加一个实体类为例,我们通常会写这样的代码:

        public void AddBatch(Handbook handbook)
        {
            dbContext.Set<Handbook>().Add(handbook);
            dbContext.SaveChanges();
        }

如果直接调用EF4.1的DbContext,在调用 DbContext里的SaveChanges(),数据就会被固化到数据库里。但我们相信,只要数据能够在本地保存,通过DbContext,它就一定会存到数据库里,因此,测试时没有必要把数据库写到数据库中去,只要在本地进行验证。
综上,我们需要的MockDbContext只要满足两个条件:第一,SaveChanges()不把数据固化到数据库,而是存在本地;第二,可以在本地作数据验证。
我们首先来满足第一个条件。打开DbContext看一下:

    public partial class MTBContainer : DbContext
    {
        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>...
    }

这个类信息不多,因此推测逻辑都在其基类DbContext里。因此,在Object Browser里打开DbContext看了一下:

    public class DbContext : IDisposable, IObjectContextAdapter
    {
        protected DbContext();
           //… more constructors
          // Other code ...
        public virtual int SaveChanges();
        public DbSet<TEntity> Set<TEntity>() where TEntity : class;
        public DbSet Set(Type entityType);
                  //...
    }

里面有一个SaveChanges()方法,而且还是虚的。这样,事情就简单了(起初是这样认为的,也是从这里开始走的弯路):重写一下SaveChanges()方法,让它什么都不做,它不就不会把数据存回数据库了吗?OK,第一个条件基本达成。

第二个条件,本地验证。由于实体类对象都保存在DbSet<T>里,而DbSet<T>里有一个ObservableCollection<T>类型的Local属性保存的正是本地实体类对象(这个理解有问题的)。这样,事情就好办了,我们写测试验证的时候直接验证这里的结果就行了。

按着这个思路,复制粘贴了一份MTBDbContext.Context.tt,改名为MockMTBDbContext.Context.tt并且放到了对应的单元测试项目中。然后,稍微修改了一下模板:添加了一些using的命名空间,改了生存的类名和构造函数名,然后就是重点添加一个什么都不做的SaveChanges()的重写方法——保存,自动生成代码如下:

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

接下来,就写了一个测试代码来看看一个基本操作:

        /// <summary>
        ///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
        }

跑Case,成功Pass。哈。没想到这么轻松。

但是…

但是…

CRUD四项操作中,CUD都可以测试,R(Retrieve)的时候,却怎么也得不到结果……添加的记录在Local可以看到,但是.ToList()的时候怎么都是null。

=====

下篇解释了错误的原因,并且重新设计了一个接口以完成目标。点击继续...

Little knowledge is dangerous.