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

时间:2009-10-08 12:50:21

标签: c# asp.net-mvc unit-testing entity-framework tdd

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

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:

[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_Twice   抛出异常:   System.InvalidOperationException:The   无法加载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 个答案:

答案 0 :(得分:0)

从我个人的经验来看,LINQ to SQL在编写单元测试时会花费大量时间。

你的BL层非常适合LINQ to SQL类,因为它知道诸如.IsLoaded,can .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。这会好得多。

答案 1 :(得分:0)

更好的方法是不要触及Domain层中Entity框架生成的类。相反,您应该创建自己的业务层,并使用LINQ将生成的类投影到业务对象。

这样,您可以更轻松地设置测试。

答案 2 :(得分:0)

我没有使用实体框架,我并不是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上注入新的依赖项。使用依赖注入的最佳方法是使用像Castle Windsor,Structure Map,Ninject或Unity这样的DI容器。我假设你的代码在这里看起来像什么:

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的真实实现,您想要传递一个假的。此示例使用RhinoMocks

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框架和模拟框架涉及很多,但这些概念使您的代码更易于测试。