我正在尝试测试不允许与用户共享相同空间两次的业务规则。以下是正在测试的方法。有问题的行标在下面。
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
};
}
答案 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框架和模拟框架涉及很多,但这些概念使您的代码更易于测试。