Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate tests from json files #206

Open
la10736 opened this issue Aug 15, 2023 · 2 comments
Open

Generate tests from json files #206

la10736 opened this issue Aug 15, 2023 · 2 comments

Comments

@la10736
Copy link
Owner

la10736 commented Aug 15, 2023

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.

  • #[json(glob_path)]: function attribute to identify the json files. All following serde attributes will be applied to the struct used to deserialize data for test if any.
  • #[field]: argument attribute that we should deserialize from the json, the optional argument name can be used to map the json field to this argument. In this case all serde's attribute of this field will be used as field's attribute in deserialized struct.
  • #[data]: argument attribute to define one argument where the type implement serde::Deserialize and can be deserialized from the json object in the files

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
Copy link
Owner Author

la10736 commented Dec 24, 2023

  • align to master (relative path)
  • use SysEngine in files_arg too and rewrite tests to use mockall
  • use relative path in files
  • implement render
  • E2E tests
  • Docs
  • ? syntax for json_path ?

@la10736
Copy link
Owner Author

la10736 commented Jan 21, 2024

The proposed rendering is just wrong 😢 . 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);
                }
            }
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant