模擬 Java InputStream 對象
一、簡介
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
。我們了解了類是如何構建的,以及我們可以在各種場景中使用哪些實現。最後,我們學習瞭如何編寫自己的實現來動態生成數據。
與往常一樣,代碼示例可在 GitHub 上獲得。