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

问题描述 投票:0回答:1

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>

非常感谢!

java junit powermock powermockito
1个回答
0
投票

我尝试了将模拟构造函数PowerMock的特性与模拟类(而不是模拟接口)相结合的东西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的代码。

也许你可以使用反射来访问private static final字段Scanner,将它设置为你之前创建的Scanner实例并且你可以控制,如this question的公认答案所述:可能它不是更简洁的方式来编写测试,但我认为它可以工作并解决您的问题。

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


0
投票

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

换句话说,我有一个Solution类,代码是我不能(读:不想)触摸,其中包括来自scannerSystem.in读取的输入:

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

我尝试确保在创建System.in扫描程序实例之前将final static设置为所需的值,但正如我们可以看到的那样,修改该扫描程序以便为不同的测试用例自定义它并不容易。

另一个棘手的问题是,在Solution类中,输出被设置为写入文件,其位置是使用System.getenv("OUTPUT_PATH")从环境变量获得的。这会产生一个问题,因为测试可以并行运行并尝试将结果写入同一文件中,如该环境变量所指定的那样。

长话短说,我最终做的是使用System.class模拟PowerMock,为每个测试用例创建一个嵌套类,并为每个测试用例类添加@PrepareForTest,这最终对我有用。下面是我的DoAllTest类的代码,它包含所有“特定于挑战”的信息,在这种情况下,它是“炸弹人游戏”的挑战。有两个测试用例0025用于此挑战。令人惊讶的是,在以下代码中包含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被模拟的情况下,scanner对象可以设置为所需的值。

© www.soinside.com 2019 - 2024. All rights reserved.