模拟用户输入并检查输出的简单方法

最近,我的一些学生向我询问了赫尔辛基大学MOOC提供的单元测试的机制,我检查了它们的实现,并认为这对于初学者了解实际发生的情况是有帮助的,因此在此发表了这篇小文章。

我们将以“机场”项目为例,这是OOP2第一周的最后一项任务。

我们仅关注测试,因此我将跳过有关如何解决它的事情。 在本练习中,我们将每次手动执行main方法,重复输入飞机编号,容量,有时我们认为我们的代码可以工作,然后运行本地测试,以便可以提交给服务器进行在线判断和评分。

用户输入

我一直使用这个小项目作为借助单元测试保护的重构示例。 当我又重复又痛苦地输入飞机ID,航班号,机场代码和操作代码时,我问我的学生:“这很痛苦吗?”。

显然,他们所有人都回答了。 然后我问,“即使无聊又痛苦,您会一次又一次地进行这种测试吗?”

安静。

从我过去的经验中,我知道跳过这些无聊的测试很容易,并且我们可以安慰自己,“这些代码非常简单,我不会犯错,它将起作用并且会起作用,不用担心。”

由于做出这样的选择,我会产生痛苦的回忆,因为过去我犯了太多简单而愚蠢的错误,所以无论看起来多么简单,我仍然会进行测试-即使是手动测试,也无聊又痛苦。

我添加此内容是因为单元测试无法完全替代手动测试,尽管它将使手动测试更加容易和有效。

对于Airport项目,如果不需要每次都重复输入,并且可以捕获程序的输出,则与预期相比,我们将更快地获得反馈。

String operation = scanner.nextLine();
...
System.out.println("Blahblahblah...");

例如,我们确切地知道是否首先输入x ,然后它将进入飞行服务部分并打印菜单选项;如果我们第二次输入x ,则程序将结束循环并退出,结果,我们将仅获取机场面板和飞行服务的说明输出。

因此,让我们转到一个测试用例,看看实际会发生什么。

@Test
public void printsMenusAndExits() throws Throwable {
    String syote = "x\nx\n";
    MockInOut io = new MockInOut(syote);
    suorita(f(syote));

    String[] menuRivit = {
        "Airport panel",
        "[1] Add airplane",
        "[2] Add flight",
        "[x] Exit",
        "Flight service",
        "[1] Print planes",
        "[2] Print flights",
        "[3] Print plane info",
        "[x] Quit"
    };

    String output = io.getOutput();
    String op = output;
    for (String menuRivi : menuRivit) {
        int ind = op.indexOf(menuRivi);
        assertRight(menuRivi, syote, output, ind > -1);
        op = op.substring(ind + 1);
    }
}

上面是第二个测试用例,它涵盖了我们所说的最简单的情况,仅输入两个x

当我们查看测试代码时,它分为三部分:

  • 准备输入
  • 执行Main.main(args)方法
  • 检查输出以查看它是否依次包含所有预期行

您知道scanner.nextLine()scanner.nextInt()的正常行为。 该程序将挂起并等待用户输入,以便执行下一行代码。 但是,为什么它在这里没有任何等待就可以顺利运行?

在开始这一部分之前,我想简要解释一下该方法的执行,它使用Java反射以一种不直接但可以进行更多检查的方式来调用该方法,例如,第一个测试用例要求Main为公共类,但您可能会发现要通过手动测试,可以将Main访问级别设置为package。

@Test
public void classIsPublic() {
    assertTrue("Class " + klassName + " should be public, so it must be defined as\n" +
        "public class " + klassName + " {...\n}", klass.isPublic());
}

这里klass.isPublic()正在检查是否根据需要设置访问级别。

好。 看来MockInOut类使魔术发生了,我们可以检查代码以在MockInOut找到想法。 您可以在GitHub上访问源代码。

public MockInOut(String input) {
    orig = System.out;
    irig = System.in;

    os = new ByteArrayOutputStream();
    try {
        System.setOut(new PrintStream(os, false, charset.name()));
    } catch (UnsupportedEncodingException ex) {
        throw new RuntimeException(ex);
    }

    is = new ByteArrayInputStream(input.getBytes());
    System.setIn(is);
}

您可能已经输入System.out数千次,但是您是否意识到可以像上面一样默默地更改out ? 这同时设置outin系统的,这样我们就可以完全执行后得到的输出,我们也不需要手工输入这个时候,因为在声明Scanner scanner = new Scanner(System.in); ,则参数System.in会以无提示方式更改,因此scanner.nextLine()将获得准备好的输入而不会挂起。

同样,输出将不会在控制台中打印,而是会累积到ByteArrayOutputStream ,此后可以访问。

您可能想知道,如果我们真的要恢复System.inSystem.out的正常行为,该怎么办?

/**
 * Restores System.in and System.out
 */
public void close() {
    os = null;
    is = null;
    System.setOut(orig);
    System.setIn(irig);
}

基本上,它节省了原来inout ,需要恢复时,只需再次清除遭入侵的人,并设置他们回来,那么一切都将照常进行。

您可以在下面复制简单的示例代码以进行快速测试。

import java.io.*;
import java.util.*;

class HelloWorld {
    public static void main(String[] args) throws IOException {
        PrintStream orig = System.out;

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        System.setOut(new PrintStream(os, false, "UTF-8"));
        // Here it won't print but just accumulate
        for (int i = 0; i < 100; i++) {
            System.out.println("Hello World");
        }

        System.setOut(orig);
        // Print 100 lines of "Hello World" here since out was restored
        System.out.println(os.toString("UTF-8"));

        InputStream is = System.in;
        System.setIn(new ByteArrayInputStream("x\nx\n".getBytes()));
        Scanner scanner = new Scanner(System.in);
        // Without hang on
        System.out.println(scanner.nextLine());
        System.out.println(scanner.nextLine());
        try {
            // There are only two lines provided, so here will fail
            System.out.println(scanner.nextLine());
        } catch (NoSuchElementException e) {
            e.printStackTrace();
        }

        System.setIn(is);
        scanner = new Scanner(System.in);
        // Hang on here since `in` was restored
        System.out.println(scanner.nextLine());
    }
}

实际上,注入和替换是一种用于分离单元测试依赖关系的常用方法,这对于仅关注代码非常有用。 还有更先进,更复杂的方法来做到这一点,但在这里,我们只是想说明一个简单的方法是“黑客” inout ,这样你可以专注于你的代码,而不是inout

对于某些遗留项目,此方法可能对重构至关重要,因为太多的依赖关系使测试变得非常困难!

翻译自: https://www.javacodegeeks.com/2019/02/approach-simulate-input-check-output.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值