2 votes

Comment vérifier si une fonction a été appelée en Rust ?

J'ai une fonction comme suit

pub fn registration(student_id: &T::StudentId, registrar: &T::RegistrarID) {
    // More code here.
    if num_of_students < student_limit {
        Self::function_one(&registrar, &num_of_students);
    } else {
        Self::function_two(&num_of_students);
    }
}

Dans les tests unitaires, je prévois de vérifier si function_one o function_two a été appelé.

#[test]
fn registration_more_students_should_call_functon_one() {
    with_test_data(
        &mut TestBuilder::default().num_of_students(1000).build(),
        || {
            //assert_called!(module_name::registration("TV:009", "DF-000-09"));
        },
    );
}

Comment puis-je tester si une fonction a été appelée en Rust ?

11voto

Shepmaster Points 1732

Alerte aux opinions fortes : vous faites mal vos tests . Ceci est du même ordre que "comment tester une méthode privée". Vous ne devriez pas vous soucier de l'implémentation de registration à ce niveau de détail.

Ceci étant dit, si c'est vraiment important de savoir quelle if est prise, alors utilisez l'injection de dépendances :

fn registration(mut registration: impl Registration, registrar: i32) {
    let num_of_students = 0;
    let student_limit = 0;

    if num_of_students < student_limit {
        registration.function_one(registrar, num_of_students);
    } else {
        registration.function_two(num_of_students);
    }
}

trait Registration {
    fn function_one(&mut self, registrar: i32, num_of_students: i32);
    fn function_two(&mut self, num_of_students: i32);
}

impl<R: Registration> Registration for &'_ mut R {
    fn function_one(&mut self, registrar: i32, num_of_students: i32) {
        (**self).function_one(registrar, num_of_students)
    }
    fn function_two(&mut self, num_of_students: i32) {
        (**self).function_two(num_of_students)
    }
}

/*
// An example implementation for production
struct DatabaseRegistration;

impl Registration for DatabaseRegistration {
    fn function_one(&mut self, registrar: i32, num_of_students: i32) {
        eprintln!("Do DB work: {}, {}", registrar, num_of_students)
    }
    fn function_two(&mut self, num_of_students: i32) {
        eprintln!("Do DB work: {}", num_of_students)
    }
}
*/

#[cfg(test)]
mod test {
    use super::*;

    #[derive(Debug, Copy, Clone, Default)]
    struct TestRegistration {
        calls_to_one: usize,
        calls_to_two: usize,
    }

    impl Registration for TestRegistration {
        fn function_one(&mut self, _: i32, _: i32) {
            self.calls_to_one += 1;
        }
        fn function_two(&mut self, _: i32) {
            self.calls_to_two += 1;
        }
    }

    #[test]
    fn calls_the_right_one() {
        let mut reg = TestRegistration::default();
        registration(&mut reg, 42);
        assert_eq!(1, reg.calls_to_two)
    }
}

Une fois que vous avez fait cela, alors vous pouvez voir que registration appelle la fonction de trait appropriée (comme indiqué dans l'exemple de test).

Cela évite que votre code de production ne soit parsemé de détritus spécifiques aux tests, tout en vous donnant la possibilité d'être plus flexible et de tester plus de cas rapidement.

Voir aussi :

6voto

Andrey Tyukin Points 29032

Voici une tentative naïve utilisant #[cfg(test)] à plusieurs endroits :

struct Registration {
    students: Vec<String>,
    #[cfg(test)]
    function_1_called: bool,
}

impl Registration {
    fn new() -> Self {
        Registration {
            students: Vec::new(),
            #[cfg(test)]
            function_1_called: false,
        }
    }

    fn function_1(&mut self, students: Vec<String>) {
        self.students.extend(students);
        #[cfg(test)]
        {
            self.function_1_called = true;
        }
    }

    fn function_2(&mut self, students: Vec<String>) {}

    fn f(&mut self, students: Vec<String>) {
        if students.len() < 100 {
            self.function_1(students);
        } else {
            self.function_2(students);
        }
    }
}

fn main() {
    println!("Hello, world!");
    let r = Registration::new();
    // won't compile during `run`:
    // println!("{}", r.function_1_called);
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_f() {
        let mut r = Registration::new();
        r.function_1(vec!["Alice".to_string(), "Bob".to_string()]);
        assert!(r.function_1_called);
    }
}

Le code est vaguement basé sur les extraits que vous avez fournis. Il y a un Registration qui contient une liste de noms d'élèves, deux méthodes function_1 y function_2 pour l'inscription des étudiants, et une fonction f qui choisit entre function_1 y function_2 en fonction du nombre d'étudiants.

Pendant les tests, Registration est compilé avec une variable booléenne supplémentaire function_1_called . Cette variable n'est définie que si function_1 est appelé (le bloc de code qui le fait est également marqué par le symbole #[cfg(test)] ).

En combinaison, cela rend un drapeau booléen supplémentaire disponible pendant les tests, de sorte que vous pouvez faire des assertions comme celle qui suit :

assert!(r.function_1_called);

Évidemment, cela pourrait fonctionner pour des structures beaucoup plus compliquées qu'un simple drapeau booléen (ce qui ne signifie pas du tout que vous devriez le faire).

Je ne peux pas dire si cela est idiomatique dans Rust ou non. Toute la configuration semble devoir être cachée derrière des macros fantaisistes, donc si ce style de test est utilisé dans Rust, il devrait déjà y avoir des crates qui fournissent ces macros (ou des macros similaires).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X