#[pyo3test]Expand description
A proc macro to decorate tests, which removes boilerplate code required for testing pyO3-wrapped functions within rust.
- takes a function (the “testcase”) designed to test either a
#[pyo3module]or a#[pyo3function], - imports the
pyo3moduleandpyo3functionso they are accessible to a python interpreter embedded in rust, - creates a
"call macro"for eachpyo3functionso you can easily call it, - executes the body of the testcase using an embedded python interpreter.
§Specifying the function or module to test with #[pyo3import(...)]
Add the attribute #[pyo3import(...)] between #[pyo3test] and the testcase using the
following format:
#[pyo3import(module_rustfn: from python_module import python_function)]OR#[pyo3import(module_rustfn: import python_module)]
where:
module_rustfnis the rust function identifier of the#[pymodule]python_moduleis the module name exposed to pythonpython_functionis the function name exposed to python
You can then directly call python_function!(...) or use python_module and python_function
within the testcase as described in pyo3: Calling Python functions
§Note:
- Multiple imports are possible
§“Call macros”
#[pyo3test] will automatically generate a macro for each of the python_functions imported.
The macro will have the same name as the function name exposed to python and can be called
using python_function!(). This avoids the need to use the correct .call(), .call1() or
.call2() method and then .unwrap().extract().unwrap() the result.
§Note:
- The
"call macros"will accept positional arguments as in the example below OR a tuple in the form ofpython_function!(*args)- the*is important, just as in python - The “Call macros” cannot currently cope with keyword arguments or a mixture of some positional arguments followed by *args
- The macros will
panic!if an error occurs due to incorrect argument types, missing arguments etc. - this is designed for use in tests, where panicing is the acceptable and required behaviour
§Example usage:
use pyo3::prelude::*;
use pyo3_testing::pyo3test;
#[pyfunction]
#[pyo3(name = "addone")]
fn py_addone(num: isize) -> isize {
num + 1
}
#[pymodule]
#[pyo3(name = "adders")]
fn py_adders(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(py_addone, module)?)?;
Ok(())
}
#[pyo3test]
#[pyo3import(py_adders: from adders import addone)]
fn test_pyo3test_simple_case() {
let result = addone!(1_isize);
assert_eq!(result, 2);
}
#[pyo3test]
#[pyo3import(py_adders: import adders)]
fn test_pyo3test_import_module_only() {
let result: isize = adders
.getattr("addone")
.unwrap()
.call1((1_isize,))
.unwrap()
.extract()
.unwrap();
assert_eq!(result, 2);
}