如何使用PowerMock测试调用另一个静态方法的静态方法?

时间:2019-02-27 16:53:41

标签: java junit powermock powermockito

PowerMock是一个很棒的工具,我最近开始使用它来测试一些静态方法。不幸的是,除了测试之外,我无法重写任何内容,需要PowerMock能够严格按原样测试此代码。

这是我的PowerMock测试:

import java.io.*;
import org.junit.*;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;

import org.mockito.runners.MockitoJUnitRunner;
import org.powermock.core.classloader.annotations.PrepareForTest; 

@RunWith(MockitoJUnitRunner.class)
@PrepareForTest({Solution.class})
public class SolutionTest {

    // stream to record the output (System.out)
    private ByteArrayOutputStream testOutput;

    @Before
    public void setUpOutputStream() {
        testOutput = new ByteArrayOutputStream();
        System.setOut(new PrintStream(testOutput));
    }

    // input feed to Scanner (System.in)
    private void setInput(String input) {
        System.setIn(new ByteArrayInputStream(input.getBytes()));
    }

    @Test
    public void test1() {
        // set System.in
        setInput("foo");

        final String expected = "foobar";
        final String actual = testOutput.toString();

        // run the program (empty arguments array)
        Solution.main(new String[0]);

        assertEquals(expected, actual);
    }

    @Test
    public void test2() {
        setInput("new");
        Solution.main(new String[0]);
        final String expected = "newbar";
        final String actual = testOutput.toString();
        assertEquals(expected, actual);
    }
}

PowerMock使我有可能在这种情况下在静态方法上连续运行(并通过)两个测试:

import java.util.Scanner;
public class Solution {
    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        String input = scanner.nextLine();

        scanner.close();

        System.out.print(input + "bar");
    }
}

