Java单元测试:从公共方法或包私有方法进行测试

时间:2019-10-25 07:55:16

标签: java unit-testing

我有一个带有公共方法Price的类calculate(),该方法调用了另一个私有方法calculateByA()

public class Price {
    public SomeObject calculate() {
        if(someCondition) {
            calculateByA()
            // ....
        } else {
            //Calculate something else
        }
    }

    private int calculateByA() {
        //calculation logic
    }
}

我想进行单元测试以测试calculationByA()的逻辑。

我想到了:

  1. 通过调用公共方法calculate()进行测试,并将someCondition设置为true
  2. calculateByA()更改为package-private,然后直接在测试中调用。

但是,我不确定哪种方法更好。

3 个答案:

答案 0 :(得分:1)

我非常不喜欢powermock等人的用法。问题不在于它们让您绕过了能见度系统(其他语言根本不需要理会,例如参见python),而是您尝试测试实现细节。

为可见的方法编写测试,并观察其私有方法的副作用。在您的示例中,例如在someCondition中测试,您的结果在这种情况下是否符合预期。 如果由于存在许多私有方法而遇到问题,请考虑使用一个程序包并拆分代码-无论如何它可能太大了。还可以使用DI luke!

答案 1 :(得分:0)

您的问题有三个选择。最终,取决于您的情况最适合您。但是,以下是一些有关如何选择它们的想法:

  1. 通过公共界面测试方法

在您提到的情况下,可以使用正确的配置调用calculate来执行calculateByA。我相信这是最好的方法。该代码的结构意味着calculate是访问calculateByA的公共接口。这意味着您的测试将在行为发生变化时提醒人们注意问题。反过来,这意味着班级的用户将经历行为上的变化。

  1. 将的定义更改为保护

正如您提到的,这将允许您从同一程序包中的其他类(包括单元测试类)调用calculateByA。当然,这是一种允许对calculateByA进行简单且独立的单元测试的方法。但是,我不喜欢这样,仅仅是因为有一个原因,您只希望通过calculate函数进行访问,并且通过更改访问权限,很容易使同一包中的其他类直接调用calculateByA

  1. 使用反射直接调用私有方法

有许多技术可以让您直接在测试中调用私有方法。我不会重复@TiemoVorschütz的出色回答,其中详细介绍了如何执行此操作。但是,这将是我的最后选择。仅仅使用反射进行测试会更加脆弱,并增加测试套件的维护成本。

答案 2 :(得分:-1)

PowerMock可让您直接测试您的私有方法:

Price.java:

import java.util.concurrent.ThreadLocalRandom;

public class Price {

  private final boolean someCondition;


  public Price(final boolean someCondition) {

    this.someCondition = someCondition;
  }


  public int calculate() {
    if (someCondition) {
      return calculateByA();
    } else {
      // Calculate something else
      return 0;
    }
  }


  private int calculateByA() {
    final int randomNum = ThreadLocalRandom.current().nextInt(0, 10 + 1);
    return 4711 * randomNum;
  }
}

PriceTest.java:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

@RunWith(PowerMockRunner.class)
@PrepareForTest(Price.class)
public class PriceTest {

  /*
   * Refelection test for a single private method
   */
  @Test
  public void calculateByA() throws Exception {

    final Price instance = new Price(true);
    final int result = Whitebox.invokeMethod(instance, "calculateByA");
    System.out.println(result);
  }


  /*
   * Test public method calculate which invokes private method calculateByA
   */
  @Test
  public void testCalculate() throws Exception {

    final Price instance = new Price(true);
    System.out.println(instance.calculate());
  }
}

JUnit4旁边的依赖项:

</properties>
     <powermock.version>2.0.2</powermock.version>
</properties>

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.28.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-core</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>