【问题标题】:Unit test for method that calls multiple other methods using Mockito使用 Mockito 调用多个其他方法的方法的单元测试
【发布时间】:2012-09-27 15:34:51
【问题描述】:

也许我的搜索完全失败了,但我找不到任何与如何为 Java 类/方法编写单元测试相关的文档或讨论,而 Java 类/方法又会调用其他非私有方法。看起来,Mockito 的立场是,如果必须使用间谍来测试需要模拟内部方法调用的方法,则设计可能存在问题(不是真正的 OO)。我不确定这是否总是正确的。但使用间谍似乎是实现这一目标的唯一方法。例如,为什么你不能有一个“包装器”风格的方法,它又依赖于其他方法来实现原始功能,但另外提供功能、错误处理、日志记录或依赖于其他方法结果的不同分支等?

所以我的问题有两个:

  1. 有一个内部调用其他方法的方法是否设计和实现不佳的代码?
  2. 如果选择 Mockito 作为他们的模拟框架,为这种方法编写单元测试的最佳实践和/或方法是什么(假设它本身是一个好主意)?

这可能是一个困难的要求,但我更希望那些决定回答的人不仅仅是重新发布 Mockito 措辞和/或对间谍的立场,因为我已经知道这种方法和意识形态。另外,我也使用过 Powermockito。对我来说,这里的问题是 Mockito 开发了这个框架,必须创建额外的解决方法来支持这个需求。所以我想我想要回答的问题是,如果间谍是“坏的”,并且 Powermockito 不可用,应该如何对调用其他非私有方法的方法进行单元测试?

【问题讨论】:

标签: java unit-testing mockito


【解决方案1】:

有一个内部调用其他方法的方法是设计和实现不佳的代码吗?

不是真的。但我想说,在这种情况下,调用其他方法的方法应该像其他尚未单独测试的方法一样进行测试。 也就是说,它可以保护您免受公共方法在您没有注意到的情况下停止调用其他方法的情况。

是的,它(有时)会产生很多测试代码。我相信这就是重点:编写测试的痛苦是一个很好的线索,您可能需要考虑将这些子方法提取到一个单独的类中。

如果我能接受这些测试,那么我认为子方法还没有被提取出来。

如果选择 Mockito 作为他们的模拟框架,那么为这种方法编写单元测试的最佳实践和/或方法是什么(假设它本身是一个好主意)?

我会这样做:

public class Blah {
    public int publicMethod() {
        return innerMethod();
    }

    int innerMethod() {
        return 0;
    }
}


public class BlahTest {
    @Test
    public void blah() throws Exception {
        Blah spy = spy(new Blah());
        doReturn(1).when(spy).innerMethod();

        assertThat(spy.publicMethod()).isEqualTo(1);
    }
}

【讨论】:

    【解决方案2】:

    对我来说,这个问题与凝聚力的概念密切相关。

    我的答案是:

    在一个类中拥有调用其他方法(私有)的方法(公共)是可以的,事实上我经常认为这是好的代码。然而,有一个警告,你的班级仍然应该具有很强的凝聚力。对我来说,这意味着你的班级的“状态”应该被很好地定义,你班级的方法(思考行为)应该参与以可预测的方式改变你的班级状态。

    您要测试的内容是否属于这种情况?如果没有,您可能正在查看一门课程,而您应该查看两门(或更多)课程。

    您要测试的类的状态变量是什么?

    您可能会发现,在考虑了这些类型问题的答案之后,您的代码会变得更容易以您认为应该的方式进行测试。

    【讨论】:

      【解决方案3】:

      如果您确实需要(或希望)避免再次调用较低级别的方法,则可以将它们存根而不是模拟它们。例如,如果方法 A 调用 B 和 C,您可以这样做:

      MyClass classUnderTest = new MyClass() {
          @Override
          public boolean B() {return true;}
      
          @Override
          public int C() {return 0;}
      };
      doOtherCommonSetUp(classUnderTest);
      String result = classUnderTest.A("whatever");
      assertEquals("whatIWant", result);
      

      我在遗留代码中使用了相当多的这种方法,其中大量重构很容易导致软件版本的造船者病:将难以测试的东西隔离成一个小方法,然后将其存根。

      但如果被调用的方法相当无害并且不需要模拟,我只是让它们再次被调用,而不用担心我覆盖了它们中的每条路径。

      【讨论】:

        【解决方案4】:

        真正的问题应该是:

        我真正想测试什么?

        实际上答案应该是:

        我的对象响应外部变化的行为

        也就是说,根据与对象交互的方式,您希望在单个测试中测试每个可能的单个场景。这样,您可以确保您的班级根据您提供测试的场景的期望做出反应。

        有一个内部调用其他方法的方法是设计和实现不佳的代码吗?

        不是真的,真的不是!这些从公共成员调用的所谓私有方法就是辅助方法。有辅助方法是完全正确的!

        帮助方法可以帮助将一些更复杂的行为从类本身分解成更小的可重用代码片段。只有它知道它应该如何表现并通过你的类的公共成员相应地返回状态。

        很少见带有辅助方法的类,通常它们必须采用类不应对外部世界做出反应的内部行为。

        如果选择 Mockito 作为他们的模拟框架,那么为这种方法编写单元测试的最佳实践和/或方法是什么(假设它本身是一个好主意)?

        以我的拙见,您不会测试这些方法。当公共成员通过公共成员调用时您期望的对象的状态进行测试时,它们会得到测试。例如,使用 MVP 模式,如果您想测试用户身份验证,则不应测试每个私有方法,因为私有方法可能会从依赖于被测对象的对象中调用其他公共方法等等。相反,测试你的观点:

        @TestFixture
        public class TestView {
            @Test
            public void test() {
                // arrange
                string expected = "Invalid login or password";
                string login = "SomeLogin";
                string password = "SomePassword";
        
                // act
                viewUnderTest.Connect(login, password);
                string actual = viewUnderTest.getErrorMessage;
        
                // assert
                assertEqual(expected, actual);
            }
        }
        

        这个测试方法描述了一旦点击了connectButton,你的视图的预期行为。如果ErrorMessage 属性不包含预期值,这意味着您的视图或演示者的行为不符合预期。您可以检查演示者是否订阅了您视图的Connect 事件,或者您的演示者是否设置了正确的错误消息等。

        事实上,您永远不需要测试私有方法中发生的任何事情,因为您需要在调试时进行调整和更正,这反过来又会导致您同时测试内部方法的行为,但不需要特殊测试方法应该为那些辅助方法明确编写。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多