首先我们要知道assertequals(期望,结果)这个函数的本质就是比较期望和结果是否相等,所以结果就是一个数而已,我们可以先int一个A,然后将结果放进A中再去比较。
那么之前提到的void函数怎么处理呢,总而言之,我们想要得到得是一个结果,我们只需要将void先进行运算得出一个结果在去进行比较就可以:
例如:
对于 void
函数(即不返回任何值的函数),我们通常关注它是否按照预期执行了某些操作,比如修改对象的状态、产生特定的输出等。当 void
函数需要手动输入输入数值时,我们需要在测试方法中模拟这些输入。
下面是一个使用 JUnit 测试 void
函数的例子,假设我们有一个 void
函数 calculateSum
,它接收两个整数作为输入,并将它们的和存储在一个类的字段中:
java复制代码
public class Calculator { | |
private int sum; | |
public void calculateSum(int a, int b) { | |
sum = a + b; | |
} | |
public int getSum() { | |
return sum; | |
} | |
} |
为了测试这个函数,我们可以创建一个 JUnit 测试类,并使用 assertEquals
或其他断言方法来验证 calculateSum
方法是否正确执行了。由于 calculateSum
是 void
的,我们不能直接对其返回值进行测试,但我们可以检查 Calculator
类的状态是否按照预期改变。
java复制代码
import org.junit.jupiter.api.BeforeEach; | |
import org.junit.jupiter.api.Test; | |
import static org.junit.jupiter.api.Assertions.assertEquals; | |
public class CalculatorTest { | |
private Calculator calculator; | |
@BeforeEach | |
public void setUp() { | |
// 在每个测试方法执行前初始化 Calculator 对象 | |
calculator = new Calculator(); | |
} | |
@Test | |
public void testCalculateSum() { | |
// 设置输入值 | |
int a = 5; | |
int b = 10; | |
// 调用 void 方法 | |
calculator.calculateSum(a, b); | |
// 验证状态是否改变 | |
// 在这种情况下,我们检查 sum 字段是否等于输入值的和 | |
int expectedSum = a + b; | |
int actualSum = calculator.getSum(); | |
// 使用断言方法验证结果 | |
assertEquals(expectedSum, actualSum, "The sum is not calculated correctly."); | |
} | |
} |
在上面的测试类中,@BeforeEach
注解的方法 setUp
在每个测试方法执行之前都会被调用,用于初始化 Calculator
对象。然后,在 testCalculateSum
测试方法中,我们调用 calculateSum
方法,并传递一些硬编码的输入值。接着,我们通过调用 getSum
方法来获取计算后的和,并使用 assertEquals
断言来验证计算的结果是否符合预期。
请注意,如果你的 void
函数依赖于用户输入(比如从控制台读取),那么你可能需要重构你的代码以便能够更容易地进行单元测试。通常,这意味着将用户输入抽象为参数,或者至少将其封装在一个可以从测试中模拟的接口或类中。这样,你就可以在测试中提供模拟的输入,而不是依赖于实际的用户输入。
还有一个问题是,如果我的测试函数需要手动输入一些值,(也就是运用了Scanner这个东西),那我们应该如何测试呢?要知道在测试中我们没有办法打开控制台去手动输入。
在JUnit中测试带有Scanner s = new Scanner(System.in);
的函数时,会遇到一个主要问题:JUnit测试是自动运行的,而System.in
通常用于从控制台接收手动输入。因此,在测试环境中,我们不能依赖实际的控制台输入。
为了解决这个问题,我们可以采取以下几种策略:
1. 重构代码以使用依赖注入
最佳实践是将Scanner
对象作为参数传递给需要它的方法或构造函数,而不是直接在方法内部创建它。这样,在测试时,我们可以传入一个模拟的Scanner
对象。
java复制代码
import java.util.Scanner; | |
public class MyClass { | |
public int readInput(Scanner scanner) { | |
int input = scanner.nextInt(); | |
// 使用输入执行某些操作 | |
return input; | |
} | |
} | |
// 在测试中 | |
import org.junit.jupiter.api.Test; | |
import org.mockito.Mockito; | |
import java.util.Scanner; | |
import static org.junit.jupiter.api.Assertions.assertEquals; | |
import static org.mockito.Mockito.when; | |
public class MyClassTest { | |
@Test | |
public void testReadInput() { | |
Scanner mockScanner = Mockito.mock(Scanner.class); | |
when(mockScanner.nextInt()).thenReturn(42); // 模拟输入42 | |
MyClass myClass = new MyClass(); | |
int result = myClass.readInput(mockScanner); | |
assertEquals(42, result); // 断言结果是否符合预期 | |
} | |
} |
2. 使用System
规则的替代
如果你不能或不想重构代码,你可以使用如PowerMock
这样的库来模拟静态方法调用,例如System.in
。但是,请注意,这通常被视为一种不那么优雅的方法,因为它涉及到了更多的复杂性,并可能使测试更难理解和维护。
3. 使用ByteArrayInputStream
另一种方法是使用ByteArrayInputStream
来模拟System.in
的输入流。这不需要额外的库,但同样需要对System.in
进行一些操作,这可能在某些情况下被视为不太好的做法。
java复制代码
import java.io.ByteArrayInputStream; | |
import java.io.InputStream; | |
import java.util.Scanner; | |
public class MyClass { | |
public int readInput() { | |
Scanner scanner = new Scanner(System.in); | |
int input = scanner.nextInt(); | |
scanner.close(); | |
return input; | |
} | |
} | |
// 在测试中 | |
import org.junit.jupiter.api.AfterEach; | |
import org.junit.jupiter.api.BeforeEach; | |
import org.junit.jupiter.api.Test; | |
import java.io.ByteArrayInputStream; | |
import java.io.InputStream; | |
import static org.junit.jupiter.api.Assertions.assertEquals; | |
public class MyClassTest { | |
private final InputStream systemInBackup; | |
private final ByteArrayInputStream in; | |
public MyClassTest() { | |
this.systemInBackup = System.in; | |
this.in = new ByteArrayInputStream("42\n".getBytes()); | |
} | |
@BeforeEach | |
public void setUp() { | |
System.setIn(in); | |
} | |
@AfterEach | |
public void tearDown() { | |
System.setIn(systemInBackup); | |
} | |
@Test | |
public void testReadInput() { | |
MyClass myClass = new MyClass(); | |
int result = myClass.readInput(); | |
assertEquals(42, result); | |
} | |
} |
在这个例子中,我们在测试开始之前备份了System.in
,然后在测试运行时将其替换为ByteArrayInputStream
,该流包含我们想要模拟的输入("42\n")。测试结束后,我们再将System.in
恢复为原始状态。
总结
最佳实践是重构代码以支持依赖注入,这样你就可以轻松地传入模拟对象进行测试。如果你不能重构代码,那么使用ByteArrayInputStream
来模拟System.in
可能是一个可行的替代方案,但请注意这增加了测试的复杂性和潜在风险。使用PowerMock
或其他类似库来模拟静态方法调用通常是最后的手段,应该尽量避免。