一、简介
InputStream
是用于处理数据的通用抽像类。数据可以来自非常不同的来源,但使用该类允许我们从来源中抽像出来并独立于特定来源进行处理。
但是,当我们编写测试时,我们实际上需要提供一些可靠的实现。在本教程中,我们将了解我们应该选择哪些可用的实现,或者什么时候最好自己编写。
2.InputStream
接口基础
在我们开始编写自己的代码之前,我们最好先了解一下InputStream
接口是如何构建的。幸运的是,它非常简单。要实现一个简单的InputStream,
我们只需要考虑一种方法—— **[read](https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html#read()) .**
它不接受任何参数,并以int
形式返回流的下一个字节。如果InputStream
已经结束,它返回-1,通知我们停止处理。
2.1。测试用例
在本教程中,我们将测试一种以InputStream
形式处理文本消息并返回已处理字节数的方法。它在内部做什么与我们无关;我们只会断言读取了正确的字节数:
int bytesCount = processInputStream(someInputStream); assertThat(bytesCount).isEqualTo(expectedNumberOfBytes);
2.2.使用朴素的实现
为了更好地理解InputStream
的工作原理,我们将编写一个带有硬编码消息的简单实现。除了消息之外,我们的实现还有一个索引,指向我们接下来应该阅读的消息字节。每次调用read 方法时,我们都会从消息中获取一个字节,然后增加索引。
在我们这样做之前,我们还需要检查我们是否还没有从消息中读取所有字节。如果是这样,我们需要返回-1:
int byteCount = processInputStream(new InputStream() { private final byte[] msg = "Hello World".getBytes(); private int index = 0; @Override public int read() { if (index >= msg.length) { return -1; } return msg[index++]; } }); assertThat(byteCount).isEqualTo(11);
3. 使用ByteArrayInputStream
如果我们绝对确定整个数据负载将适合内存,那么最简单的选择是ByteArrayInputStream
。我们向构造函数提供一个字节数组,然后流以与上一节中的示例类似的方式逐字节地迭代它:
String msg = "Hello World"; int bytesCount = processInputStream(new ByteArrayInputStream(msg.getBytes())); assertThat(bytesCount).isEqualTo(11);
4. 使用FileInputStream
如果我们可以将数据保存为文件,我们也可以以FileInputStream
的形式加载它。这种方法的优点是数据不会作为一个整体加载到内存中,而是在需要时从磁盘中读取。如果我们将文件放在资源文件夹中,我们可以使用方便的getResourceAsStream
方法,在一行代码中直接从路径创建InputStream
:
InputStream inputStream = MockingInputStreamUnitTest.class.getResourceAsStream("/mockinginputstreams/msg.txt"); int bytesCount = processInputStream(inputStream); assertThat(bytesCount).isEqualTo(11);
请注意,在此示例中,InputStream
的实际实现将是BufferedFileInputStream
。顾名思义,它读取更大的数据块并将它们存储在缓冲区中。因此,它限制了从磁盘读取的次数。
5. 动态生成数据
有时我们想测试我们的系统是否能在处理大量数据时正常工作。我们可以只使用从磁盘加载的大文件,但这种方法有一些严重的缺点。这不仅会浪费空间,而且像git
这样的版本控制系统不能很好地处理大型二进制文件。幸运的是,我们不需要事先拥有所有数据。相反,我们可以即时生成它。
为此,我们需要实现我们的InputStream
。让我们从定义字段和构造函数开始:
public class GeneratingInputStream extends InputStream { private final int desiredSize; private final byte[] seed; private int actualSize = 0; public GeneratingInputStream(int desiredSize, String seed) { this.desiredSize = desiredSize; this.seed = seed.getBytes(); } }
“desiredSize”变量会告诉我们何时应该停止生成数据。“种子”变量将是一个重复的数据块。最后,“actualSize”
变量将帮助我们跟踪我们返回了多少字节。我们需要它,因为我们实际上并没有保存任何数据。我们只返回“当前”字节。
使用我们定义的变量,我们可以实现read
方法:
@Override public int read() { if (actualSize >= desiredSize) { return -1; } return seed[actualSize++ % seed.length]; }
首先,我们检查是否达到了所需的大小。如果我们这样做了,我们应该返回-1 以便流的使用者知道停止读取。如果没有,我们应该从种子中返回一个字节。为了确定它应该是哪个字节,我们使用模运算符得到生成数据的实际大小除以种子长度的余数。
6.总结
在本教程中,我们研究了如何在测试中处理InputStreams
。我们了解了类是如何构建的,以及我们可以在各种场景中使用哪些实现。最后,我们学习了如何编写自己的实现来动态生成数据。
0 评论