我们继续测试我们的handle_connection
函数。
首先,我们需要一个TcpStream
来使用。在一个端对端或者集成测试中,我们可能想要创建一个TCP链接来测试我们的代码。一个方法是监听本地的端口0。端口0不是一个无效的UNIX端口,它用作测试。操作系统将会选择一个可用的TCP端口给我们。
在这个例子中,我们将会为链接处理器编写一个单元测试,检查每个输出对应的输出是否正确。为了保持单元测试的隔离性和正确性,我们模拟一个TcpStream
。
首先,我们改变handle_connection
的签名让它更加容易测试。handle_connection
实际上并不需要async_std::net::TcpStream
;它需要一个实现了async_std::io::Read
和async_std::io::Write
和marker::Unpin
的任何结构体。修改参数签名以便我们可以传递一个mock用于测试。
use std::marker::Unpin;
use async_std::io::{Read, Write};
async fn handle_connection(mut stream: impl Read + Write + Unpin) {
接下来,我们mock一个实现了这些trait的TcpStream
。首先,我们实现Read
trait的poll_read
方法。我们mock的TcpStream
将包含一些被拷贝进read buffer的数据,我们将会返回Poll::Ready
,以此表示读取完毕。
use super::*;
use futures::io::Error;
use futures::task::{Context, Poll};
use std::cmp::min;
use std::pin::Pin;
struct MockTcpStream {
read_data: Vec<u8>,
write_data: Vec<u8>,
}
impl Read for MockTcpStream {
fn poll_read(
self: Pin<&mut Self>,
_: &mut Context,
buf: &mut [u8],
) -> Poll<Result<usize, Error>> {
let size: usize = min(self.read_data.len(), buf.len());
buf[..size].copy_from_slice(&self.read_data[..size]);
Poll::Ready(Ok(size))
}
}
我们实现的Write
非常类似,虽然我们需要写三个方法:poll_write
,poll_flush
,poll_close
。poll_write
将会复制传入的数据到mock的TcpStream
,当完成时返回Poll::Ready
。不需要为TcpStream
实现flush
或者close
,因此在poll_flush
和poll_close
直接返回Poll::Ready
。
impl Write for MockTcpStream {
fn poll_write(
mut self: Pin<&mut Self>,
_: &mut Context,
buf: &[u8],
) -> Poll<Result<usize, Error>> {
self.write_data = Vec::from(buf);
return Poll::Ready(Ok(buf.len()));
}
fn poll_flush(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Error>> {
Poll::Ready(Ok(()))
}
fn poll_close(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Error>> {
Poll::Ready(Ok(()))
}
}
最后,我们的mock需要实现Unpin
,表示它在内存的位置可以安全地move。更多关于Pin
和Unpin
的信息,参考前面章节。
use std::marker::Unpin;
impl Unpin for MockTcpStream {}
现在,我们已经准备测试handle_connection
函数。设置包含一些初始化信息的MockTcpStream
之后,我们可以通过属性#[async_std::test]
来运行handle_connection
,这与我们使用#[async_std::main]
类似。为了确保handle_connection
如预期一样运行,我们将检查基于它初始的文本是否写入了MockTcpStream
。
use std::fs;
#[async_std::test]
async fn test_handle_connection() {
let input_bytes = b"GET / HTTP/1.1\r\n";
let mut contents = vec![0u8; 1024];
contents[..input_bytes.len()].clone_from_slice(input_bytes);
let mut stream = MockTcpStream {
read_data: contents,
write_data: Vec::new(),
};
handle_connection(&mut stream).await;
let mut buf = [0u8; 1024];
stream.read(&mut buf).await.unwrap();
let expected_contents = fs::read_to_string("hello.html").unwrap();
let expected_response = format!("HTTP/1.1 200 OK\r\n\r\n{}", expected_contents);
assert!(stream.write_data.starts_with(expected_response.as_bytes()));
}