【问题标题】:Am I using mocks the right way?我是否以正确的方式使用模拟?
【发布时间】:2012-01-16 20:25:10
【问题描述】:

我有一些遗留的东西,想通过测试来覆盖它。 有以下方法,我不知道如何使用模拟进行测试。

public String listTransactions(Request request, Response response) {
    String transactionFamily = request.get("transactionFamily");
    List transactions = service.fetchTransactions(transactionFamily);
    responseBuilder.addElement("collection", transactions);
    responseBuilder.addElement("token", tokenGenerator.next());
    String  formattedResponse = responseBuilder.build();
    response.send(formattedResponse);
    return null;
}

我的第一个方法是:

public void testResponseIsBuilt() {
    request = stub(Request.class);
    request.method("get").with("transactionFamily").willReturn("dummyFamily");
    response = mock(Response.class);
    response.mehod("send").called(once());
    service = stub(TransactionService.class);
    service.method("fetchTransactions").willReturn(testTransactions);
    responseBuilder = mock(ResponseBuilder.class);
    responseBuilder.method("addElement").called(once()).with("collection", testTransactions);
    responseBuilder.method("addElement").called(once()).with("token", sampleToken);
    responseBuilder.method("build").called(once());     
    responder.setService(service);
    responder.setResponseBuilder(responseBuilder);

    responder.listTransactions(request, response);
}

我知道单个测试用例应该只涵盖 SUT 的单个方面。因此,考虑到这一点,我可以想象以下一组测试:

  • testTransactionsFetchedForFamily
  • testTransactionsAddedToResponse
  • testTokenSetInResponse
  • testFormattedResponseBuilt
  • testResponseSent

例如执行“testResponseBuilt”我需要做这样的设置(如上)

  • 存根请求
  • 存根服务
  • 具有 3 个期望的模拟 responseBuilder(2 个 addElement 和 1 个构建)

是不是太多了?这样的设置是不是太复杂了?我在这里“过度模拟”了吗?

我可以考虑将所有 responseBuilder 的东西放到单独的协作者中,但是这对我来说有点疯狂,因为我刚刚提取了 ResponseBuilder 本身。

我怀疑我在这里写单元测试时遗漏了一些重要的点。

【问题讨论】:

    标签: java unit-testing mocking


    【解决方案1】:

    你在这些测试中走得太远了。看来您的方法的责任是正确设置和发送响应(从代码来看 - 命名可能会更好,例如,现在 listing 是什么事务?)。这就是这里应该测试的内容。

    编辑:

    再看一遍,似乎大部分工作都是由responseBuilder 完成的——剩下的代码只是设置它。所以你真正可以在这里测试的是它是否提供了预期的数据(这是一个或两个测试),最后是否发送了响应(第二个/第三个测试)。请注意,检查是否调用了.build 并不是必需的,因为如果没有它会导致响应发送测试失败。

    这意味着您需要模拟和验证对responseBuilder.add 方法)和response.send)的期望。测试响应格式属于responseBuilder测试,正如测试是否正确获取事务属于service测试。

    (您也可以验证 service 是用正确的参数调用的,因为它是硬编码的,但前提是您要非常小心)

    总的来说,我会删除测试 #1 和 #4,并专注于剩余的测试。

    为了验证这些期望,您必须删除剩余的依赖项。没有真正的解决办法。您需要在决定您愿意编写多少代码以测试单行代码(以及是否值得)之间找到最佳点。

    【讨论】:

    • 但是要设置一些模拟期望,我需要在测试中揭示一些内部结构,对吧?为了获得正确的响应格式,我需要准备 responseBuilder 来处理事务和令牌,所以我需要再次揭示内部结构。还是我完全迷路了?
    • @grafthez:检查我的编辑 - 你是部分正确的,你想要测试的是 responseBuilder 是否提供了正确的数据以及是否发送了响应。其他测试属于不同的类。
    【解决方案2】:

    测试套件应包含设置方法,其中应提及请求、服务。这将确保您为所有测试用例重用变量。您制作的模块化程度越高,得到的响应就越细。希望对您有所帮助。

    【讨论】:

    • 你是对的。我已经在这里说清楚了——它会在实际实现中转到 setUp() 。我的问题有点不同:我的实现执行“a b c d”并测试“b”期望需要设置“a”。要测试“c”期望,我需要设置“a b”,依此类推...
    • JUnit 用于测试流程,因此据我所知,您需要为完整的测试用例测试 d 并为 c、b 和 a 提供断言。您的测试套件应该只包含一个测试流程的测试。
    猜你喜欢
    • 2011-09-08
    • 1970-01-01
    • 1970-01-01
    • 2015-09-16
    • 1970-01-01
    • 2020-12-13
    • 2012-07-03
    • 2011-06-18
    • 1970-01-01
    相关资源
    最近更新 更多