10

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

所以我的问题有两个:

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

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

4

4 回答 4

7

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

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

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

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

如果一个人选择了 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);
    }
}
于 2012-09-28T10:34:07.547 回答
3

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

我的回答是:

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

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

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

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

于 2013-07-22T23:23:46.773 回答
2

如果您确实需要(或想要)避免再次调用较低级别的方法,则可以将它们存根而不是模拟它们。例如,如果方法 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);

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

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

于 2012-09-27T17:25:13.510 回答
2

真正的问题应该是:

我真正想测试什么?

实际上答案应该是:

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

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

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

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

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

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

如果一个人选择了 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事件,或者您的演示者是否设置了正确的错误消息等。

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

于 2014-03-20T15:43:14.247 回答