【问题标题】:EF Moq Unit Test, unsure verifyEF Moq 单元测试,不确定验证
【发布时间】:2017-05-24 17:01:48
【问题描述】:

我对单元测试完全陌生。我阅读了许多来自不同人的“教程”,我决定使用 msdn 解决方案。

我使用这个https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx 作为我的测试,我对那里的“测试非查询场景”感兴趣。

根据这篇文章,我尝试测试我的简单 CRUD 的 Create() 操作。

这是我的代码(FinancialAssistantEntities 是我的 DbContext(EF 数据库优先)):

上下文:

public partial class FinancialAssistantEntities : DbContext
{
    public FinancialAssistantEntities()
        : base("name=FinancialAssistantEntities")
    {
    }
    .
    .
    .
    public virtual DbSet<FAWallet> FAWallet { get; set; }
}

存储库方法:(我注释掉了我的事务的 using,因为从测试方法运行它会导致错误“No connection string named 'FinancialAssistantEntities' could be found in the应用程序配置文件。”),

public async Task<bool> CreateWallet(FAWallet model)
{
    using (var context = Context)
    {
        // transaction with IsolationLevel
        //using (var tran = context.Database.BeginTransaction(IsolationLevel.ReadUncommitted))
        {
            try
            {                       
                context.FAWallet.Add(model);
                //context.SaveChanges();
                await context.SaveChangesAsync();
                //tran.Commit();
                return true;
            }
            catch (Exception ex)
            {
                //tran.Rollback();
                throw ex;
            }
        }
    }
}

测试方法:

[TestMethod]
public void CreateWalletTest()
{
    var wallet = new FAWallet()
    {
        WalletId = Guid.NewGuid(),
        //WalletName = StringHelper.GenerateRandomString(12),
        // admin ID
        WalletUserId = "e6888245-1d9b-431c-a068-aa62932e47ec",
        WalletCreateDate = DateTime.Now,
        WalletEnabled = true
    };

    var mockSet = new Mock<DbSet<FAWallet>>();

    var mockContext = new Mock<FinancialAssistantEntities>();
    mockContext.Setup(x => x.FAWallet).Returns(mockSet.Object);

    var walletRepository = new FAWalletRepository(mockContext.Object);
    walletRepository.CreateWallet(wallet).Wait();

    mockSet.Verify(x => x.Add(It.IsAny<FAWallet>()), Times.Once());
    mockContext.Verify(x => x.SaveChangesAsync(), Times.Once()); 
}

首先,我不知道评论交易的使用是否是个好主意,虽然我对测试还不太了解。

其次,我的测试总是通过。我什至注释掉了 WalletName 属性的集合,因为该字段不可为空,所以似乎我做错了什么。

【问题讨论】:

  • 您基本上是在尝试测试 EF 是否在执行其设计的任务。不要那样做。微软会在发布它以供使用之前对其进行完全测试。您可以考虑抽象存储库对上下文的依赖。
  • 这个特殊的模拟框架适用于虚拟方法,这就是为什么您无法模拟代码的事务部分的原因。这是一个实现问题,可以抽象出来。测试通过,因为根据测试的设置,它将按照配置执行。
  • Nkosi:我在其他主题下阅读过与您类似的答案,但我的意思是测试我的错误,例如。在该系统自动填充其他字段之后,我有一些对象仅由用户部分填充,例如。创建日期、创建用户等。当我错过填写其中一些字段时,SaveChanges() 会给我一个错误。我不能把它放在 ModelState 验证中,因为这个数据既不是由用户填充也不是作为隐藏值传递的,它是在一般验证之后稍后填充的。当我添加新的非空字段而不填写它时,那种测试会告诉我我忘了填写它。
  • 不要测试实体框架。微软已经这样做了。测试您自己的代码是否符合预期。

标签: c# entity-framework unit-testing moq


【解决方案1】:

前言

在我们开始检查您的问题的点点滴滴之前,让我明确一点,主要问题不是单元测试。相反,它是关于Object Oriented Programming 和一些分析。

分析问题空间

让我们看看你在评论中写了什么:

我在其他主题下读过与您类似的答案,但我的意思是 例如,测试我的错误。我有一些用户填充的对象 只是部分地,在该系统自动填写其他字段之后, 例如。创建日期,创建用户等。当我错过填写一些 这些字段,SaveChanges() 会给我一个错误。

你是从错误的角度来完成这项任务的。

我为什么这么说?因为:

  1. 您正在使用实体框架 ORM (EF) persistence model 作为行为的来源,该模型负责给定的业务交互

  2. 您希望 EF 进行这种验证

  3. 您想通过 EF 机制测试这一切。你在测试错误的东西

解决问题

您真正想做的是将您的模型与 EF 的核心联系起来。这不好,因为:

  • 您将代码与 EF 紧密耦合,具有不必要的依赖关系
  • 它使测试业务逻辑变得困难而缓慢
  • 业务逻辑是代码中最重要和最有价值的部分之一,您(在理想情况下)最终会得到报酬

现在让我们详细关注上面的前三点。

首先:我强烈建议您创建一个尽可能少依赖的对象。为了示例,我们称它为 entity,它将包含所有需要的 behavior 封装。就像你提到的那样;具有设置属性和其他不变量的公共方法。

第二:您还可以拥有保护此类型及其所有不变量所需的所有验证。这种验证的常见位置可以是构造函数或任何接受和验证它接收的参数的公共方法。如果出现错误,您只需抛出自定义业务异常并在测试中针对这些异常进行断言。

这些都组合成一个对象,称为; cohesion.

第三:既然您有一个更清晰的对象,它可以对给定的业务交互进行建模,您现在只需要完全隔离地测试此代码。这是一件好事,因为它速度快、重点突出并且不会加载大量依赖项(与使用 EF 的集成测试相比)。


一切都好,结局好

当然,就像所有事情一样,这一切都是有代价的。那就是当你将某些东西从系统中分离出来时,你可能会引入另一层间接。这就是您现在需要将“域模型”映射到 EF 持久性模型,反之亦然。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-17
    • 1970-01-01
    • 1970-01-01
    • 2015-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多