How do I print the description of table-driven tests?

I have table-driven tests in a function. When I execute cargo test or suggestions from this page, I only see slight variations of:

running 1 test
test binary_search::package_shipping_capacity::tests::test_ship_within_days ... ok

There are five (sub?) tests in this function and I would like to print the description of each test being executed.

This is the complete code:

/// Capacity to ship packages within K days
///
/// A conveyor belt has packages that must be shipped from one port to
/// another within `days` days.
///
/// The `ith` package on the conveyor belt has a weight of `weights[i]`.
/// Each day, we load the ship with packages from the conveyor belt (in
/// the order given by `weights`). We may not load more weight than the
/// maximum weight capacity of the ship.
///
/// Return the least weight capacity of the ship that will result in all
/// the packages on the conveyor belt being shipped within days days.


fn ship_within_days(weights: Vec<u16>, k: u16) -> u16 {
    
    let is_feasible = |current: u16| -> bool {
        let mut days = 1;
        let mut total = 0;

        for weight in weights.iter() {
            total += weight;
            if total > current {
                total = *weight;
                days += 1;
                if days > k {
                    return false
                }
            }
        }

        return true
    };


    let mut low = *weights.iter().max().unwrap();
    let mut high = weights.iter().sum();

    while low < high {
        let mid = low + (high - low) / 2;
        if is_feasible(mid) {
            high = mid
        } else {
            low = mid + 1
        }
    }

    return low
}


#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_ship_within_days() {
        let shipping_data = [
            (
                "minimum capacity to ship all packages in 5 days is 15",
                vec![1,2,3,4,5,6,7,8,9,10],
                5,
                15,
            ),
            (
                "minimum capacity to ship all packages in 7 days is 10",
                vec![1,2,3,4,5,6,7,8,9,10],
                7,
                10,
            ),
            (
                "minimum capacity to ship all packages in 3 days is 6",
                vec![3,2,2,4,1,4],
                3,
                6,
            ),
            (
                "minimum capacity to ship all packages in 4 days is 3",
                vec![1,2,3,1,1],
                4,
                3,
            ),
            (
                "min capacity to ship all packages in 26 days is 488",
                vec![488,247,151,268,358,270,366,2,85,49,209,37,353,17,287,
                     385,421,467,32,201,398,27,108,291,435,447],
                26,
                488,
            ),
        ];

        for (description, weights, days, expected) in shipping_data {
            assert_eq!(ship_within_days(weights, days), expected, "{}", description)
        }
    }
}

Note that assert_eq! only prints the description when the test fails, i.e. assert_eq! panics. Otherwise assert_eq! doesn't print your description, even when you run your tests without capturing stdout with cargo test -- --nocapture.

In a similar situation I just written as much functions as I have tests. And hidden all boilerplate with a macro. Note though that description has to go into a function name, I do not think cargo test knows how to print anything else about the test.

Looks something like this:

/// Capacity to ship packages within K days
///
/// A conveyor belt has packages that must be shipped from one port to
/// another within `days` days.
///
/// The `ith` package on the conveyor belt has a weight of `weights[i]`.
/// Each day, we load the ship with packages from the conveyor belt (in
/// the order given by `weights`). We may not load more weight than the
/// maximum weight capacity of the ship.
///
/// Return the least weight capacity of the ship that will result in all
/// the packages on the conveyor belt being shipped within days days.


fn ship_within_days(weights: Vec<u16>, k: u16) -> u16 {
    
    let is_feasible = |current: u16| -> bool {
        let mut days = 1;
        let mut total = 0;

        for weight in weights.iter() {
            total += weight;
            if total > current {
                total = *weight;
                days += 1;
                if days > k {
                    return false
                }
            }
        }

        return true
    };


    let mut low = *weights.iter().max().unwrap();
    let mut high = weights.iter().sum();

    while low < high {
        let mid = low + (high - low) / 2;
        if is_feasible(mid) {
            high = mid
        } else {
            low = mid + 1
        }
    }

    return low
}


#[cfg(test)]
mod tests {

    use super::*;
    
    macro_rules! test_swd {
        ($name:ident, $weights:expr, days $days:expr, expected $expected:expr) => {
            #[test]
            fn $name() {
                assert_eq!(
                    ship_within_days($weights, $days),
                    $expected)
            }
        }
    }
    test_swd!(
        test_min_cap_5d_15,
        vec![1,2,3,4,5,6,7,8,9,10],
        days 5,
        expected 15);
    test_swd!(
        test_min_cap_7d_10,
        vec![1,2,3,4,5,6,7,8,9,10],
        days 7,
        expected 10);
    test_swd!(
        test_min_cap_3d_6,
        vec![3,2,2,4,1,4],
        days 3,
        expected 6);
    test_swd!(
        test_min_cap_4d_3,
        vec![1,2,3,1,1],
        days 4,
        expected 3);
    test_swd!(
        test_min_cap_26d_488,
        vec![488,247,151,268,358,270,366,2,85,49,209,37,353,17,287,
             385,421,467,32,201,398,27,108,291,435,447],
        days 26,
        expected 488);

}
1 Like

Another note: if you are determined to have description which is concatenation of some strings, second argument to tested function and expected result you can use paste to remove the need to explicitly define test function name. That might make it harder to understand where failing test is located though, so I would not recommend this approach.

This makes more sense to me than using paste.

I dont have any special requirements. The test cases are to complement insufficient (or bad) problem descriptions.

My goal was to make the Rust code look similar to its corresponding Go code. Using this approach I think I might save some typing.

I do not think cargo test knows how to print anything else about the test.

If you try to change the expected output of any test in the table, you're going to see it's associated description. Eg:

assertion `left == right` failed: min capacity to ship all packages in 26 days is 488
  left: 488
 right: 48
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
test binary_search::package_shipping_capacity::tests::test_ship_within_days ... FAILED

But this only happens on failure, not success.

That’s output of assert_eq! which cargo test knows to capture (actually, it just knows to capture all stdout and stderr and to my knowledge has no special handling for asserts specifically). No failure means no output from assert_eq! and no failure means cargo test will not bother displaying captured stdout or stderr either even if you used eprintln! or println! with the message regardless of failure or success.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.