【问题标题】:How to test if else statements?如何测试 if else 语句?
【发布时间】:2015-11-11 12:27:43
【问题描述】:

我一直在努力测试 if else 和 for 循环,这里是示例,请您指导我自动化测试是如何为这些实际工作的 声明?

for (int i=1; i<=10; i++){                     // for loop from 1 to 10
    System.out.println(" guess "+i+ ":");
    int guess = scan.nextInt();
    //if guess is greater than number entered 
    if(guess>number)
        System.out.println("Clue: lower");
    //if guess is less than number entered 
    else if (guess<number )
        System.out.println("Clue: Higher");
    //if guess is equal than number entered 
    else if(guess==number) {
        System.out.println("Correct answer after only "+ i + " guesses – Excellent!");

    } 

【问题讨论】:

  • 请参阅我的答案以获取分步解决方案。

标签: java testing junit automated-tests mockito


【解决方案1】:

目录

  • 第 0 步 - 将代码重构为可行、可编译的示例
  • 第 1 步 - 重构以进行依赖注入
  • 第 2 步 - 将 java.util.Scanner 包装到您自己的委托中
  • 第 3 步 - 创建一个通用测试
  • 第 4 步 - 从循环中提取处理单次尝试
  • 第 5 步 - 为游戏创建单元测试
  • 第 6 步 - 为 SingleGuessHandler 创建单元测试
  • 最后的笔记

第 0 步 - 将代码重构为可行、可编译的示例

public class Main {

    public static void main(String[] args) {
        new Game().run();
    }

}

public class Game {

    public void run() {
        Scanner scan = new Scanner(System.in);
        int number = new Random().nextInt(100);

        for (int i = 1; i <= 10; i++) {
            System.out.println(" guess " + i + ":");
            int guess = scan.nextInt();
            if (guess > number) {
                System.out.println("Clue: lower");
            } else if (guess < number) {
                System.out.println("Clue: Higher");
            } else if (guess == number) {
                System.out.println("Correct answer after only " + i + " guesses – Excellent!");
            }
        }
    }

}

第 1 步 - 重构以进行依赖注入

所有这些操作都应该通过 IDE 重构来完成 - 移动方法、提取到字段、创建构造函数、将初始化程序移动到构造函数、提取到参数等。

public class Main {

    public static void main(String[] args) {
        Game game = new Game(new Scanner(System.in), System.out, new Random().nextInt(100));

        game.run();
    }

}

public class Game {

    private final Scanner scanner;
    private final PrintStream printStream;
    private final int number;

    public Game(Scanner scanner, PrintStream out, int number) {
        this.scanner = scanner;
        this.printStream = out;
        this.number = number;
    }

    public void run() {
        for (int i = 1; i <= 10; i++) {
            printStream.println(" guess " + i + ":");
            int guess = scanner.nextInt();
            if (guess > number) {
                printStream.println("Clue: lower");
            } else if (guess < number) {
                printStream.println("Clue: Higher");
            } else if (guess == number) {
                printStream.println("Correct answer after only " + i + " guesses – Excellent!");
            }
        }
    }

}

第 2 步 - 将 java.util.Scanner 包装到您自己的委托中

尝试模拟java.util.Scanner,您很快就会发现该类是final,因此我们创建了一个简单的包装器,并将java.util.Scanner 替换为Main 和Game 类中的包装器。

public class MyScanner {

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

    public int nextInt() {
        return scanner.nextInt();
    }

}

第 3 步 - 创建一个通用测试

import org.junit.Test;
import org.mockito.InOrder;

import java.io.PrintStream;

import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class GameTest {

    private final MyScanner scanner = mock(MyScanner.class);
    private final PrintStream printStream = mock(PrintStream.class);

    private final Game game = new Game(scanner, printStream, 15);

    @Test
    public void returnsCorrectOutputWhenNumberGuessedAfterThreeAttempts() {
        when(scanner.nextInt()).thenReturn(10, 20, 15);

        game.run();

        InOrder inOrder = inOrder(scanner, printStream);
        inOrder.verify(printStream).println(" guess 1:");
        inOrder.verify(scanner).nextInt();
        inOrder.verify(printStream).println("Clue: Higher");
        inOrder.verify(printStream).println(" guess 2:");
        inOrder.verify(scanner).nextInt();
        inOrder.verify(printStream).println("Clue: lower");
        inOrder.verify(printStream).println(" guess 3:");
        inOrder.verify(scanner).nextInt();
        inOrder.verify(printStream).println("Correct answer after only 3 guesses – Excellent!");
    }

}

第 4 步 - 从循环中提取处理单次尝试

在循环中有 3 个可能的流程,我们有 10 次迭代。这总共给出了 3^10 ~ 60.000 个可能的流。而且我们不想编写 60,000 个测试。所以我们再次重构(仅使用 IDE 重构选项)以达到以下状态。注意,所有都应该编译并且测试应该仍然是绿色的。

public class Main {

    public static void main(String[] args) {
        Game game = new Game(new SingleGuessHandler(new MyScanner(), System.out), new Random().nextInt(100));

        game.run();
    }

}

public class Game {

    private final SingleGuessHandler singleGuessHandler;
    private final int number;

    public Game(SingleGuessHandler singleGuessHandler, int number) {
        this.singleGuessHandler = singleGuessHandler;
        this.number = number;
    }

    public void run() {
        for (int i = 1; i <= 10; i++) {
            singleGuessHandler.runSingleGuess(i, number);
        }
    }

}

public class SingleGuessHandler {

    private final MyScanner scanner;
    private final PrintStream printStream;

    public SingleGuessHandler(MyScanner scanner, PrintStream printStream) {
        this.scanner = scanner;
        this.printStream = printStream;
    }

    public void runSingleGuess(int attempt, int number) {
        printStream.println(" guess " + attempt + ":");
        int guess = scanner.nextInt();
        if (guess > number) {
            printStream.println("Clue: lower");
        } else if (guess < number) {
            printStream.println("Clue: Higher");
        } else if (guess == number) {
            printStream.println("Correct answer after only " + attempt + " guesses – Excellent!");
        }
    }

}

public class MyScanner {

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

    public int nextInt() {
        return scanner.nextInt();
    }

}

public class GameTest {

    private final MyScanner scanner = mock(MyScanner.class);
    private final PrintStream printStream = mock(PrintStream.class);

    private final Game game = new Game(new SingleGuessHandler(scanner, printStream), 15);

    @Test
    public void returnsCorrectOutputWhenNumberGuessedAfterThreeAttempts() {
        when(scanner.nextInt()).thenReturn(10, 20, 15);

        game.run();

        InOrder inOrder = inOrder(scanner, printStream);
        inOrder.verify(printStream).println(" guess 1:");
        inOrder.verify(scanner).nextInt();
        inOrder.verify(printStream).println("Clue: Higher");
        inOrder.verify(printStream).println(" guess 2:");
        inOrder.verify(scanner).nextInt();
        inOrder.verify(printStream).println("Clue: lower");
        inOrder.verify(printStream).println(" guess 3:");
        inOrder.verify(scanner).nextInt();
        inOrder.verify(printStream).println("Correct answer after only 3 guesses – Excellent!");
    }

}

第 5 步 - 为游戏创建单元测试

我们现有的GameTest 一起测试了几个类,但现在我们只想测试Game 和SingleGuessHandler 的交互。在这一点上,我已经意识到您的原始代码不包含任何逻辑来处理在正确猜测的情况下打破循环。这使得下面的单元测试更简单,否则runSingleGuess 将不得不返回真/假值(指示是否再次调用它)并且我们必须再编写 1 个测试来检查它是否返回false,那么它是没有再叫。简而言之:

  • 1 次失败测试 - 循环迭代 10 次
  • 1 测试成功 - 循环提前终止(我不会这样做,因为它不在您的原始代码中)
@Test
public void callsSingleGuessHandlerTenTimes() {
    SingleGuessHandler singleGuessHandler = mock(SingleGuessHandler.class);
    Game game = new Game(singleGuessHandler, 17);

    game.run();

    InOrder inOrder = inOrder(singleGuessHandler);
    inOrder.verify(singleGuessHandler).runSingleGuess(1, 17);
    inOrder.verify(singleGuessHandler).runSingleGuess(2, 17);
    inOrder.verify(singleGuessHandler).runSingleGuess(3, 17);
    inOrder.verify(singleGuessHandler).runSingleGuess(4, 17);
    inOrder.verify(singleGuessHandler).runSingleGuess(5, 17);
    inOrder.verify(singleGuessHandler).runSingleGuess(6, 17);
    inOrder.verify(singleGuessHandler).runSingleGuess(7, 17);
    inOrder.verify(singleGuessHandler).runSingleGuess(8, 17);
    inOrder.verify(singleGuessHandler).runSingleGuess(9, 17);
    inOrder.verify(singleGuessHandler).runSingleGuess(10, 17);
    inOrder.verifyNoMoreInteractions();
}

第 6 步 - 为 SingleGuessHandler 创建单元测试

public class SingleGuessHandlerTest {

    private final MyScanner scanner = mock(MyScanner.class);
    private final PrintStream printStream = mock(PrintStream.class);

    private final SingleGuessHandler singleGuessHandler = new SingleGuessHandler(scanner, printStream);

    @Test
    public void printsLowerClue() {
        when(scanner.nextInt()).thenReturn(5);

        singleGuessHandler.runSingleGuess(99, 4);

        InOrder inOrder = inOrder(scanner, printStream);
        inOrder.verify(printStream).println(" guess 99:");
        inOrder.verify(scanner).nextInt();
        inOrder.verify(printStream).println("Clue: lower");
        inOrder.verifyNoMoreInteractions();
    }

    @Test
    public void printsHigherClue() {
        when(scanner.nextInt()).thenReturn(16);

        singleGuessHandler.runSingleGuess(2, 100);

        InOrder inOrder = inOrder(scanner, printStream);
        inOrder.verify(printStream).println(" guess 2:");
        inOrder.verify(scanner).nextInt();
        inOrder.verify(printStream).println("Clue: Higher");
        inOrder.verifyNoMoreInteractions();
    }

    @Test
    public void printsSuccessfulGuessMessage() {
        when(scanner.nextInt()).thenReturn(65);

        singleGuessHandler.runSingleGuess(8, 65);

        InOrder inOrder = inOrder(scanner, printStream);
        inOrder.verify(printStream).println(" guess 8:");
        inOrder.verify(scanner).nextInt();
        inOrder.verify(printStream).println("Correct answer after only 8 guesses – Excellent!");
        inOrder.verifyNoMoreInteractions();
    }

}

结语

请注意,MyScanner 和 Main 类未经测试。测试它们需要使用真实的java.util.Scanner 和真实的System.out 打印流。这是一种验收测试(一切都是真实的,没有伪造或模拟),在这种情况下,最好通过将 java 应用程序作为单独的进程运行并为其提供数字并检查其输出来实现。

最好测试被猜测数字的范围。我会通过将new Random().nextInt(100) 提取到一个类中来做到这一点,例如RandomNumberProvider。那么剩下的就是:

  • 将其作为依赖项添加到Game
  • 为Game 添加一个测试,Game 传递给SingleGuessHandler 无论提供者返回什么
  • 为RandomNumberProvider 添加测试 - 属性测试或模拟Random

还要注意,当不同的测试调用相同的方法时,会使用不同的值。它可以防止在实现中对这些值进行意外硬编码。

【讨论】:

    【解决方案2】:

    提取一个捕获代码逻辑的“纯函数”,然后测试将变得微不足道。

    static String reply(int guess, int number, int guessCount) {
        return guess > number ? "Clue: lower"
             : guess < number ? "Clue: Higher"
             : "Correct answer after only " + guessCount + " guesses -- Excellent!";
     }
    

    【讨论】:

      【解决方案3】:

      提取函数guess() 并传递guess 和number。让它返回你的结果字符串或代表它的抽象值。

      将函数和计数器封装在一个新类中。每次guess() 时,将其递增。猜到 10 次后禁用猜。

      现在您可以控制代码的输入和输出,并且可以正确地对其进行测试,而无需依赖副作用(控制台输入/输出)。

      【讨论】:

      • 你能举例说明一下吗?我对测试很陌生,谢谢
      【解决方案4】:

      你只需编写三个不同的断言来满足不同的条件。你还需要把它放在某种函数中。我从来没有尝试过对主函数进行单元测试,但我想你可以这样做,但通常它是在一个返回一些值的函数中。我不确定您将如何测试打印语句。也许您可以将文本放入变量中,然后打印出来。它看起来像这样。

      public string gameFunction(int guess){  
      String printVariable;
      for (int i=1; i<=10; i++){                     // for loop from 1 to 10
          System.out.println(" guess "+i+ ":");
          int guess = scan.nextInt();
          //if guess is greater than number entered 
          if(guess>number)
              printVariable = "Clue: lower";
              System.out.println("Clue: lower");
          //if guess is less than number entered 
          else if (guess<number )
              printVariable = "Clue: Higher";
              System.out.println("Clue: Higher");
          //if guess is equal than number entered 
          else if(guess==number) {
              printVariable = "Correct answer after only "+ i + " guesses – Excellent!";
              System.out.println("Correct answer after only "+ i + " guesses – Excellent!");
      
      return printVariable;
      } 
      }
      
      @Test
      public void testDiscreteize(){
          int guess = 1
          int number = 2
          String testPrint = "Clue: lower";
          String returnValue = game function(guess);
          assertEquals(testPrint, returnValue);
      }
      

      请注意,我没有测试任何代码,这只是关于如何设置的一般想法。

      【讨论】:

        猜你喜欢
        • 2016-08-16
        • 1970-01-01
        • 2019-04-09
        • 2021-06-07
        • 1970-01-01
        • 1970-01-01
        • 2017-02-05
        • 2016-02-04
        • 1970-01-01
        相关资源
        最近更新 更多