【问题标题】:Unit Testing without Database: Linq to SQL没有数据库的单元测试:Linq to SQL
【发布时间】:2012-07-05 16:32:54
【问题描述】:

我有一个使用 LINQ to SQL 实现的存储库。尽管我没有数据库,但我需要进行单元测试。如何为 FreezeAllAccountsForUser 方法编写 UT?你能举一个使用手动模拟的例子吗?

注意:域对象中使用了继承映射

注意:单元测试将使用 Visual Studio Team Test 完成

来自@StuperUser 的评论。单元测试涉及将代码与其交互的其他对象完全隔离开来。这意味着如果代码失败,您可以确定失败与被测代码有关。为此,您必须伪造这些对象。

代码

     public void FreezeAllAccountsForUser(int userId)
    {
        List<DTOLayer.BankAccountDTOForStatus> bankAccountDTOList = new List<DTOLayer.BankAccountDTOForStatus>(); 

        IEnumerable<DBML_Project.BankAccount> accounts = AccountRepository.GetAllAccountsForUser(userId);
        foreach (DBML_Project.BankAccount acc in accounts)
        {
            string typeResult = Convert.ToString(acc.GetType());
            string baseValue = Convert.ToString(typeof(DBML_Project.BankAccount));

            if (String.Equals(typeResult, baseValue))
            {
                throw new Exception("Not correct derived type");
            }

            acc.Freeze();

            DTOLayer.BankAccountDTOForStatus presentAccount = new DTOLayer.BankAccountDTOForStatus();
            presentAccount.BankAccountID = acc.BankAccountID;
            presentAccount.Status = acc.Status;
            bankAccountDTOList.Add(presentAccount);

        }



        IEnumerable<System.Xml.Linq.XElement> el = bankAccountDTOList.Select(x =>
                        new System.Xml.Linq.XElement("BankAccountDTOForStatus",
                          new System.Xml.Linq.XElement("BankAccountID", x.BankAccountID),
                          new System.Xml.Linq.XElement("Status", x.Status)
                        ));

        System.Xml.Linq.XElement root = new System.Xml.Linq.XElement("root", el);


        //AccountRepository.UpdateBankAccountUsingParseXML_SP(root);
        AccountRepository.Update();

    }

存储层

namespace RepositoryLayer
{
public interface ILijosBankRepository
{
    System.Data.Linq.DataContext Context { get; set; }
    List<DBML_Project.BankAccount> GetAllAccountsForUser(int userID);
    void Update();

}

public class LijosSimpleBankRepository : ILijosBankRepository
{
    public System.Data.Linq.DataContext Context
    {
        get;
        set;
    }


    public List<DBML_Project.BankAccount> GetAllAccountsForUser(int userID)
    {
        IQueryable<DBML_Project.BankAccount> queryResultEntities = Context.GetTable<DBML_Project.BankAccount>().Where(p => p.AccountOwnerID == userID);
        return queryResultEntities.ToList();
    }


    public virtual void Update()
    {
        //Context.SubmitChanges();
    }

}

}

领域类

namespace DBML_Project
{

public  partial class BankAccount
{
    //Define the domain behaviors
    public virtual void Freeze()
    {
        //Do nothing
    }
}

public class FixedBankAccount : BankAccount
{

    public override void Freeze()
    {
        this.Status = "FrozenFA";
    }
}

public class SavingsBankAccount : BankAccount
{

    public override void Freeze()
    {
        this.Status = "FrozenSB";
    }
}  
}

LINQ to SQL 自动生成的类

[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.BankAccount")]
[InheritanceMapping(Code = "Fixed", Type = typeof(FixedBankAccount), IsDefault = true)]
[InheritanceMapping(Code = "Savings", Type = typeof(SavingsBankAccount))]
public partial class BankAccount : INotifyPropertyChanging, INotifyPropertyChanged

【问题讨论】:

    标签: c# unit-testing linq-to-sql tdd domain-driven-design


    【解决方案1】:

    简单地说,你不能。存储库实现的唯一目的是与数据库通信。因此,数据库技术确实很重要,您应该执行集成测试。

    无法对该代码进行单元测试,因为 LINQ to Objects 是 LINQ to SQL 的超集。您可能有一个绿色单元测试,但在使用 real db 时仍然会出现运行时异常,因为您在存储库中使用了无法转换为 SQL 的 LINQ 功能。

    【讨论】:

    • 但是下面的帖子说的是这种方法。你怎么看? stackoverflow.com/questions/7076726/…另见stackoverflow.com/questions/4649786/…
    • 想象一个简单的例子:您在实体中创建了一个自定义属性,该属性未映射到表格单元格,但以某种方式计算。在 where 子句中使用该属性的存储库方法将通过单元测试并在集成测试中失败。
    • 编写会产生误报的单元测试有什么意义?
    • 您可以创建一个存储库的模拟,它返回内存对象列表并验证更新命令是否接收到正确的参数值。
    • 查看最小起订量、Rhino Mocks、FakeItEasy。你总是可以手动创建一个模拟。这些模拟框架使任务变得不那么冗长。
    【解决方案2】:

    存储库的职责是持久化域对象并根据请求获取它们。即它的工作是获取一个对象并将其反序列化/序列化为某种形式的持久存储。

    因此,存储库的测试必须针对真实存储进行测试,在这种情况下是数据库。即这些是集成测试 - 测试您的类与外部数据库的集成。

    一旦确定了这一点,客户端/应用程序的其余部分就不必针对真实的数据库工作。他们可以模拟存储库并进行快速的单元测试。您可以假设 GetAccount 在集成测试通过后可以正常工作。

    更多详情: 通过将 Repository 对象作为 ctor 或方法 arg 传递,您打开了传递假或模拟的大门。因此,现在服务测试可以在没有真正的存储库的情况下运行 >> 没有数据库访问 >> 快速测试。

    public void FreezeAllAccountsForUser(int userId, ILijosBankRepository accountRepository)
    {
      // your code as before
    }
    
    test ()
    {  var mockRepository = new Mock<ILijosBankRepository>();
        var service = // create object containing FreezeAllAccounts...
    
        service.FreezeAllAccounts(SOME_USER_ID, mockRepository);
    
        mock.Verify(r => r.GetAllAccountsForUser(SOME_USER_ID);
        mock.Verify(r => r.Update());
    }
    

    【讨论】:

    • 谢谢。 “他们可以模拟存储库并进行快速的单元测试。”你能解释一下这些单元测试的样子吗?
    • 您能否解释一下如何测试不是存储库方法的 FreezeAllAccountsForUser 方法?
    • 谢谢。用的是哪个mock工具?
    • 工具本身无所谓..大部分都差不多。我认为我使用的语法是 Moq-ish.. 可能无法编译。
    【解决方案3】:

    您可以通过在数据上下文中使用 IDbSet 接口并为数据上下文类提取接口。面向接口编程是创建可单元测试代码的关键。

    您希望为这些 linq 查询创建单元测试的原因是对逻辑查询进行单元测试。集成测试会受到各种假阴性的影响。数据库未处于正确状态,同时运行其他查询,其他集成测试等。很难将数据库隔离到足以进行可靠集成测试的程度。这就是为什么集成测试经常被忽略的原因。如果我必须选择一个,我想要单元测试......

    【讨论】:

      猜你喜欢
      • 2010-12-15
      • 2010-11-26
      • 1970-01-01
      • 2011-08-20
      • 1970-01-01
      • 2014-05-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多