Skip to content

Latest commit

 

History

History
99 lines (76 loc) · 3.78 KB

9-3-Testing the TCP Server.md

File metadata and controls

99 lines (76 loc) · 3.78 KB

我们继续测试我们的handle_connection函数。

首先,我们需要一个TcpStream来使用。在一个端对端或者集成测试中,我们可能想要创建一个TCP链接来测试我们的代码。一个方法是监听本地的端口0。端口0不是一个无效的UNIX端口,它用作测试。操作系统将会选择一个可用的TCP端口给我们。

在这个例子中,我们将会为链接处理器编写一个单元测试,检查每个输出对应的输出是否正确。为了保持单元测试的隔离性和正确性,我们模拟一个TcpStream

首先,我们改变handle_connection的签名让它更加容易测试。handle_connection实际上并不需要async_std::net::TcpStream;它需要一个实现了async_std::io::Readasync_std::io::Writemarker::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_writepoll_flushpoll_closepoll_write将会复制传入的数据到mock的TcpStream,当完成时返回Poll::Ready。不需要为TcpStream实现flush或者close,因此在poll_flushpoll_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。更多关于PinUnpin的信息,参考前面章节。

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