Как убрать предупреждение "function is never used" для функции, используемой только в тестах?
Проблема и её корень
Вы столкнулись с классической ситуацией в Rust. Компилятор видит, что функция prepare_test_data определена в основном модуле, но не вызывается из основного кода. Модуль тестов #[cfg(test)] компилируется только при запуске cargo test. При обычной сборке (cargo build) этот модуль полностью игнорируется, поэтому компилятор считает функцию мёртвым кодом.
Есть три правильных способа решить эту проблему, каждый из которых подходит для разных ситуаций.
Способ 1: Перенести функцию в модуль тестов (рекомендуемый)
Самый идиоматичный подход — поместить вспомогательную функцию внутрь модуля tests или в отдельный вложенный модуль, который тоже аннотирован #[cfg(test)]. Тогда компилятор будет видеть её только во время тестирования.
// main.rs или lib.rs
#[cfg(test)]
mod tests {
// Функция теперь живёт только в тестовом контексте
fn prepare_test_data() -> Vec<i32> {
vec![1, 2, 3, 4, 5]
}
#[test]
fn test_with_data() {
let data = prepare_test_data();
assert_eq!(data.len(), 5);
}
#[test]
fn another_test() {
let data = prepare_test_data();
assert!(data.contains(&3));
}
}
Почему это хорошо:
- Функция не попадает в production-бинарник.
- Нет предупреждений.
- Код чище: тестовые утилиты не смешиваются с логикой приложения.
Способ 2: Использовать #[cfg(test)] на самой функции
Если функция должна быть доступна и из основного кода, и из тестов (редкий случай), можно пометить её условной компиляцией:
// Функция будет существовать только при сборке с тестами
#[cfg(test)]
fn prepare_test_data() -> Vec<i32> {
vec![1, 2, 3, 4, 5]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_data() {
let data = prepare_test_data();
assert_eq!(data.len(), 5);
}
}
Теперь при cargo build функция просто не существует, и предупреждения нет. При cargo test она компилируется и используется.
Способ 3: Атрибут #[allow(dead_code)] (для быстрых фиксов)
Если вы пишете прототип или функция временная, можно просто подавить предупреждение. Но это считается менее идиоматичным, так как вы скрываете проблему, а не решаете её.
#[allow(dead_code)]
fn prepare_test_data() -> Vec<i32> {
vec![1, 2, 3, 4, 5]
}
Дополнительный совет: тестовые утилиты в отдельном файле
Для больших проектов создают отдельный модуль тестовых утилит. Например, файл tests/utils.rs:
// tests/utils.rs
pub fn prepare_test_data() -> Vec<i32> {
vec![1, 2, 3, 4, 5]
}
Тогда в тестах вы импортируете её как обычный внешний модуль. Файлы в папке tests/ компилируются только при cargo test, поэтому предупреждений не будет.
Вывод: лучший способ — держать тестовые утилиты внутри #[cfg(test)]-модулей. Это соответствует философии Rust: "платите только за то, что используете", и держит production-код чистым.
Альтернативный подход: использование атрибута #[cfg_attr]
Если вы не хотите перемещать функцию в тестовый модуль, но считаете #[allow(dead_code)] слишком грубым, можно применить условное подавление предупреждения только для не-тестовой сборки. Это делается с помощью cfg_attr:
#[cfg_attr(not(test), allow(dead_code))]
fn prepare_test_data() -> Vec<i32> {
vec![1, 2, 3, 4, 5]
}
Как это работает:
- Атрибут
#[cfg_attr(condition, attr)]применяет атрибутattrтолько еслиconditionистинна. - Условие
not(test)означает: "если мы НЕ в тестовом режиме". - То есть при
cargo build(не тесты) добавляетсяallow(dead_code), и предупреждение исчезает. - При
cargo testатрибут не применяется, и функция считается используемой — предупреждения тоже нет.
Этот способ полезен, когда:
- Функция используется и в тестах, и в основном коде, но пока только в тестах (например, вы пишете тесты до реализации).
- Вы хотите явно документировать, что функция предназначена для тестов, но оставить её в основном модуле.
Однако лично я рекомендую первый способ из основного ответа — перемещение функции в тестовый модуль. Это чище, и новичку в Rust проще понять, что тестовый код не должен смешиваться с production-кодом. А cfg_attr оставьте для более сложных сценариев, когда без него не обойтись.