How do you reduce duplication between documentation examples and tests

Good example documentation, automated tests, and no duplication. Pick any two.

That seems to be the problem I run into every now and then. When writing code examples for crate documentation, I tend to duplicate certain code, especially when the test code is good enough to be user facing documentation. A naive example:

lib.rs:

//! Some description
//!
//! # Examples
//!
//! Easy usage:
//!
//! ```rust
//! # #[derive(new)]
//! # struct DummyParamType;
//! #
//! # fn main() {
//! let param_type = DummyParamType::new();
//!
//! assert!(CrateType::do_something(param_type).is_ok());
//! # }
//!
//! Complex usage:
//!
//! ```rust
//! # #[derive(new)]
//! # struct DummyParamType;
//! #
//! # fn main() {
//! let param_type = DummyParamType::new();
//!
//! assert!(CrateType::do_something_else(param_type).is_ok());
//! # }
//! ```

pub use crate_type::CrateType;

mod crate_type;

crate_type.rs

struct CrateType;
impl CrateType {
    fn do_something<T>(_param: T) -> Result<(), ()> { Ok(()) }
    fn do_something_else<T>(_param: T) -> Result<(), ()> { Ok(()) }
}

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

    #[test]
    fn easy_usage() {
        assert!(CrateType::do_something(param_type).is_ok());
    }

    #[test]
    fn complex_usage() {
        assert!(CrateType::do_something_else(param_type).is_ok());
    }

    #[derive(new)]
    struct DummyParamType;
}

These things are duplicated:

  • Declaration / definition of DummyParamType across examples.
    The boilerplate isn't just dummy types, but also import statements, and it becomes very noisy when the types come from other crates and are non-trivial to set up.
  • Declaration / definition of DummyParamType across example + tests
  • assert!(...); across example + tests

I can't think of a way with the current tools to have a win-win-win approach:

  • Link to the test code in the documentation -> Team Good Docs loses
  • Copy the types / usage -> Team No Dupes loses
  • Don't test what you don't put in examples -> Team Auto Test loses
    You can still end up with duplication / messier docs depending on whether you choose either "duplicate types in each example" or "Make a giant code block"
  • Use rust,ignore in examples -> Team Auto Test loses)

How do you address this?

1 Like

I'm probably missing something, but aren't Doc Examples automatically compiled and tested as well? Wouldn't that already cover your needs?

Or is there something specific about test-tests that you would miss in doc-tests?

Yeap the doc examples are tested, but each code block is a clean slate, which means the supporting code (imports, supporting types and boilerplate setup) which are not interesting in the example have to be duplicated, which is somewhat painful when there is a lot of it — it's code noise when skimming and increases maintenance effort when the code has to be updated.

The ability to hide boilerplate using //! # code is a consolation though :slightly_smiling_face:

1 Like