问题描述
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将通过,但由于java.lang.IllegalStateException: Scanner closed
,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>
非常感谢!
1楼
我尝试了将模拟构造函数 PowerMock的功能与模拟类 (而不是模拟接口 )相结合的方法,但是没有成功:我试图解决的问题是在setInput
调用之前创建了Scanner
实例,所以我尝试了
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
实例,然后您可以进行控制,如的公认答案所述:可能不是编写测试的更干净的方法,但我认为它可以解决您的问题。
我希望这可以帮助您找到可行且可行的解决方案...
2楼
我遇到了与此类似的问题。
我试图为HackerRank挑战创建一个本地测试环境。
我的目标是能够在提供的半成品Solution
类中完成我的解决Solution
,并针对从其网站下载的测试用例进行测试,而不必为每个挑战修改样板代码。
换句话说,我有一个Solution
类,其中包含我无法(阅读:不想)触摸的代码,其中包括scanner
从System.in
读取的输入:
private static final Scanner scanner = new Scanner(System.in);
我试图确保在创建final static
扫描器实例之前将System.in
设置为所需的值,但是正如我们可以看到的那样,修改该扫描器以针对不同的测试用例进行自定义绝非易事。
另一个棘手的问题是,在Solution
类中,将输出设置为写入文件,该文件的位置是使用System.getenv("OUTPUT_PATH")
从环境变量获取的。
这就产生了一个问题,因为测试可以并行运行并尝试将结果写入该环境变量指定的同一文件中。
长话短说,我最终要做的是使用PowerMock
模拟System.class
,为每个测试用例创建一个嵌套类,并为每个测试用例类添加@PrepareForTest
,最终对我@PrepareForTest
。
下面是我的DoAllTest
类的代码,其中包含所有“特定于挑战”的信息,在这种情况下,这是“炸弹人游戏”挑战。
这个挑战有两个测试用例00
和25
。
令人惊奇的是,下面的代码中包含Solution
实例的SolutionWrap
对象实际上被两个测试共享,但是PowerMock
负责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
对象设置为所需的值。