la10736 / rstest

Fixture-based test framework for Rust
Apache License 2.0
1.21k stars 43 forks source link

Generate tests from json files #206

Open la10736 opened 1 year ago

la10736 commented 1 year ago

The big picture here is to generate tests from some kind of serialization protocols. But I would start from a narrow case to expand it in a future.

The idea is define a glob path to get the json files that can contains one object or an array of objects. And write a test for each object.

In this ticket we manage just an MVP, later we'll extend it.

Minimal Example

file in resources/test/people.json

[{
    "name": "John Doe",
    "age": 23
},
{
    "name": "Andy Brown",
    "age": 45
}]
use serde::Deserialize;
use rstest::rstest;

#[rstest]
#[json("resources/tests/*.json")]
fn age_is_greater_eq_than_18(#[field] age: u16) {
    assert!(age>=18);
}

#[rstest]
#[json("resources/tests/*.json")]
fn use_remap(#[field(age)] a: u16) {
    assert!(a>=18);
}

#[rstest]
#[json("resources/tests/*.json")]
fn use_remap_from_serde(#[field] #[serde(rename = "age")] a: u16) {
    assert!(a>=18);
}

#[derive(Deserialize)]
struct User {
    name: String,
    age: u16,
}

#[rstest]
#[json("resources/tests/*.json")]
fn use_obj(#[data] user: User) {
    assert!(user.age>=18);
}

More Info

  1. Tests will be in a modules hierarchy that reflect the file path from the crate root (previous folder will be indicated by _UP): in this case the functions path resources::tests::people_json::age_is_greater_eq_than_18 and the names are data_1 and data_2. If json contains a sigle root object the last module is omitted and its name will be used as names.
  2. You can use just one #[json] attribute for a function
  3. Should work also with #[values] and #[case]
la10736 commented 11 months ago
la10736 commented 10 months ago

The proposed rendering is just wrong :cry: . In order to avoid modules name duplication every rendering should start with the function name. So, in the previous described case we'll have something like the the following expanded code (some details are omitted):

mod age_is_greater_eq_than_18 {
    use super::*;
    #[derive(Deserialize)]
    struct __JsonObjDeserialized {
        age: u16
    }
    mod resources {
        use super::*;
        mod tests {
            use super::*;
            mod people_json {
                use super::*;
                #[test]
                fn data_1() {build 
                        let __JsonObjDeserialized {
                            age
                        } = serde_json::from_str(#"{
                                  "name": "John Doe",
                                  "age": 23
                              }"#
                        ).expect(#"I cannot deserialize '"{
                                  "name": "John Doe",
                                  "age": 23
                              }"' to extract fields"#);
                       assert!(age>=18);
                }

                #[test]
                fn data_2() {build 
                        let __JsonObjDeserialized {
                            age
                        } = serde_json::from_str(#"{
                                  "name": "Andy Brown",
                                  "age": 45
                              }"#
                        ).expect(#"I cannot deserialize '"{
                                   "name": "Andy Brown",
                                   "age": 45
                              }"' to extract fields"#);
                       assert!(age>=18);
                }
            }
        }
    }
}

mod use_remap {
    use super::*;
    #[derive(Deserialize)]
    struct __JsonObjDeserialized {
        #[serde(rename = "age")]
        a: u16
    }
    /// Rest of the code is like the previous case where use `a` instead of `age` in the test
}

mod use_remap_from_serde {
    /// The same code like in `use_remap`
}

mod use_object {
    mod resources {
        use super::*;
        mod tests {
            use super::*;
            mod people_json {
                use super::*;
                #[test]
                fn data_1() {build 
                        let user = serde_json::from_str(#"{
                                  "name": "John Doe",
                                  "age": 23
                              }"#
                        ).expect(#"I cannot deserialize '"{
                                  "name": "John Doe",
                                  "age": 23
                              }"'"#);
                       assert!(user.age>=18);
                }

                #[test]
                fn data_2() {build 
                        let user = serde_json::from_str(#"{
                                  "name": "Andy Brown",
                                  "age": 45
                              }"#
                        ).expect(#"I cannot deserialize '"{
                                   "name": "Andy Brown",
                                   "age": 45
                              }"'"#);
                       assert!(user.age>=18);
                }
            }
        }
    }
}