如何使用 JUnit 或 Mockito 测试使用 Random()、不带参数和 return 值的方法
How to test a method that uses Random(), without arguments and return value, using JUnit or Mockito
我正在学习成为一名 Java 开发人员,现在我正在学习测试驱动开发,这意味着我对 JUnit 和 Mockito 还很陌生。
我已经挣扎了一段时间了,我卡住了。
我不知道如何测试这个没有参数、没有 return 值和随机发生器的特定方法。
旧逻辑:
public void getPlayerToStart(int randomNr) {
if (randomNr == 1) {
currentPlayer = p1;
opponentPlayer = p2;
} else {
currentPlayer = p2;
opponentPlayer = p1;
}
}
旧测试
@Test
void testSetCurrentPlayerSetToPlayer1() {
gameEngine.getPlayerToStart(1);
assertEquals(gameEngine.getP1(), gameEngine.getCurrentPlayer());
assertEquals(gameEngine.getP2(), gameEngine.getOpponentPlayer());
}
@Test
void testSetCurrentPlayerSetToPlayer2() {
gameEngine.getPlayerToStart(2);
assertEquals(gameEngine.getP2(), gameEngine.getCurrentPlayer());
assertEquals(gameEngine.getP1(), gameEngine.getOpponentPlayer());
}
新逻辑:
public void getPlayerToStart() {
Random rand = new Random();
int randomNr = rand.nextInt(2) + 1;
if (randomNr == 1) {
currentPlayer = p1;
opponentPlayer = p2;
} else {
currentPlayer = p2;
opponentPlayer = p1;
}
}
我不确定如何在没有参数的情况下测试 getPlayerToStart() "randomNr"。有人能给我指出正确的方向吗!
提前致谢。
将对 new Random()
的调用移动到它自己的方法中,就像这样。
您可以重写 getPlayerToStart
方法以使用另一个方法,以保存重复的代码(但您不需要)。
public void getPlayerToStart() {
Random rand = makeRandom();
int randomNumber = rand.nextInt(2) + 1
getPlayerToStart(randomNumber);
}
public Random makeRandom() {
return new Random();
}
现在您可以使用 Mockito
- 制作一个模拟
Random
对象;
- 监视你的 class,这是你要测试的对象;
- 存根你的间谍的
makeRandom
方法,这样它 returns 你的模拟 Random
;
- 存根模拟
Random
以便它 returns 在每个测试中你喜欢的任何值。
之后,您可以编写一个测试,让玩家 1 开始,另一个测试让玩家 2 开始。
请始终牢记,"gee, this is hard to test" 的想法是 TDD 试图向您尖叫 设计需要审查。
I have no idea how to test this particular method that has no arguments, no return value and a randomizer.
随机数是一种副作用,如 I/O 或时间,应在您的设计中以这种方式处理。
也就是说,如果您正在进行 TDD,您应该认识到的一件事是随机性来源是您系统的 输入 ;它是 imperative shell 的一部分,它在 运行 测试时由您的测试工具提供,在生产中由您的组合根提供。
可测试的方法会将 "generate a seed" 与 "compute a state from the seed" 分开;单元测试非常适合后者,因为纯函数真的很容易测试。生成随机数是 state of sin 级难以测试的,但通过一些设计,您可以将围绕它的代码简化到 "obviously has no deficiencies".
的程度
您可能还想查看 Writing Testable Code, by Misko Hevery, or Tales of the Fischer King。
另一种解决方案可能是对单一责任模式的严格解释:class提供业务逻辑不应该是负责创建或获取其依赖项。这就引出了依赖注入的概念:
class CodeUnderTest {
private final Random rand;
public CodeUnderTest(@NotNull Random rand){
this.rand = rand;
}
public void getPlayerToStart() {
int randomNr = rand.nextInt(2) + 1;
if (randomNr == 1) {
currentPlayer = p1;
opponentPlayer = p2;
} else {
currentPlayer = p2;
opponentPlayer = p1;
}
}
}
您需要为此增强您的测试:
class CodeUnderTestTest{
private final Random fakeRandom = new Random(1);
private CodeUnderTest cut;
@Before
public void setup(){
cut = new CodeUnderTest(fakeRandom);
}
// your test code relying on the repeatable order
// of Random object initialized with a fix seed.
}
您还需要更改代码中实例化 CodeUnderTest
的所有位置,以添加不带种子的 Random
对象。
起初这看起来像是一个缺点,但它提供了在整个代码中只有一个 Random
实例而无需实现 Java Singelton Pattern.[=17 的可能性=]
如果将 Random
对象替换为 mock,您可以获得更多的控制权。最简单的方法是使用 mocking 框架,例如 Mockito:
class CodeUnderTestTest{
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock
private Random fakeRandom;
// you could use @InjectMocks here
// instead of the setup method
private CodeUnderTest cut;
// This will NOT raise compile errors
// for not declared or not provided
// constructor arguments (which is bad in my view).
@Before
public void setup(){
cut = new CodeUnderTest(fakeRandom);
}
@Test
void testSetCurrentPlayerSetToPlayer1() {
doReturn(0).when(fakeRandom).nextInt(2);
cut.getPlayerToStart(1);
assertEquals(cut.getP1(), cut.getCurrentPlayer());
assertEquals(cut.getP2(), cut.getOpponentPlayer());
}
}
我同意谁说你应该使用依赖注入并拥有你自己的抽象(这样你就可以模拟合作者)。
但是,创建抽象只是将责任(和测试问题)转移到别处。
您是否知道接受名为 "seed" 的整数参数的 Random 构造函数?使用相同的种子,您将始终获得相同的结果序列。
参见:
我正在学习成为一名 Java 开发人员,现在我正在学习测试驱动开发,这意味着我对 JUnit 和 Mockito 还很陌生。
我已经挣扎了一段时间了,我卡住了。
我不知道如何测试这个没有参数、没有 return 值和随机发生器的特定方法。
旧逻辑:
public void getPlayerToStart(int randomNr) {
if (randomNr == 1) {
currentPlayer = p1;
opponentPlayer = p2;
} else {
currentPlayer = p2;
opponentPlayer = p1;
}
}
旧测试
@Test
void testSetCurrentPlayerSetToPlayer1() {
gameEngine.getPlayerToStart(1);
assertEquals(gameEngine.getP1(), gameEngine.getCurrentPlayer());
assertEquals(gameEngine.getP2(), gameEngine.getOpponentPlayer());
}
@Test
void testSetCurrentPlayerSetToPlayer2() {
gameEngine.getPlayerToStart(2);
assertEquals(gameEngine.getP2(), gameEngine.getCurrentPlayer());
assertEquals(gameEngine.getP1(), gameEngine.getOpponentPlayer());
}
新逻辑:
public void getPlayerToStart() {
Random rand = new Random();
int randomNr = rand.nextInt(2) + 1;
if (randomNr == 1) {
currentPlayer = p1;
opponentPlayer = p2;
} else {
currentPlayer = p2;
opponentPlayer = p1;
}
}
我不确定如何在没有参数的情况下测试 getPlayerToStart() "randomNr"。有人能给我指出正确的方向吗!
提前致谢。
将对 new Random()
的调用移动到它自己的方法中,就像这样。
您可以重写 getPlayerToStart
方法以使用另一个方法,以保存重复的代码(但您不需要)。
public void getPlayerToStart() {
Random rand = makeRandom();
int randomNumber = rand.nextInt(2) + 1
getPlayerToStart(randomNumber);
}
public Random makeRandom() {
return new Random();
}
现在您可以使用 Mockito
- 制作一个模拟
Random
对象; - 监视你的 class,这是你要测试的对象;
- 存根你的间谍的
makeRandom
方法,这样它 returns 你的模拟Random
; - 存根模拟
Random
以便它 returns 在每个测试中你喜欢的任何值。
之后,您可以编写一个测试,让玩家 1 开始,另一个测试让玩家 2 开始。
请始终牢记,"gee, this is hard to test" 的想法是 TDD 试图向您尖叫 设计需要审查。
I have no idea how to test this particular method that has no arguments, no return value and a randomizer.
随机数是一种副作用,如 I/O 或时间,应在您的设计中以这种方式处理。
也就是说,如果您正在进行 TDD,您应该认识到的一件事是随机性来源是您系统的 输入 ;它是 imperative shell 的一部分,它在 运行 测试时由您的测试工具提供,在生产中由您的组合根提供。
可测试的方法会将 "generate a seed" 与 "compute a state from the seed" 分开;单元测试非常适合后者,因为纯函数真的很容易测试。生成随机数是 state of sin 级难以测试的,但通过一些设计,您可以将围绕它的代码简化到 "obviously has no deficiencies".
的程度您可能还想查看 Writing Testable Code, by Misko Hevery, or Tales of the Fischer King。
另一种解决方案可能是对单一责任模式的严格解释:class提供业务逻辑不应该是负责创建或获取其依赖项。这就引出了依赖注入的概念:
class CodeUnderTest {
private final Random rand;
public CodeUnderTest(@NotNull Random rand){
this.rand = rand;
}
public void getPlayerToStart() {
int randomNr = rand.nextInt(2) + 1;
if (randomNr == 1) {
currentPlayer = p1;
opponentPlayer = p2;
} else {
currentPlayer = p2;
opponentPlayer = p1;
}
}
}
您需要为此增强您的测试:
class CodeUnderTestTest{
private final Random fakeRandom = new Random(1);
private CodeUnderTest cut;
@Before
public void setup(){
cut = new CodeUnderTest(fakeRandom);
}
// your test code relying on the repeatable order
// of Random object initialized with a fix seed.
}
您还需要更改代码中实例化 CodeUnderTest
的所有位置,以添加不带种子的 Random
对象。
起初这看起来像是一个缺点,但它提供了在整个代码中只有一个 Random
实例而无需实现 Java Singelton Pattern.[=17 的可能性=]
如果将 Random
对象替换为 mock,您可以获得更多的控制权。最简单的方法是使用 mocking 框架,例如 Mockito:
class CodeUnderTestTest{
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock
private Random fakeRandom;
// you could use @InjectMocks here
// instead of the setup method
private CodeUnderTest cut;
// This will NOT raise compile errors
// for not declared or not provided
// constructor arguments (which is bad in my view).
@Before
public void setup(){
cut = new CodeUnderTest(fakeRandom);
}
@Test
void testSetCurrentPlayerSetToPlayer1() {
doReturn(0).when(fakeRandom).nextInt(2);
cut.getPlayerToStart(1);
assertEquals(cut.getP1(), cut.getCurrentPlayer());
assertEquals(cut.getP2(), cut.getOpponentPlayer());
}
}
我同意谁说你应该使用依赖注入并拥有你自己的抽象(这样你就可以模拟合作者)。 但是,创建抽象只是将责任(和测试问题)转移到别处。
您是否知道接受名为 "seed" 的整数参数的 Random 构造函数?使用相同的种子,您将始终获得相同的结果序列。
参见: