针对 Entity Framework InMemory 进行测试
Testing against Entity Framework InMemory
我目前正在使用内存数据库测试Entity Framework的DbContext
。
为了使测试尽可能原子化,DbContext
每个测试方法都是唯一的,并且填充了每个测试所需的初始数据。
为了设置 DbContext
的初始状态,我创建了一个 void SetupData
方法,用我将在测试中使用的一些实体填充上下文。
这种方法的问题是测试无法访问在设置期间创建的对象,因为 Entity Framework 将分配 ID 本身,直到 运行 时间才知道.
为了克服这个问题,我认为我的 SetupData
方法可以变成这样:
public Fixture SetupData(MyContext context)
{
var fixture = new Fixture();
fixture.CreatedUser = new User();
context.Users.Add(fixture.CreatedUser);
context.SaveChanges();
return fixture;
}
public class Fixture
{
public User CreatedUser { get; set;}
}
如您所见,它是 return 一个我称之为 "Fixture" 的实例。 (不知道这个名字适不适合).
这样,SetupData
将 return 对象 (Fixture
) 引用实体 。因此,测试可以使用创建的对象。否则,对象将无法识别,因为在调用 SaveChanges
之前不会创建 Id。
我的问题是:
- 这是一种不好的做法吗?
- 有没有更好的方式来引用initial
数据?
我更喜欢这种方法:
public void SetupData(MyContext context)
{
var user = new User() { Id = Fixture.TEST_USER1_ID, UserName = Fixture.TEST_USER1_NAME };
context.Users.Add(user);
context.SaveChanges();
}
public class Fixture
{
public const int TEST_USER1_ID = 123;
public const string TEST_USER!_NAME = "testuser";
}
您的方法可能也不错,但您可能想知道测试中某处的用户 ID,这使得在单个已知位置指定它变得非常容易,这样它就不会改变例如,您稍后更改测试数据和添加用户的顺序。
这不是一个坏习惯。事实上,这是创建可读的 Given-When-Then 测试的好方法。如果您考虑:
- 拆分您的
SetupData
方法
- 重命名
- 可能会更改为扩展方法
public static MyContextExtensions
{
public static User Given(this MyContext @this, User user)
{
@this.Users.Add(user);
@this.SaveChanges();
return user;
}
public static OtherEntity Given(this MyContext @this, OtherEntity otherEntity)
{
// ...
}
// ...
}
然后你可以写(一个概念性的例子,细节需要修改以匹配你的实现):
[Test]
public GivenAUser_WhenSearchingById_ReturnsTheUser()
{
var expectedUsername = "username";
var user = _context.Given(AUser.WithName(expectedUsername));
var result = _repository.GetUser(user.Id);
Assert.That(result.Name, Is.EqualTo(expectedUsername));
}
...其他实体也是如此。
我目前正在使用内存数据库测试Entity Framework的DbContext
。
为了使测试尽可能原子化,DbContext
每个测试方法都是唯一的,并且填充了每个测试所需的初始数据。
为了设置 DbContext
的初始状态,我创建了一个 void SetupData
方法,用我将在测试中使用的一些实体填充上下文。
这种方法的问题是测试无法访问在设置期间创建的对象,因为 Entity Framework 将分配 ID 本身,直到 运行 时间才知道.
为了克服这个问题,我认为我的 SetupData
方法可以变成这样:
public Fixture SetupData(MyContext context)
{
var fixture = new Fixture();
fixture.CreatedUser = new User();
context.Users.Add(fixture.CreatedUser);
context.SaveChanges();
return fixture;
}
public class Fixture
{
public User CreatedUser { get; set;}
}
如您所见,它是 return 一个我称之为 "Fixture" 的实例。 (不知道这个名字适不适合).
这样,SetupData
将 return 对象 (Fixture
) 引用实体 。因此,测试可以使用创建的对象。否则,对象将无法识别,因为在调用 SaveChanges
之前不会创建 Id。
我的问题是:
- 这是一种不好的做法吗?
- 有没有更好的方式来引用initial 数据?
我更喜欢这种方法:
public void SetupData(MyContext context)
{
var user = new User() { Id = Fixture.TEST_USER1_ID, UserName = Fixture.TEST_USER1_NAME };
context.Users.Add(user);
context.SaveChanges();
}
public class Fixture
{
public const int TEST_USER1_ID = 123;
public const string TEST_USER!_NAME = "testuser";
}
您的方法可能也不错,但您可能想知道测试中某处的用户 ID,这使得在单个已知位置指定它变得非常容易,这样它就不会改变例如,您稍后更改测试数据和添加用户的顺序。
这不是一个坏习惯。事实上,这是创建可读的 Given-When-Then 测试的好方法。如果您考虑:
- 拆分您的
SetupData
方法 - 重命名
- 可能会更改为扩展方法
public static MyContextExtensions
{
public static User Given(this MyContext @this, User user)
{
@this.Users.Add(user);
@this.SaveChanges();
return user;
}
public static OtherEntity Given(this MyContext @this, OtherEntity otherEntity)
{
// ...
}
// ...
}
然后你可以写(一个概念性的例子,细节需要修改以匹配你的实现):
[Test]
public GivenAUser_WhenSearchingById_ReturnsTheUser()
{
var expectedUsername = "username";
var user = _context.Given(AUser.WithName(expectedUsername));
var result = _repository.GetUser(user.Id);
Assert.That(result.Name, Is.EqualTo(expectedUsername));
}
...其他实体也是如此。