🚧 Attention, peinture fraîche !

Cette page a été traduite par une seule personne et n'a pas été relue et vérifiée par quelqu'un d'autre ! Les informations peuvent par exemple être erronées, être formulées maladroitement, ou contenir d'autres types de fautes.

Test du serveur TCP

Passons désormais au test de notre fonction gestion_connexion.

D'abord, nous avons besoin d'un TcpStream avec lequel travailler. Dans un test d'intégration ou de bout-à-bout, nous serions tentés de faire une vraie connexion TCP pour tester notre code. Une des stratégies pour faire cela est de démarrer un écouteur sur le port 0 de localhost. Le port 0 n'est pas un port UNIX valide, mais il fonctionne pour faire des tests. Le système d'exploitation va obtenir un port TCP ouvert pour nous.

Dans cet exemple, nous allons plutôt écrire un test unitaire pour le gestionnaire de connexion, pour vérifier que les réponses correctes soient retournées à leurs entrées respectives. Pour faire en sorte que notre test unitaire soit isolé et déterminé, nous allons remplacer le TcpStream par un mock.

Pour commencer, nous allons changer la signature de gestion_connexion pour faciliter ses tests. En fait, gestion_connexion ne nécessite pas de async_std::net::TcpStream, il a juste besoin d'une structure qui implémente async_std::io::Read, async_std::io::Write, et marker::Unpin. Changeons la signature du type dans ce sens nous permet de lui passer un mock pour la tester.

use std::marker::Unpin;
use async_std::io::{Read, Write};

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

Ensuite, créons un mock de TcpStream qui implémente ces traits. Implémentons d'abord le trait Read, qui a une méthode, poll_read. Notre mock de TcpStream va contenir certaines données qui sont copiées dans le tampon de lecture, et nous allons retourner Poll::Ready pour signaler que la lecture est terminée.

    use super::*;
    use futures::io::Error;
    use futures::task::{Context, Poll};

    use std::cmp::min;
    use std::pin::Pin;

    struct MockTcpStream {
        donnees_lecture: Vec<u8>,
        donnees_ecriture: Vec<u8>,
    }

    impl Read for MockTcpStream {
        fn poll_read(
            self: Pin<&mut Self>,
            _: &mut Context,
            tampon: &mut [u8],
        ) -> Poll<Result<usize, Error>> {
            let taille: usize = min(self.donnees_lecture.len(), tampon.len());
            tampon[..taille].copy_from_slice(&self.donnees_lecture[..taille]);
            Poll::Ready(Ok(taille))
        }
    }

Notre implémentation de Write y ressemble beaucoup, même si nous avons besoin d'écrire trois méthodes : poll_write, poll_flush, et poll_close. poll_write va copier toutes les données d'entrée dans le mock de TcpStream, et retourne Poll::Ready lorsqu'elle sera terminée. Il n'y a pas besoin de purger et fermer le mock de TcpStream, donc poll_flush et poll_close peuvent simplement retourner Poll::Ready.

    impl Write for MockTcpStream {
        fn poll_write(
            mut self: Pin<&mut Self>,
            _: &mut Context,
            tampon: &[u8],
        ) -> Poll<Result<usize, Error>> {
            self.donnees_ecriture = Vec::from(tampon);

            Poll::Ready(Ok(tampon.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(()))
        }
    }

Enfin, notre mock a besoin d'implémenter Unpin, ce qui veut dire que sa position dans la mémoire peut être déplacée en toute sécurité. Pour plus d'informations sur l'épinglage et le trait Unpin, rendez-vous à la section sur l'épinglage.

    use std::marker::Unpin;
    impl Unpin for MockTcpStream {}

Maintenant nous sommes prêts à tester la fonction gestion_connexion. Après avoir réglé le MockTcpStream pour contenir les données initiales, nous pouvons exécuter gestion_connexion en utilisant l'attribut #[async_std::test], de la même manière que nous avons utilisé #[async_std::main]. Pour nous assurer que gestion_connexion fonctionne comme attendu, nous allons vérifier que les données ont été correctement écrites dans le MockTcpStream en fonction de son contenu initial.

    use std::fs;

    #[async_std::test]
    async fn test_gestion_connexion() {
        let octets_entree = b"GET / HTTP/1.1\r\n";
        let mut contenu = vec![0u8; 1024];
        contenu[..octets_entree.len()].clone_from_slice(octets_entree);
        let mut flux = MockTcpStream {
            donnees_lecture: contenu,
            donnees_ecriture: Vec::new(),
        };

        gestion_connexion(&mut flux).await;
        let mut tampon = [0u8; 1024];
        flux.read(&mut tampon).await.unwrap();

        let contenu_attendu = fs::read_to_string("hello.html").unwrap();
        let reponse_attendue = format!("HTTP/1.1 200 OK\r\n\r\n{}", contenu_attendu);
        assert!(flux.donnees_ecriture.starts_with(reponse_attendue.as_bytes()));
    }