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