基本方法为私有时的单元测试包装器方法

时间:2016-04-11 03:23:23

标签: java unit-testing tdd

我读了关于单元测试包装器方法的this answer。在以下代码中:

public static Object methodA(Object arg) {
    if (arg == null) {
        return null;
    } else {
        return methodB();
    }
}

我不需要测试methodB()中的所有功能,只测试arg为空的2个案例,而不是因为methodB()已经过测试,这似乎是合理的。

但是,如果methodB()私有方法,我应该测试methodB()将提供的所有功能,因为根据another answer,私有方法是实现细节。

问题是,如果我有两个方法methodA1()methodA2(),他们都会像这样调用methodB()

public static MyObject methodA1(int x) {
    MyObject obj = new MyObject();
    obj.setX(x);
    return methodB(obj);
}

public static MyObject methodA2(int y) {
    MyObject obj = new MyObject();
    obj.setY(y);
    return methodB(obj);
}

private static MyObject methodB(MyObject obj) {
    // doSomething with obj
    return obj;
}

我应该分别测试methodA1()methodA2()吗?或者我可以测试私有方法methodB(),因为methodA1()methodA2()只是methodB()的包装方法,所以如果正确测试methodB(),我就赢了我需要测试methodA1()methodA2()

编辑:

我首先为公共方法编写了单独的测试。但问题是,如果methodA存在许多变体,并且某些功能/需求在其中一些中共享,则测试用例的代码将被复制。这就是为什么我想知道我是否应该测试私有方法methodB()

我遇到的真正问题是我要求在数据库中添加记录,并且我应该提供许多不同的API。例如,我应该提供:

MyObject addMale(String name, String job);     // fill sex with "Male"
MyObject addStudent(String name, String sex);  // fill job with "Student"

所有这些都检查参数是否有效,填写未指定的字段并调用私有方法将记录实际插入数据库,即所谓的methodB()

有很多像这样的API,所以如果我只能测试methodB()所有字段的情况,也许我可以减少测试用例的重复,但这是一个很好的做法来做这样的单元测试?

EDIT2:

我知道我可以测试私有方法,因为我在其他测试用例中使用了反射,而且我知道它也可以调用私有方法。但我的问题是在这种情况下,测试私有方法是否是一个合适的解决方案。

3 个答案:

答案 0 :(得分:1)

这实际上是一段我一直想知道的问题,虽然它不一定正确,但我会分享我的观点。

主要问题是私人方法难以测试。我做了一些谷歌搜索,并有一些工具和技术,让你访问私人方法(反射是我遇到的主要方法),但他们似乎有点复杂。

但是你编写私有方法的原因是因为有另一种方法,一个公共方法,它调用它。因此,虽然您无法直接测试私有方法,但您可以通过测试调用它的公共方法来检查其功能。

如果我是你,我会广泛测试methodA1()methodA2(),确保methodB()的所有功能都通过您在公共方法上运行的测试进行测试。

答案 1 :(得分:1)

您已收到一些意见,为什么您应该为methodA1()methodA2()编写单元测试;你已经推倒了它们。你在这里问了这个问题;你正在寻找一些不为他们编写完整的单元测试的理由。让我们看看我们是否可以给你你想要的答案。 ; - )

根据您在编辑中添加的内容,您可能会看到构建器模式的变体。例如)

MyObject o = new Builder().setX(1).setY(2).setZ(3).setW(4).build();

我们如何测试建造者?

由于构建器有4个属性,可以按任意顺序设置,因此会有4个属性! = 24种不同的排序。完整的测试套件必须包括:

@Test public void testXYZW() { ... }
@Test public void testXYWZ() { ... }
// ... 21 more permutations ...
@Test public void testWZYX() { ... }

但这就是全部吗?没有!其中一些属性可能具有默认值,因此我们也必须测试这些模式。总排序现在为P(4,4)+ P(4,3)+ P(4,2)+ P(4,1)+ P(4,0)= 24 + 24 + 12 + 4 + 1 = 85单元测试。

@Test public void testXWY() { ... }
@Test public void testWY() { ... }
@Test public void testZ() { ... }
// ... 61 more permutations ordering

这仅测试X,Y,Z,W属性的每个排列,每个测试每个属性一个测试值。为每种可能的组合和排列编写一套详尽的测试显然是笨拙的。

构建器类的设计者会理解属性设置的排列不会影响最终的构造;为订单的排列编写测试实际上并没有增加测试覆盖率。省略属性的测试很有用,因为它们测试默认值。再次测试省略属性的不同组合不会增加测试覆盖率。因此,仔细考虑之后,可能只需要进行两次测试:

@Test
public void testXYZW() {
    MyObject o = new Builder().setX(1).setY(2).setZ(3).setW(4).build();
    assertThat(o.wasBuiltProperly());
}

@Test void testDefaults() {
    MyObject o = new Builder().build();
    assertThat(o.wasBuiltProperlyFromDefaults());
}

如果正确完成methodB()的完整测试,那么您可以放心地仅测试methodA1()methodA2()中输入的验证。

@Test void testMethodA1Professor() {
    MyObject o = methodA1("professor");
    assertThat(o.wasBuiltProperlyWithProfessor());
}

@Test void testMethodA1WithNull() {
    MyObject o = methodA1(null);
    assertThat(o.wasBuiltProperlyWithNull());
}

答案 2 :(得分:0)

通常,当一个类中的多个方法使用私有方法时,隐藏在它背后的是一个独特的概念。它可能应该有自己的类。

  

不应该在课堂外使用

有几种方法可以将methodB提取到自己的类中,而不是将其作为公共API的一部分提供:

  • 将其嵌入使用它的类中并为其提供限制范围

  • 将它放在较低级别的另一个模块中。使该模块可以从您的API获得,但不能从客户端获得。