Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TCPサーバーのテスト

handle_connection 関数のテストに進みましょう。

まず、作業対象となる TcpStream が必要です。 エンドツーエンドテストや統合テストでは、コードをテストするために実際の TCP 接続を確立したい場合があります。 これを行う戦略の 1 つは、localhost のポート 0 でリスナーを開始することです。 ポート 0 は有効な UNIX ポートではありませんが、テストには利用できます。 オペレーティングシステムが空いている TCP ポートを選択してくれます。

代わりに、この例では接続ハンドラーの単体テストを書き、 それぞれの入力に対して正しいレスポンスが返されることを確認します。 単体テストを分離され、決定的なものに保つために、TcpStream をモックに置き換えます。

まず、テストしやすくするために handle_connection のシグネチャを変更します。 handle_connection は実際には async_std::net::TcpStream を必要としません。 必要なのは、async_std::io::Readasync_std::io::Write、および marker::Unpin を実装する任意の構造体です。 これを反映するように型シグネチャを変更すると、テスト用にモックを渡せるようになります。

use async_std::io::{Read, Write};

async fn handle_connection(mut stream: impl Read + Write + Unpin) {

次に、これらのトレイトを実装するモックの TcpStream を構築しましょう。 まず、1 つのメソッド poll_read を持つ 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_writepoll_flushpoll_close の 3 つのメソッドを書く必要があります。 poll_write は入力データをモックの TcpStream にコピーし、完了したら Poll::Ready を返します。 モックの TcpStream をフラッシュまたはクローズするために行うべき処理はないため、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);

            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 をセットアップした後、 #[async_std::main] を使用した方法と同様に、属性 #[async_std::test] を使用して handle_connection を実行できます。 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()));
    }