简体   繁体   English

如何测试其他语句?

[英]How to test if else statements?

i have been struggling with testing the if else and for loops here is the example can you pleas guide me how does the automated testing actually work for these statements? 我一直在努力测试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!");

    } 

Table of Contents 目录

  • Step 0 - refactor the code to workable, compilable example 步骤0-将代码重构为可行的可编译示例
  • Step 1 - refactor to be able to do dependency injection 第1步-重构以便能够进行依赖注入
  • Step 2 - wrap java.util.Scanner into your own delegate 第2步-将java.util.Scanner包装到您自己的委托中
  • Step 3 - create one general test 步骤3-建立一项一般测试
  • Step 4 - extract handling single attempt from the loop 第4步-从循环中提取处理单次尝试
  • Step 5 - create unit test for Game 第5步-为游戏创建单元测试
  • Step 6 - create unit test for SingleGuessHandler 第6步-为SingleGuessHandler创建单元测试
  • Final notes 最后的笔记

Step 0 - refactor the code to workable, compilable example 步骤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!");
            }
        }
    }

}

Step 1 - refactor to be able to do dependency injection 第1步-重构以便能够进行依赖注入

All of these operations should be done with IDE refactoring - move method, extract to field, create constructor, move initializer to constructor, extract to parameter etc. 所有这些操作都应通过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!");
            }
        }
    }

}

Step 2 - wrap java.util.Scanner into your own delegate 第2步-将java.util.Scanner包装到您自己的委托中

Trying to mock java.util.Scanner you will quickly find out that class is final, so we create a simple wrapper and replace java.util.Scanner with our one in Main and Game classes. 尝试模拟java.util.Scanner您会很快发现该类是最终的,因此我们创建了一个简单的包装器,并在MainGame类中将java.util.Scanner替换为我们的类。

public class MyScanner {

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

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

}

Step 3 - create one general test 步骤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!");
    }

}

Step 4 - extract handling single attempt from the loop 第4步-从循环中提取处理单次尝试

In the loop there are 3 possible flows and we have 10 iterations. 在循环中,有3种可能的流程,并且我们有10次迭代。 This gives in total 3^10 ~ 60.000 possible flows. 这总共提供了3 ^ 10〜60.000个可能的流。 And we don't want to write 60 thousand tests. 而且我们不想编写6万个测试。 So we refactor again (using only IDE refactoring options) to get to the following state. 因此,我们再次重构(仅使用IDE重构选项)以达到以下状态。 Note, all should compile and the test should be still green. 请注意,所有程序均应编译,并且测试应仍为绿色。

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!");
    }

}

Step 5 - create unit test for Game 第5步-为游戏创建单元测试

Our existing GameTest tests together a couple of classes but now we want to test only the interaction of Game and SingleGuessHandler . 我们现有的GameTest可以一起测试几个类,但是现在我们只想测试GameSingleGuessHandler的交互。 At this point, I have realised that your original code didn't contain any logic to handle breaking the loop in case of correct guess. 至此,我已经意识到,您的原始代码不包含任何逻辑来处理在正确猜测的情况下中断循环的情况。 This makes below unit test much simpler, otherwise runSingleGuess would have to return true/false value (indicating whether to call it again) and we would have to write 1 more test to check that if it returns false , then it is not called again. 这使得下面的单元测试更加简单,否则runSingleGuess将必须返回true / false值(指示是否再次调用它),并且我们将不得不再编写1个测试以检查它是否返回false ,然后才不再调用它。 So in short: 简而言之:

  • 1 test for failure - loop iterated 10 times 1次测试失败-循环迭代10次
  • 1 test for success - early termination of the loop (I will not do this since it was not in your original code) 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();
}

Step 6 - create unit test for SingleGuessHandler 第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();
    }

}

Final notes 最后的笔记

Please note that classes MyScanner and Main are untested. 请注意, MyScannerMain类未经测试。 Testing them would require to use real java.util.Scanner and real System.out print stream. 测试它们将需要使用真实的java.util.Scanner和真实的System.out打印流。 This is a type of acceptance test (everything is real, no fakes or mocks) and in this case would be probably best achievable by running the java application as separate process and feeding it numbers and checking what it outputs. 这是一种验收测试(一切都是真实的,没有伪造或嘲笑),在这种情况下,最好通过将Java应用程序作为单独的进程运行并提供其编号并检查其输出结果来实现。

It would be good idea to test the range of the number that is being guessed. 测试要猜测的数字范围是个好主意。 I would do it by extracting new Random().nextInt(100) into a class eg RandomNumberProvider . 我可以通过将new Random().nextInt(100)提取到一个类(例如RandomNumberProvider Then all that's left is: 然后剩下的就是:

  • adding it as dependency to Game 将其添加为对Game依赖
  • adding a test for Game that Game passes to SingleGuessHandler whatever provider returned 添加一个针对Game的测试,无论提供者返回什么, Game将传递给SingleGuessHandler
  • adding a test for RandomNumberProvider - either property test or by mocking Random RandomNumberProvider添加测试-属性测试或通过模拟Random

Also note that when different tests are calling the same method, different values are used. 还要注意,当不同的测试调用相同的方法时,将使用不同的值。 It prevents accidental hardcoding of these values in the implementation. 它可以防止在实现中对这些值进行意外的硬编码。

Extract a "pure function" that captures the logic of your code, then testing will be trivial. 提取一个捕获代码逻辑的“纯函数”,然后进行测试就很简单了。

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!";
 }

Extract a function guess() and pass guess and number . 提取一个函数guess()并传递guessnumber Make it return your result string or an abstract value representing it. 使它返回结果字符串或代表它的抽象值。

Encapsulate the function and the counter in a new class. 将函数和计数器封装在一个新类中。 Each time you guess() , increment it. 每次您guess() ,将其递增。 Disable guessing when you've reached 10 guesses. 达到10个猜测时禁用猜测。

Now you control the inputs and outputs of your code and can test it properly without having to rely on side effects (console in/out). 现在,您可以控制代码的输入和输出,并且可以正确测试它,而不必依赖副作用(控制台输入/输出)。

You just write three different asserts that satisfy the different conditions.You also need to put it in some sort of function. 您只需编写满足不同条件的三个不同的断言,还需要将其放入某种函数中。 I've never tried to unit test a main function but I guess you could do it that way, but usually it is in a function that returns some value. 我从未尝试过对主函数进行单元测试,但是我想您可以这样做,但是通常它是在返回一些值的函数中进行的。 I'm not sure how you would test the print statement. 我不确定您将如何测试打印语句。 Perhaps you can put the text in a variable and then print that. 也许您可以将文本放在变量中,然后打印出来。 It would look something like this. 看起来像这样。

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);
}

Be aware that I haven't tested any of this code, this is just the general idea as to how you would set things up. 请注意,我还没有测试过任何代码,这只是关于如何进行设置的一般思路。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM