简体   繁体   English

没有数据库的单元测试:Linq to SQL

[英]Unit Testing without Database: Linq to SQL

I have a repository implemented using LINQ to SQL. 我有一个使用LINQ to SQL实现的存储库。 I need to do Unit Testing though I don't have a database. 我没有数据库,但我需要进行单元测试。 How can I write the UT for FreezeAllAccountsForUser method? 如何为FreezeAllAccountsForUser方法编写UT? Can you please show an example using manually mocking? 您能否显示一个使用手动模拟的示例?

Note: There is inheritance mapping used in domain objects 注意:域对象中使用了继承映射

Note: Unit Testing is to be done using Visual Studio Team Test 注意:单元测试将使用Visual Studio Team Test完成

Comment from @StuperUser. @StuperUser的评论。 Unit testing involves completely isolating code from the other objects it interacts with. 单元测试涉及将代码与与其交互的其他对象完全隔离。 This means that if the code fails, you can be sure that the failure is to do with the code under test. 这意味着,如果代码失败,则可以确定失败与被测试代码有关。 To do this you have to fake these objects. 为此,您必须伪造这些对象。

CODE

     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();

    }

Repository Layer 存储库层

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();
    }

}

}

Domain Classes 域类

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

Auto generated Class by LINQ to SQL 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

Simply, you can't. 简单地说,你做不到。 The sole purpose of the repository implementation is talking to the database. 存储库实现的唯一目的是与数据库对话。 So the database technology does matter and you should perform integration tests. 因此,数据库技术确实很重要,您应该执行集成测试。

Unit testing this code is impossible because LINQ to Objects is a superset of LINQ to SQL. 由于LINQ to Objects是LINQ to SQL的超集,因此无法对该代码进行单元测试。 You might have a green unit test and still get the runtime exception when using real db because you used a feature of LINQ in your repository that cannot be translated into SQL. 您可能有一个绿色的单元测试,并且在使用实际数据库时仍然会遇到运行时异常,因为您在存储库中使用了LINQ的功能,该功能无法转换为SQL。

The Repository responsibility is to persist domain objects and fetch them on request. 存储库的责任是保留域对象,并根据请求获取它们。 ie it's job is to take an object and deserialize/serialize it to from some form of durable storage. 也就是说,它的工作是从某个持久存储中获取一个对象并将其反序列化/序列化为对象。

So tests for the repository have to test against the real storage in this case a DB. 因此,对于存储库的测试必须针对实际存储(在这种情况下为DB)进行测试。 ie these are integration tests - tests that your class integrates with the external DB. 也就是说,这些是集成测试-您的类与外部DB集成的测试。

Once you have this nailed, the rest of the client/app doesn't have to work against the real DB. 一旦确定了这一点,其余的客户端/应用程序就不必针对实际的数据库工作。 They can mock the repository and have fast unit tests. 他们可以模拟存储库并进行快速的单元测试。 You can assume that GetAccount works since the integration tests pass. 您可以假定GetAccount在集成测试通过后就可以使用了。

More details: By passing in the Repository object as a ctor or method arg, you open the doors for passing in a fake or a mock. 更多详细信息:通过将Repository对象作为ctor或方法arg传递,您可以打开传递伪造或模拟的大门。 Thus now the service tests can run without a real repository >> there is no DB-access >> fast tests. 因此,现在服务测试可以在没有真实存储库的情况下运行>>没有DB访问>>快速测试。

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());
}

You can by using the IDbSet interface in your datacontext and extracting an interface for your datacontext class. 您可以通过在数据上下文中使用IDbSet接口并为数据上下文类提取接口来实现。 Programming towards interfaces is the key to creating unit testable code. 对接口进行编程是创建单元可测试代码的关键。

The reason that you would want to create unit tests for these linq queries is to have a unit test for the logical query. 您要为这些linq查询创建单元测试的原因是要对逻辑查询进行单元测试。 Integration tests are subject to all kinds of false negatives. 集成测试易受各种假阴性的影响。 The db not being in the right state, other queries running at the same time, other integration tests, etc. It is very hard to isolate a db well enough for a reliable integration test. db处于不正确的状态,其他查询同时运行,其他集成测试,等等。很难很好地隔离数据库以进行可靠的集成测试。 That is why integration tests are so often ignored. 这就是为什么集成测试经常被忽略的原因。 If i have to pick one, i want the unit test... 如果我必须选一个,我要单元测试...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM