模拟Linq2Sql DataContext

时间:2012-08-01 11:36:54

标签: c# unit-testing c#-4.0 linq-to-sql mocking

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

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

非常感谢任何有关此事的帮助。

3 个答案:

答案 0 :(得分: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.
    }
}

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

答案 1 :(得分: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();
        }
    }
}

注意:

  • 当您声明IMyDataContextCalls接口时,实际上是在抽象使用DataContext,因此如果您遵循此方法,此接口应仅包含POCO对象(大多数情况下)您的接口将与任何不受欢迎的依赖关系分离。

  • 在具体的MyDataContextCalls实施中,您明确使用DataClasses1DataContext上下文,但您可以随时更改实施,不会影响您的外部代码,这是因为您总是使用IMyDataContextCalls界面。因此,在任何时候你都可以使用精彩的 NHibernate =)或者糟糕的ef或模拟的

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


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

例如,在上面的代码中,我向您展示了如何为您的存储库编写手册 fake ,但这显然是我们在实际项目中不想做的事情,想象一下这个数字为编写测试而必须编写的对象。

而是使用AutoFixture结合Moq的力量:

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

将成为:

        var fixture = new Fixture().Customize(new AutoMoqCustomization());
        var sut = fixture.CreateAnonymous<MyCommand>();

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

答案 2 :(得分:4)

简而言之,您不要模拟DataContext。您从中提取接口并使用实体集的某些集合模拟该接口,然后验证这些集合的内容。