为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

千巷猫影 2022-07-14 09:42:36
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但是,在这种调用第二个静态方法(scanner 也是静态成员)的替代方案中,该问题再次出现。在这里,test1 会通过,但 test2 甚至无法运行,因为 java.lang.IllegalStateException: Scanner closed我需要两个测试在后一种情况下都通过,就像在前一种情况下一样。
查看完整描述

2 回答

?
繁华开满天机

TA贡献1816条经验 获得超4个赞

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


换句话说,我有一个Solution类,其中包含我无法(阅读:不想)触摸的代码,其中包括由scannerfrom读取的输入System.in:


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

我试图确保在创建System.in该扫描仪实例之前将其设置为所需的值final static,但正如我们从定义中看到的那样,修改该扫描仪以针对不同的测试用例对其进行定制绝非易事。


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


长话短说,我最终做的是模拟System.classusing PowerMock,为每个测试用例创建一个嵌套类并@PrepareForTest为每个测试用例类添加,这最终对我有用。下面是我的DoAllTest课程代码,其中包含所有“特定于挑战”的信息,在这种情况下,它是“炸弹人游戏”挑战。有两个测试用例00和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()的对象时调用。通过模拟,可以将对象设置为所需的值。SolutionTestSystem.ininputFileNameSystem.classscanner


查看完整回答
反对 回复 2022-07-14
?
慕森卡

TA贡献1806条经验 获得超8个赞

我尝试了将模拟构造函数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稍微改变课程时,你的问题没有解决方案:过去它对我有用,就像这里提出的(免责声明:由我) ,但如果没有自由改变它显然是行不通Solution的的代码。


可能您可以使用反射来访问private static final字段Scanner,将其设置为Scanner您之前创建的实例并且您可以控制,如该问题的已接受答案中所述:可能这不是编写测试的更干净的方式,但我认为它可以工作并解决您的问题。


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


查看完整回答
反对 回复 2022-07-14
  • 2 回答
  • 0 关注
  • 409 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号