代码之家  ›  专栏  ›  技术社区  ›  Geo

如何在使用ADO.NET实体框架时测试业务逻辑(doman模型规则)?

  •  1
  • Geo  · 技术社区  · 15 年前

    我尝试测试不允许与用户共享同一空间两次的业务规则。下面是正在测试的方法。有问题的行标记在下面。

    public void ShareSpace(string spaceToShare,string emailToShareIt)
    {
      SharedSpace shareSpace = new SharedSpace();
      shareSpace.InvitationCode = Guid.NewGuid().ToString("N");
      shareSpace.DateSharedStarted = DateTime.Now;
      shareSpace.Expiration = DateTime.Now.AddYears(DefaultShareExpirationInYears);
      shareSpace.Active = true;
      shareSpace.SpaceName = spaceToShare;
      shareSpace.EmailAddress = emailToShareIt;
      if (!this.MySpacesShared.IsLoaded) 
         this.MySpacesShared.Load(); //Here I am getting the exception further below.
    
      if (this.MySpacesShared.Any(s => (s.EmailAddress == emailToShareIt) 
                                  & (s.SpaceName == spaceToShare)))
        throw new InvalidOperationException("Cannot share the a space with a user twice.");
      else
        this.MySpacesShared.Add(shareSpace);
    }
    

    测试方法如下:

    [TestMethod]
    public void Cannot_Share_SameSpace_with_same_userEmail_Twice()
    {
        account.ShareSpace("spaceName", "user1@domain.com");
        try
        {
              account.ShareSpace("spaceName", "user1@domain.com");
              Assert.Fail("Should throw exception when same space is shared with same user.");
        }
        catch (InvalidOperationException)
        { /* Expected */ }
        Assert.AreEqual(1, account.MySpacesShared.Count);
        Assert.AreSame(null, account.MySpacesShared.First().InvitedUser);
    }
    

    我在测试结果上得到的错误:

    试验方法 spacehelper.tests.controllers.spacecontrollertest.cannot_share_samespace_with_same_useremail_两次 引发异常: System.InvalidOperationException: 无法加载EntityCollection 因为它没有连接到 ObjectContext…

    当我一步一步地调试机制时,这个错误出现在load()事件上。我非常肯定这与我的测试场景中没有ADO.NET实体框架这一事实有关,因为我在这里使用的是假信息,并且没有连接到我的数据库。

    如果有人想看到,这里是我对该测试的初始化:

    [TestInitialize()]
     public void MyTestInitialize() 
     {
         user = new User()
         {
             Active = true,
             Name = "Main User",
             UserID = 1,
             EmailAddress = "user1@userdomain.com",
             OpenID = Guid.NewGuid().ToString()
         };
    
         account = new Account()
         {
             Key1 = "test1",
             Key2 = "test2",
             AccountName = "Brief Account Description",
             ID = 1,
             Owner = user
         };
     }
    
    3 回复  |  直到 15 年前
        1
  •  0
  •   NerdFury    15 年前

    我没有使用实体框架,我也不是100%确定我知道这里发生的事情的全部范围,但是您需要做的是在具有接口的实体框架代码周围放置一个包装器,然后使用模拟框架假装您在实际上没有调用数据库时调用数据库。我会给你一个大概的想法,但是你必须把它应用到实体框架中,因为我不知道细节。

    public interface IShareSpaceGateway {
      IEnumerable<ShareSpace> GetSharedSpaces(string spaceToShare, string emailToShareIt);
    }
    
    public class ShareSpaceGatewayEF: IShareSpaceGateway
    {
      // MySpacesShared should be included up here, not sure what type it is
      public IEnumerable<ShareSpace> GetSharedSpaces(string spaceToShare, string emailToShareIt)
      {
        if (!this.MySpacesShared.IsLoaded) 
         this.MySpacesShared.Load();
    
        return this.MySpacesShared.Any(s => (s.EmailAddress == emailToShareIt) 
                                  & (s.SpaceName == spaceToShare));
      }
    }
    

    您可以在isharedspacegateway中使用任何有意义的方法。其目的是减少代码重复。

    现在,您希望能够注入对IShareSpaceGateway的新依赖项。使用依赖注入的最佳方法是使用DI容器,如Castle Windsor、结构图、Ninject或Unity。我假设你的代码在这里是什么样子的:

    public class Account
    {
      private ISharedSpaceGateway _sharedSpaceGateway;
      public Account(ISharedSpaceGateway sharedSpaceGateway)
      {
        _sharedSpaceGateway = sharedSpaceGateway;
      }
    
      public int ID { get; set; }
      public string Key1 { get; set; }
      public string Key2 { get; set; }
      public string AccountName { get; set; }
    
      public void ShareSpace(string spaceToShare,string emailToShareIt)
      {
        SharedSpace shareSpace = new SharedSpace();
        shareSpace.InvitationCode = Guid.NewGuid().ToString("N");
        shareSpace.DateSharedStarted = DateTime.Now;
        shareSpace.Expiration = DateTime.Now.AddYears(DefaultShareExpirationInYears);
        shareSpace.Active = true;
        shareSpace.SpaceName = spaceToShare;
        shareSpace.EmailAddress = emailToShareIt;
        var sharedSpaces = sharedSpaceGateway.GetSharedSpaces(spaceToShare, emailToShareIt);
        if(sharedSpaces.Count() > 0)    
          throw new InvalidOperationException("Cannot share the a space with a user twice.");
    
        this.MySpacesShared.Add(shareSpace);
      }
    }
    

    现在,在单元测试中,您希望使用模拟框架(如moq)或Rhinomocks来设置测试。在您的测试中,您不想使用sharedspacegateway的实际实现,而是希望传入一个假的实现。此示例使用犀牛模型

    public class TestFixture{
    
    
    private ISharedSpaceGateway gateway;
    [TestInitialize()]
     public void MyTestInitialize() 
     {
        gateway = MockRepository.CreateMock<ISharedSpaceGateway>();
        gateway.Expect(g => g.GetSharedSpaces("spaceName", "user1@domain.com"))
              .Return(new SharedSpace()); // whatever you want to return from the fake call
    
             user = new User()
             {
                     Active = true,
                     Name = "Main User",
                     UserID = 1,
                     EmailAddress = "user1@userdomain.com",
                     OpenID = Guid.NewGuid().ToString()
             };
    
             account = new Account(gateway) // inject the fake object
             {
                     Key1 = "test1",
                     Key2 = "test2",
                     AccountName = "Brief Account Description",
                     ID = 1,
                     Owner = user
             };
     }
    
    [TestMethod]
    public void Cannot_Share_SameSpace_with_same_userEmail_Twice()
    {
            account.ShareSpace("spaceName", "user1@domain.com");
            try
            {
              account.ShareSpace("spaceName", "user1@domain.com");
              Assert.Fail("Should throw exception when same space is shared with same user.");
            }
            catch (InvalidOperationException)
            { /* Expected */ }
            Assert.AreEqual(1, account.MySpacesShared.Count);
            Assert.AreSame(null, account.MySpacesShared.First().InvitedUser);
            gateway.VerifyAllExpectations();
    }
    

    使用DI框架和模拟框架涉及很多,但是这些概念使您的代码更易于测试。

        2
  •  0
  •   Anton Gogolev    15 年前

    从我个人的经验来看,Linq to SQL在编写单元测试方面非常糟糕。

    BL层与LINQ to SQL类非常耦合,因为它知道以下概念 .IsLoaded 可以 .Load() 集合等。将此逻辑移动到 ISharedSpacesPersistenceService 重写方法如下:

    // Dependency-Inject this
    public ISharedSpacesPersistenceService SharedSpacesPersistenceService { get; set; }
    
    public void ShareSpace(string spaceToShare,string emailToShareIt)
    {
        SharedSpace shareSpace = new SharedSpace();
        shareSpace.InvitationCode = Guid.NewGuid().ToString("N");
        shareSpace.DateSharedStarted = DateTime.Now;
        shareSpace.Expiration = DateTime.Now.AddYears(DefaultShareExpirationInYears);
        shareSpace.Active = true;
        shareSpace.SpaceName = spaceToShare;
        shareSpace.EmailAddress = emailToShareIt;
    
        if(SharedSpacesPersistenceService.ContainsSpace(s.EmailAddress, spaceToShare)
            throw new InvalidOperationException("Cannot share the a space with a user twice.");     
    
        this.MySpacesShared.Add(shareSpace);
    }
    

    只是一个小问题:更换 DateTime.Now.AddYears(DefaultShareExpirationInYears) 具有 DateTime.Now.Add(DefaultShareExpiration) 并设置 DefaultShareExpiration 类型到 TimeSpan . 这样会更好。

        3
  •  0
  •   J.W.    15 年前

    更好的方法是,您不应该接触由域层中的实体框架生成的类。相反,您应该创建自己的业务层,并使用Linq将生成的类投影到业务对象中。

    这样,您可以以更简单的方式设置测试。