2016-05-17 44 views
1

我试图在服务中的查询中测试业务逻辑。所以我不希望我的测试能够真正访问数据库,因为它们是单元测试,而不是集成测试。使用实体框架而不依赖注入的测试服务

所以我已经做了一个简单的例子,我的上下文,以及我如何试图填补它。

我有一个实体

public class SomeEntity 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

和服务

public class Service 
{ 
    public int CountSomeEntites() 
    { 
     using (var ctx = new Realcontext()) 
     { 
      int result = ctx.SomeEntities.Count(); 
      return result; 
     } 
    } 
} 

而这才是真正的背景下

public partial class Realcontext : DbContext 
{ 
    public virtual DbSet<SomeEntity> SomeEntities { get; set; } 

    public Realcontext() : base("name=Realcontext") 
    { 
     InitializeContext(); 
    } 

    partial void InitializeContext(); 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     throw new UnintentionalCodeFirstException(); 
    } 
} 

所以我试图创建一个假背景和我在我的测试方法中虚构了真实环境的构造函数

这是假的情况下

public class FakeContext : DbContext 
{ 
    public DbSet<SomeEntity> SomeEntities { get; set; } 

    public FakeContext() 
    { 
    } 
} 

最后的测试类

[TestClass] 
public class ServiceTests 
{ 
    [TestMethod] 
    public void CountEmployee_ShoulReturnCorrectResult() 
    { 
     using (ShimsContext.Create()) 
     { 
      ShimRealcontext.Constructor = context => GenerateFakeContext(); 
      ShimDbContext.AllInstances.Dispose =() => DummyDispose(); 

      Service service = new Service(); 
      int result = service.CountSomeEntites(); 

      Assert.AreEqual(result, 2); 
     } 
    } 

    private FakeContext GenerateFakeContext() 
    { 
     FakeContext fakeContext = new FakeContext(); 
     fakeContext.SomeEntities.AddRange(new[] 
     { 
      new SomeEntity {Id = 1, Name = "entity1"}, 
      new SomeEntity {Id = 2, Name = "entity2"} 
     }); 
     return fakeContext; 
    } 
} 

当我运行测试时,RealContext构造正常返回,一个FakeContext建在GenerateFakeContext()方法,它包含2 SomeEntities并返回,但在服务之后,变量ctx的属性SomeEntities等于空。

是因为我的变量ctx被声明为new RealContext()?但调用RealContext的构造函数会返回FakeContext(),那么该变量应该是FakeContext类型的变量吗?

我做错了什么?还是有没有其他的方式来测试服务而不访问真正的数据库?

+0

在我们的测试中,我们借鉴了EF测试源中的模拟。它在Apache许可证下。 – Eris

+0

谢谢!我不知道这个工具。我会看看 –

回答

0

我有simlair的情况,我解决了它与构建配置和条件编译。这不是最好的解决方案,但它为我工作并解决了问题。这是发票:

1.创建DataContext的接口

首先,你需要创建将由你要使用这两个方面CLASSE来实现的接口。让它被命名为'IMyDataContext'。在它里面,你需要描述你需要访问的所有DbSets。

public interaface IMyDataContext 
{ 
    DbSet<SomeEntity> SomeEntities { get; set; } 
} 

而且两者你的上下文类需要impelemt它:

public partial class RealDataContext : DataContext, IMyDataContext 
{ 
    DbSet<SomeEntity> SomeEntities { get; set; } 

    /* Contructor, Initialization code, etc... */ 
} 

public class FakeDataContext : DataContext, IMyDataContext 
{ 
    DbSet<SomeEntity> SomeEntities { get; set; } 

    /* Mocking, test doubles, etc... */ 
} 

通过你甚至可以使性的判定只读在接口级的方式。

2.新增“测试”构建配置

Here你可以找到如何添加新的生成配置。我给我的配置命名为'测试'。创建新配置后,请转到您的DAL项目属性,左侧窗格中的“生成”部分。在'配置'下拉列表中选择您刚刚创建的配置,并在输入'条件编译符号'中输入'TEST'。

3 Incapsulate背景下注射

需要明确的是,我的做法仍然是法/基于属性DI液=)

所以现在我们需要实现一些注入代码。为了简单起见,如果您需要更多的抽象,您可以将其直接添加到您的服务中或提取到另一个类中。主要想法是使用条件编译方向而不是IoC框架。

public class Service 
{ 
    // Injector method 
    private IMyDataContext GetContext() { 
     // Here is the main code 

#if TEST // <-- In 'Test' configuration 
      // we will use fake context 
      return new FakeDataContext(); 
#else 
      // in any other case 
      // we will use real context 
      return new RealDataContext(); 
#endif 

    } 

    public int CountSomeEntites() 
    { 
     // the service works with interface and does know nothing 
     // about the implementation 

     using (IMyDataContext ctx = GetContext()) 
     { 
      int result = ctx.SomeEntities.Count(); 
      return result; 
     } 
    } 
} 

限制

所描述的方法解决了您所描述的问题,但它有一个限制:作为的IoC允许你切换上下文动态在运行时,条件complation需要你重新编译解决方案。

在我的情况下,这不是一个问题 - 我的代码没有被100%的测试覆盖,而且我也没有在每个版本上运行它们。通常我只在提交代码前运行测试,因此在VS中切换构建配置非常简单,运行测试,确保没有任何内容被破坏,然后返回到调试模式。在发布模式下,您也不需要运行测试。即使您需要 - 您可以制作“发布构建测试模式”配置并继续使用相同的解决方案。

另一个问题是,如果您持续集成 - 您需要对生成服务器进行其他设置。这里有两种方法:

  • 设置两个构建定义:一个用于发布,一个用于测试。如果您的服务器设置为自动释放,则需要小心,因为在部署第一台服务器时,会在第二台服务器中显示测试失败。
  • 设置复杂构建定义,它首次在Test配置中构建您的代码,运行测试并且如果它们正常 - 然后重新编译目标配置中的代码并准备部署。

因此,任何解决方案都是简化性和灵活性之间的又一折衷。

UPDATE

一段时间后,我明白我上面描述的方法是非常沉重的。我的意思是 - 建立配置。如果只有两个IDataContext的实现:'Core'和'Fake',你可以简单地使用bool参数和简单的if/else分支来代替编译指令#if/#else/#endif以及配置你的构建服务器的所有头痛。

如果你有两个以上的实现 - 你可以使用枚举和switch块。这里的一个探讨是定义你将在default的情况下返回什么,或者如果值超出了枚举的范围。

但是这种方法的主要好处是您不再需要编译时间。注射器参数可随时更改,例如使用web.config和ConfigurationManager。使用它你可以在运行时切换你的数据上下文。