关于这个Moq测试用例的实用性的建议

时间:2014-07-02 17:01:37

标签: c# unit-testing moq

我的情况是,我被告知我的工作是为以下代码编写单元测试。就是这样 - 使用所有模拟测试方法。周期。

我担心这是写逆逻辑的练习。此外,如果此代码中存在一个微妙的错误,我将为该错误编写测试,不是吗?

public EntityLogin GetAccount(string email)
{
    EntityLogin account = null;
    try
    {
        var accounts = _appointmentRepository.GetEntityLogin(_clientContext.selectedSiteId, email);
        if (accounts != null && accounts.Any())
        {
            account = accounts.OrderByDescending(x => x.dtInsertDate).FirstOrDefault();
        }
    }
    catch (Exception ex)
    {
        Logger.ErrorFormat("Error in method '{0}': {1}", MethodBase.GetCurrentMethod().Name, ex.GetFullMessage());
        throw;
    }
    return account;
}

这是我提出的测试。

    [TestMethod]
    public void GetAccountTest()
    {
        // Arrange
        const string email1 = "a@b.com";
        const string email2 = "c@d.com";
        const string email3 = "e@f.com";
        const string email4 = "g@h.com";

        var expected1 = new EntityLogin {iEntityLoginId = 1234, dtInsertDate = DateTime.Today.AddDays(-10)};
        var expected2 = new EntityLogin {iEntityLoginId = 5678, dtInsertDate = DateTime.Today.AddDays(-5)};

        _mockAppointmentRepository
            .Setup(c => c.GetEntityLogin(_siteId, email1))
            .Returns(new[] {expected1})
            .Verifiable();

        _mockAppointmentRepository
            .Setup(c => c.GetEntityLogin(_siteId, email2))
            .Returns((EntityLogin[])null)
            .Verifiable();

        _mockAppointmentRepository
            .Setup(c => c.GetEntityLogin(_siteId, email3))
            .Returns(new EntityLogin[] {})
            .Verifiable();

        _mockAppointmentRepository
            .Setup(c => c.GetEntityLogin(_siteId, email4))
            .Returns(new[] {expected1, expected2})
            .Verifiable();

        var target = GetAppointmentService();

        // Act
        var actual1 = target.GetAccount(email1);
        var actual2 = target.GetAccount(email2);
        var actual3 = target.GetAccount(email3);
        var actual4 = target.GetAccount(email4);

        // Assert
        Assert.AreEqual(expected1, actual1); // returns single result
        Assert.IsNull(actual2); // returns null on null result
        Assert.IsNull(actual3); // returns null on empty collection result
        Assert.AreEqual(actual4, expected2); // sorts desc by insert date on multiple results
        _mockAppointmentRepository.Verify();
    }

我的问题是,我在这里做得好吗?

3 个答案:

答案 0 :(得分:1)

为什么在一个单元测试中测试几个案例?将它拆分为四个单元测试:

[TestMethod]
public void GetAccount_WhenEmailIsNull_ReturnsNull()
{ .. test that is responsible for testing is email null } 

[TestMethod]
public void GetAccount_WhenEmailIsValid_ReturnsAccountsSortedByInsertDate() { .. }
// I'm not sure is "SortedByInsertDate" test should be splitted to another test though

等。当你保持良好的命名(Method_WhatIsProvided_WhatShouldBeDoneInSuchCase) - 或smth。就像你的测试值会增加。单元测试应该测试一件事 - 当你破坏你的代码时 - 你应该知道你已经破坏了什么。除了单元测试之外,我不确定是通过" null"来控制流量。好主意(并且对于应该返回集合的东西检查null永远不是好主意 - 你应该期待空集合。)

答案 1 :(得分:1)

代码的内容在_appointmentRepository。特别是IQueryable<Account>返回的GetEntityLogin(我假设您返回IQueryable,因为您应该使用数据库进行过滤,而不是应用程序)。

如果你没有使用IQueryable,那么你的SUTrepo之间会有一个紧密耦合的设计,他们的行为会捆绑在一起,两者之间没有明显的契约。也就是说它们必须一起使用,它们必须一起改变,所以实际上它们是一个单独的类(或单个可变代码单元)。

在自动化测试中,您可以使用一些测试原型。

  • 生产代码
  • 存根
  • 模拟

我会忽略生产代码,因为它显然是什么,专注于其他代码。

假冒是合同的实施,100%合作,但由于某种原因不适合生产。在这种情况下,我建议使用SQL CE或LocalDB伪造数据库,因为它允许您重用最大量的生产代码。您不能使用生产数据库进行单元测试的原因是它由生产共享(因此会影响生产)。它也不容易被重置(因此每个测试不是孤立的)。 LocalDB / SQL CE不适合生产,因为无法从多个位置/应用程序服务器访问它。这正是它将测试与其他开发人员隔离的原因。它们还可以处理本地文件,这使您可以轻松地隔离测试用例。

Stub是一个不实现合同的类。它们作为一种MacGuffin用于单元测试。它唯一的价值在于它存在并且闪亮。

Mock是一个实现脚本而不是契约的类。你告诉它如何回应每个动作,它就是这样做的。在我看来,模拟测试在所有形式的测试中价值最低。您正在测试行为而不是功能,因此它很脆弱。但是如果你在这里看看你的SUT,那么它的行为很少。你应该知道它应该在_appointmentRepository中调用一个方法,这似乎是这种方法逻辑的核心所在。

最后,我建议您先创建一个代码DbContext。创建一个带有连接字符串和IDbInitializer的构造函数。然后在每个测试开始时,首先传入一个新的随机文件位置以获取代码,以实现新的数据库文件。使用IDbInitializer为数据设定种子。然后在清理时删除随机文件。

答案 2 :(得分:1)

不要像你只是为预先存在的代码编写测试而考虑这种情况,而是考虑到你已经完成了识别SUT职责的任务。如果从这个角度编写测试,你应该能够避免意外编写细微错误的情况。

例如,以下是SUT的职责:

  1. 如果没有帐户与提供的电子邮件地址相关联,GetAccount会返回null
  2. 如果一个帐户与提供的电子邮件地址相关联,GetAccount会返回该帐户
  3. 如果多个帐户与提供的电子邮件地址相关联,则GetAccount应返回最近创建的帐户
  4. 每个场景都可以有自己的单元测试来验证预期结果。实际上,您在问题中提供的测试基本上可以分解为这些单独的场景。

    旁注:您并不需要Verify模拟对象的行为 - 通过检查返回值已经隐式发生了此验证。 (例如,如果模拟存储库与预期的参数不匹配,那么SUT如何返回expected1?)