Тестирование TCP-сервера
Давайте перейдём к тестированию нашей функции handle_connection.
Во-первых, нам нужен TcpStream для работы. В сквозном или интеграционном тесте мы можем захотеть установить реальное TCP-соединение для проверки нашего кода. Одна из стратегий для этого — запустить приложение на порту 0 localhost. Порт 0 не является допустимым портом UNIX, но он подойдёт для тестирования. Операционная система выберет для нас открытый порт TCP.
Вместо этого в этом примере мы напишем модульный тест для обработчика соединения, чтобы проверить, что для входных данных возвращаются правильные ответы. Чтобы наш модульный тест оставался изолированным и детерминированным, мы замокаем TcpStream.
Для начала, мы изменим сигнатуру handle_connection, чтобы упростить тестирование. handle_connection на самом деле не требует async_std::net::TcpStream, а требует любую структуру, которая реализует async_std::io::Read, async_std::io::Write и marker::Unpin. Изменив сигнатуру типа таким образом, мы сможем передать мок для тестирования.
use async_std::io::{Read, Write};
async fn handle_connection(mut stream: impl Read + Write + Unpin) {
Далее давайте создадим мок TcpStream, который реализует нужные типажи. Во-первых, давайте реализуем типаж Read с методом poll_read. Наш мок TcpStream будет содержать некоторые данные, которые копируются в буфер чтения, и мы вернём 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 скопирует входные данные в мок TcpStream и вернёт Poll::Ready после завершения. Для сброса или закрытия мока TcpStream не требуется никакой работы, поэтому 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);
            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(()))
        }
    }
Наконец, нашему моку нужно будет реализовать Unpin, что означает, что его местоположение в памяти может быть безопасно перемещено. Для получения дополнительной информации о закреплении и Unpin см. раздел о закреплении .
    impl Unpin for MockTcpStream {}
Теперь мы готовы протестировать функцию handle_connection. После настройки MockTcpStream, содержащего некоторые начальные данные, мы можем запустить handle_connection, используя атрибут #[async_std::test], аналогично тому, как мы использовали #[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 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()));
    }