繁体   English   中英

使用EF的单元测试数据库

[英]Unit testing database using EF

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

我在数据访问层中有一个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所在文件夹中存储的本地数据库

需要帮助!

缺少的部分是依赖注入/ 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容器,因为它们可以帮助自动关联依赖关系。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM