使用EF的单元测试数据库

时间:2019-01-11 20:17:06

标签: c# database winforms entity-framework unit-testing

一个单元如何测试连接到数据库的服务?

我在数据访问层有一个playerRepository类,它直接与数据库交互;在业务层有一个playerService类,它创建一个playerRepository的实例,并为诸如-删除玩家,保存玩家,获取所有玩家,按ID /名称yadda yadda获取玩家。

我想对playerService进行单元测试而不使用真实数据库,而是使用EF随附的内存数据库。

问题是,我在弄清楚如何设置它时遇到了麻烦。

我有一个PlayerContext : DbContext类,该类用作代码优先的模型(通过EF教程完成了此部分)。而且我必须向构造函数DbContextOptionsBuilder<PlayerContext>添加一个参数,但是我不知道从哪里开始。我不知道如何和在哪里设置连接字符串,“默认”数据库将在哪里存储自身。

我想通过使用不带NSubstitute或Moq的EF来做到这一点,我这样做是为了了解如何在不使用EF之外的其他框架的情况下完成它。

public class PlayerContext : DbContext
{
    public PlayerContext() : base()
    {

    }

    public DbSet<Player> Players { get; set; }
}

public class Player
{
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int DciNumber { get; set; }
}

我正在使用Visual Studio提供的单元测试

[TestClass]
public class PlayerServiceTest
{
    // Should get all the players from database
    [TestMethod]
    public void GetAllPlayers()
    {
        // arrange - act
        // TODO: Return all the players from the database

        // assert
        // TODO: Should return empty list
    }

PlayerService类看起来像这样

public class PlayerService
{
    private PlayerRepository _playerRepository = new PlayerRepository();


    public List<Player> GetAllPlayers()
    {
        var players = _playerRepository.GetAllPlayers();

        return players;
    }

PlayerRepository

public class PlayerRepository
{
    public List<Player> GetAllPlayers()
    {
        using (var context = new PlayerContext())
        {
            var players = context.Players.ToList();

            return players;
        }
    }

通常我的问题是:

  1. 在进行单元测试的情况下,如何使PlayerContext带有另一个连接到内存数据库的连接字符串,以及如何在不通过单元测试运行时为它提供正确的连接字符串

  2. 如何更改数据库的位置,因为它使用C:\Users中的默认路径。

我不是在寻找与DAL的PlayerRepository进行集成测试,我只想测试业务层,而我所需要的只是,当我运行PlayerService使用连接到其中的PlayerRepository的测试时-内存数据库就是这样。否则,它应该连接到与应用程序主exe所在文件夹中存储的本地数据库

需要帮助!

1 个答案:

答案 0 :(得分:3)

缺少的部分是依赖注入/ IoC。此处的原理是使用可以模拟的协定接口定义依赖项(存储库)。将该依赖项注入依赖它的类中。这样,您可以用模拟对象(这些对象返回已知状态,引发预期的异常等)来替代数据库,文件处理等具体实现,以测试您的业务逻辑对这些情况的处理。

public class PlayerService
{
    private readonly IPlayerRepository _playerRepository = null;

    public PlayerService(IPlayerRepository playerRepository)
    {
         _playerRepository = playerRepository ?? throw new ArgumentNullException("playerRepository");
    }


    public List<Player> GetAllPlayers()
    {
        var players = _playerRepository.GetAllPlayers();

        return players;
    }
}

查看一下IoC容器,例如Autofac,Unity,Ninject等,以获取有关如何构建容器以在构造它们时自动识别具体类实例并将其注入您的服务的示例。

当您去编写单元测试时,您将创建IPlayerRepository类的模拟(例如:Moq),并将其传递给被测服务。即:

[Test]
public void TestService()
{
   var mockRepository = new Mock<IPlayerRepository>();
   mockRepository.Setup(x => x.GetPlayers()).Returns(buildTestPlayerList());
   var serviceUnderTest = new PlayerService(mockRepository.Object);
   // continue with test...
}

在最坏的情况下:如果您要放弃该容器,那么它也可以正常工作:

public class PlayerService
{
    private IPlayerRepository _playerRepository = null;
    public IPlayerRepository PlayerRepository
    {
        get { return _playerRepository ?? (_playerRepository = new PlayerRepository()); }
        set { _playerRepository = value; }
    }

    // ...
}

...然后进行测试...

[Test]
public void TestService()
{
   var mockRepository = new Mock<IPlayerRepository>();
   mockRepository.Setup(x => x.GetPlayers()).Returns(buildTestPlayerList());
   var serviceUnderTest = new PlayerService { PlayerRepository = mockRepository.Object };
   // continue with test...
}

这是我称为“惰性属性注入”的模式,您可以在其中选择发送依赖关系,但是默认情况下,它只会创建默认的依赖关系。当在旧代码中引入依赖替换和单元测试时,这可能是一个有用的模式,而旧代码很大程度上依赖于更新中间代码类。从某种意义上讲,这是懒惰的,因为依赖项只是在第一次访问时才“更新”。如果您在服务中调用不需要依赖关系的方法,则不会初始化每个依赖关系,只有使用的依赖关系才会初始化。我强烈建议您阅读IoC容器,因为它们有助于自动化依赖关系的接线。

相关问题