模拟Linq2Sql DataContext

Gre*_*ter 19 c# unit-testing mocking linq-to-sql c#-4.0

我有一个Lin2Sql DataContext,我用来从sql数据库获取我的所有数据,但是我很难找到成功模拟这个的方法,以便我可以创建相关的单元测试.

在我想要测试的数据访问对象中,我每次刷新上下文,我发现很难找到一种简单的方法来模拟它.

任何有关此事的帮助将不胜感激.

And*_*bel 20

模拟linq-to-sql上下文确实是一项艰巨的任务.我通常通过让我的单元测试针对单独的数据库副本运行来解决它,并使用专门设计的数据来适应单元测试.(我知道可以说它不再是单元测试,而是集成测试,但只要我测试代码,我就不在乎了).

为了使数据库保持已知状态,我将每个测试包装TransactionScope在测试结束时回滚的测试中.这样,数据库的状态永远不会改变.

示例测试方法如下所示:

[TestMethod]
public void TestRetire()
{
    using (TransactionScope transaction = new TransactionScope())
    {
        Assert.IsTrue(Car.Retire("VLV100"));
        Assert.IsFalse(Car.Retire("VLV100"));

        // Deliberately not commiting transaction.
    }
}
Run Code Online (Sandbox Code Playgroud)

该代码来自一篇关于我前段时间写的方法的博文:http://coding.abel.nu/2011/12/using-transactions-for-unit-tests/

  • +100!:这是**测试DAL的方式**.模拟ORM /数据访问提供程序很难,耗时,并且通常会导致非常脆弱的测试.不要*单元测试*DAL(纯单元测试样式); 做什么安德斯建议. (3认同)
  • 这是我想采用的一般方法……但是,测试数据库是如何维护和版本控制的?如果可能的话,我不希望有单独的站立过程或大型数据库 - 使用 MDF 的一些明智方法?SQL Express? (2认同)

Jup*_*aol 13

既然你要求一种模拟方法,DataContext我认为你真的想做一些单元测试而不是集成测试.

好吧,我会告诉你如何做到这一点,但首先我想鼓励你阅读以下链接,它们都是关于编写干净的可测试代码.

并查看此响应中的链接:

观看Misko Hevery的清洁代码谈话(赠送给Google员工)

我曾经在自己和工作中重复的一件事是,任何人都可以编写单元测试,因为它们很容易编写.因此,一个简单的测试基本上都是关于进行一些比较并在结果失败时抛出异常,任何人都可以做到这一点.当然,有数百个框架可以帮助我们以优雅的方式编写这些测试.但真正的交易,以及真正的努力将被用于学习如何编写干净的可测试代码

即使你雇用Misko Hevery来帮助你编写测试,如果你的代码不是测试友好的,他也会很难写.

现在模拟DataContext对象的方法是:不要这样做

而是使用自定义界面包围调用:

public interface IMyDataContextCalls
{
    void Save();
    IEnumerable<Product> GetOrders();
}
// this will be your DataContext wrapper
// this wll act as your domain repository
public class MyDataContextCalls : IMyDataContextCalls
{
    public MyDataContextCalls(DataClasses1DataContext context)
    {
        this.Context = context;
    }

    public void Save()
    {
        this.Context.SubmitChanges();
    }

    public IEnumerable<Product> GetOrders()
    {
        // place here your query logic
        return this.Context.Products.AsEnumerable();
    }


    private DataClasses1DataContext Context { get; set; }

}

// this will be your domain object
// this object will call your repository wrapping the DataContext
public class MyCommand
{
    private IMyDataContextCalls myDataContext;
    public MyCommand(IMyDataContextCalls myDataContext)
    {
        this.myDataContext = myDataContext;
    }

    public bool myDomainRule = true;

    // assume this will be the SUT (Subject Under Test)
    public void Save()
    {
        // some business logic
        // this logic will be tested
        if (this.myDomainRule == true)
        {
            this.myDataContext.Save();
        }
        else
        {
            // handle your domain validation  errors
            throw new InvalidOperationException();
        }
    }
}

[TestClass]
public class MyTestClass
{
    [TestMethod]
    public void MyTestMethod()
    {
        // in this test your mission is to test the logic inside the 
        // MyCommand.Save method
        // create the mock, you could use a framework to auto mock it
        // or create one manually
        // manual example:
        var m = new MyCommand(new MyFakeDataContextFake());

        m.Invoking(x => x.Save())
            //add here more asserts, maybe asserting that the internal
            // state of your domain object was changed
            // your focus is to test the logic of the domain object
            .ShouldNotThrow();

        //auto mock example:
        var fix = new Fixture().Customize(new AutoMoqCustomization());
        var sut = fix.CreateAnonymous<MyCommand>();
        sut.myDomainRule = false;

        sut.Invoking(x => x.Save())
            .ShouldThrow<InvalidOperationException>();
    }

    public class MyFakeDataContextFake : IMyDataContextCalls
    {
        public void Save()
        {
            // do nothing, since you do not care in the logic of this method,
            // remember your goal is to test the domain object logic
        }

        public IEnumerable<Product> GetOrders()
        {
            // we do not care on this right now because we are testing only the save method

            throw new NotImplementedException();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 当你声明你的IMyDataContextCalls接口实际上是抽象使用a时DataContext,因此这个接口应该只包含POCO对象(大多数时候),如果你遵循这种方法,你的接口将与任何不需要的依赖关系分离.

  • 在具体MyDataContextCalls实现中,您明确使用了DataClasses1DataContext上下文,但您可以随时更改实现,这不会影响您的外部代码,这是因为您始终使用该IMyDataContextCalls接口.所以在任何时候你都可以使用精彩的NHibernate =)或者糟糕的ef或模拟的一个来改变另一个实现

  • 最后但并非不重要.请仔细检查我的代码,您会注意到域对象中没有new运算符.在编写测试友好代码时,这是一个愚蠢的规则:解除在域对象之外创建对象的责任


我个人在每个项目和我编写的每个测试中使用三个框架,我真的推荐它们:

例如,在上面的代码中,我向您展示了如何为您的存储库编写手动伪造,但这显然是我们不想在实际项目中执行的操作,想象您需要编写的对象数量以便写你的测试.

而是使用AutoFixture结合Moq的强大功能:

这一行: var m = new MyCommand(new MyFakeDataContextFake());

会变成:

        var fixture = new Fixture().Customize(new AutoMoqCustomization());
        var sut = fixture.CreateAnonymous<MyCommand>();
Run Code Online (Sandbox Code Playgroud)

就是这样,这段代码将自动为构造函数中所需的所有对象创建模拟MyCommand.