在PowerMock之前,我(由于必须测试静态方法而导致)受阻(java.lang.IllegalStateException: Scanner closed

但是,在此替代方案中,该方案调用了第二个静态方法(扫描程序也是静态成员),该问题重新出现。

import java.util.Scanner;
public class Solution {

    static void printString(String s) {
        System.out.print(s);
    }

    private static final Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {

        String input = scanner.nextLine();

        printString(input + "bar");

        scanner.close();
    }
}

在这里,test1将通过,但是由于以下原因,test2甚至无法运行 java.lang.IllegalStateException: Scanner closed

在前一种情况下,我需要两个测试都通过才能通过。

为方便起见(并且经过测试的答案将是最有价值的),我的依存关系如下:

<dependencies>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>1.6.5</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito</artifactId>
        <version>1.6.5</version>
        <scope>test</scope>
    </dependency>
</dependencies>

非常感谢!

2 个答案:

答案 0 :(得分:0)

我尝试了将 mock构造器 PowerMock的功能与 mock类(相对于 mock接口相对)Mockito的功能相结合的尝试,但是没有成功:我试图解决的问题是Scanner实例创建是在调用setInput之前发生的,因此我尝试了

private static Scanner scannerMock;

    static {
        try {
            scannerMock = Mockito.mock(Scanner.class);
            PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scannerMock);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void setInput(String input) throws Exception {
        PowerMockito.when(scannerMock.nextLine()).thenReturn(input);
    }

这可能适用于其他类,但不适用于Scanner类,因为它是final。我认为当您无法稍稍更改Solution类时,就无法解决您的问题:过去,它对我来说很像提出的方法(免责声明:由我负责)here,但是如果没有自由更改Solution的代码,显然无法正常工作。

也许您可以使用 reflection 来访问private static final字段Scanner,并将其设置为之前创建的Scanner实例,并且可以控制,如下所述在this question的公认答案中:也许这不是编写测试的较干净方法,但我认为它可以解决您的问题。

我希望这可以帮助您找到可行且可行的解决方案...

答案 1 :(得分:0)

我遇到了与此类似的问题。我试图为HackerRank挑战创建一个本地测试环境。我的目标是能够在半成品Solution类中完成我的解决方案,并针对从他们的网站下载的测试用例进行测试,而不必为每个挑战修改样板代码。

换句话说,我有一个Solution类,它包含我无法(阅读:不想)触摸的代码,其中包括一个scanner从{{1 }}:

System.in

在创建 private static final Scanner scanner = new Scanner(System.in); 扫描程序实例之前,我试图确保将System.in设置为所需的值,但是正如我们可以看到的那样,按顺序修改该扫描程序绝非易事为不同的测试用例自定义它。

final static类中,另一个棘手的问题是将输出设置为写入文件,该文件的位置是使用Solution从环境变量获取的。这就产生了一个问题,因为测试可以并行运行并尝试将结果写入该环境变量指定的相同文件中。

长话短说,我最终要做的是使用System.getenv("OUTPUT_PATH")模拟System.class,为每个测试用例创建一个嵌套类,并为每个测试用例类添加PowerMock ,最终对我有用。 以下是我用于@PrepareForTest类的代码,其中包含所有“特定于挑战”的信息,在这种情况下,这是“炸弹人游戏”挑战。有两个测试案例DoAllTest00来应对这一挑战。 令人惊讶的是,下面的代码中包含25实例的SolutionWrap对象实际上被两个测试共享,但是Solution照顾了PowerMock的模拟,它们在运行就像它们在单独的“容器”中一样。

System.class

package practice.thebombermangame; import common.SolutionTest; import common.SolutionTestable; import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.junit.experimental.runners.Enclosed; @RunWith(Enclosed.class) public class DoAllTest { class SolutionWrap implements SolutionTestable { public void runMain(String[] args) { try { Solution s = new Solution(); s.main(args); } catch (IOException e) { System.err.println(e.getMessage()); } }; }; static SolutionWrap solutionWrap = new DoAllTest().new SolutionWrap(); @RunWith(PowerMockRunner.class) @PrepareForTest({Solution.class, SolutionTest.class, Test1.class}) public static class Test1 { @Test public void test1() { String testIDString = "00"; String inputFileName = "src/practice/thebombermangame/input/input" + testIDString + ".txt"; String outputFileName = "out_path/output" + testIDString + ".txt"; String correctFileName = "src/practice/thebombermangame/output/output" + testIDString + ".txt"; SolutionTest solutionTest = new SolutionTest(inputFileName, outputFileName, correctFileName); solutionTest.doTest(solutionWrap); } }; @RunWith(PowerMockRunner.class) @PrepareForTest({Solution.class, SolutionTest.class, Test2.class}) public static class Test2 { @Test public void test2() { String testIDString = "25"; String inputFileName = "src/practice/thebombermangame/input/input" + testIDString + ".txt"; String outputFileName = "out_path/output" + testIDString + ".txt"; String correctFileName = "src/practice/thebombermangame/output/output" + testIDString + ".txt"; SolutionTest solutionTest = new SolutionTest(inputFileName, outputFileName, correctFileName); solutionTest.doTest(solutionWrap); } }; } 类由所有挑战共享,SolutionTest并在其中修改了环境变量,如下所示:

System.in

如您所见,创建对象package common; import java.io.FileInputStream; import java.io.IOException; import org.powermock.api.mockito.PowerMockito; import org.mockito.Mockito; public class SolutionTest { static String inputFileName; String outputFileName; String correctFileName; public SolutionTest(String inputFileName_, String outputFileName_, String correctFileName_) { inputFileName = inputFileName_; outputFileName = outputFileName_; correctFileName = correctFileName_; setSystemIn(); } final static void setSystemIn() { try { System.out.println("Setting System.in to " + inputFileName); System.setIn(new FileInputStream(inputFileName)); } catch(IOException e) { System.err.println(e.getMessage()); } } public void doTest(SolutionTestable solutionTestable) { PowerMockito.mockStatic(System.class); PowerMockito.when(System.getenv(Mockito.eq("OUTPUT_PATH"))).thenReturn(outputFileName); SampleTest sampleTest = new SampleTest(); sampleTest.testMain(solutionTestable, outputFileName, correctFileName); } }; 并将对象setSystemIn()设置为传递给构造函数的SolutionTest时,将调用System.in。通过模拟inputFileName,可以将System.class对象设置为所需的值。