Rust の tests ディレクトリのいろいろ
Rust の tests ディレクトリに tests/utils みたいに test utils 用のモジュールを作ると、tests/*.rs それぞれで utils の要素を全て import しないと unused 扱いになる。この例を示す。
ディレクトリ構造は以下:
./tests
├── bad_file.rs
├── core.rs
├── expected
│ ...
│ └── ...
├── general_flags.rs
├── inputs
│ ...
│ └── ...
└── utils
├── constants.rs
├── file.rs
├── mod.rs
└── run.rs
utils::run
モジュールの中身は以下:
use anyhow::Result;
use assert_cmd::Command;
use pretty_assertions::assert_eq;
use std::fs;
pub fn run(args: &[&str], expected_file: &str) -> Result<()> {
let expected = fs::read_to_string(expected_file)?;
let output = Command::cargo_bin(crate::BINARY_NAME)?
.args(args)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("invalid UTF-8");
assert_eq!(stdout, expected);
Ok(())
}
pub fn run_stdin(input_file: &str, args: &[&str], expected_file: &str) -> Result<()> {
let input = fs::read_to_string(input_file)?;
let expected = fs::read_to_string(expected_file)?;
let output = Command::cargo_bin(crate::BINARY_NAME)?
.write_stdin(input)
.args(args)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("invalid UTF-8");
assert_eq!(stdout, expected);
Ok(())
}
utils::run::run
、utils::run::run_stdin
は tests/core.rs
というインテグレーションテストでしか import されておらずそれ以外のテストでは import されていない。そのため、下記画像のように linter によって #[warn(dead_code)]
という warning が表示されてしまう。
これは Rust のインテグレーションテストの仕様で、tests/*.rs
がそれぞれ独立したクレートとして扱われることによる挙動である。
例えば、tests/core.rs
クレート(あえて「クレート」と読んでいる)から見たら utils::run
サブモジュールは使用されているが、(utils::run
を import していない)bad_file.rs
というクレートから見たら utils
モジュールのサブモジュールである utils::run
は未使用のモジュールということになり、#[warn(dead_code)]
という warning が発生してしまう。
各 OSS リポジトリのテストディレクトリを見る
Awesome Rust で探す
openobserve
test utils を #[cfg(test)]
にぶち込んでる。でもこれは結局共通の test utils を定義できるわけではないからこれではないなぁ。
Deno
tests ディレクトリを workspace の member にして、1 つの独立したクレートとして扱っているパターン。
テストターゲットの tests/integration/mod.rs
。
#[path = "..."]
属性始めてみたけど、モジュール名を自由につけることができるようになる属性らしい。例えば、cache_tests.rs
は何も属性を付けなければ mod cache_tests;
と書く必要があるけど、これを mod cache;
だけで良くなるようにできる。
#[path = "cache_tests.rs"]
mod cache;
vector
zulip に書いたやつを転載
Rust の integration test を書くときのディレクトリ構成について、調べてもわからなかった問題があったので質問させていただきます。
開発環境
$ rustc --version
rustc 1.80.1 (3f5fd8dd4 2024-08-06)
実現したいこと
- Rust の integration test でテストユーティリティ用のモジュールを定義したい
- ディレクトリ構造は cargo workspace を使わず、プロジェクトルートに
src
、tests
が置いてある基本的な構成にしたい -
tests/utils
にテストユーティリティ用のモジュールを作成したい。ただし、全てのtests/*.rs
で import しなくても#[warn(dead_code)]
を出ないようにしたい。
やったこと
背景
Rust で integration test を書く時、プロジェクトルートに tests
ディレクトリを作成し、tests/*.rs
を作成します。
このとき、テストユーティリティ用の tests/utils というモジュールを作成します。
例えば、以下のようなディレクトリ構成になります。
my-project
├── Cargo.toml
├── src
│ └── main.rs
└── tests
├── core.rs # テスト
├── error.rs # テスト
└── utils # テストユーティリティ用のモジュール
├── constants.rs
├── file.rs
├── mod.rs
├── random.rs
└── run.rs
このとき、Rust の integration test の仕様上、tests/*.rs
はそれぞれ独立したクレートとして扱われるため、utils の要素をそれぞれの tests/*.rs
で全て import しないと tests/utils のモジュールの要素(定数や関数など)は unused
扱いとなり、コンパイル時に #[warn(dead_code)]
が出てしまいます。
上記のディレクトリ構成の例でいうと、tests/core.rs
、tests/error.rs
の両方で import されている utils モジュールの要素は unused
扱いになりませんが、tests/core.rs
だけで使われている utils モジュールの要素は tests/error.rs
で使われていないとコンパイラに判断され、unused
扱いとなり、#[warn(dead_code)]
の warning が出てしまいます。
回避策
これを回避する方法として、試したのは以下の 3 つです。しかし、どれも納得の行く解決方法ではありませんでした。
-
tests/utils/mod.rs
に#![allow(dead_code)]
を記述する- → 本質的な解決方法でないため、できればやりたくない
-
src/...
をライブラリクレートにし、テストユーティリティ用のモジュールを定義し、tests/*.rs
で import する- → テストユーティリティなので
src
配下に置きたくない
- → テストユーティリティなので
- cargo workspace を使用しテストユーティリティ用の member を作成した
-
src
ディレクトリをなくし、メインロジック用の member を作成。 -
tests
、tests/utils
を member とし、tests/*.rs
でtests/utils
を import した - (こちらは Deno のリポジトリの Cargo.toml を参考にしました)
- → こちらは苦肉の策でした。単純なプロジェクトなので、わざわざ cargo workspace を使いたくなかった。プロジェクトルートに
src
、tests
という構造を保ちながら、tests/utils
にテストユーティリティ用のモジュールを作りたかった。
-
質問
まとめると、私が実現したいことは以下になります。
- Rust の integration test でテストユーティリティ用のモジュールを定義したい
- ディレクトリ構造は cargo workspace を使わず、プロジェクトルートに
src
、tests
が置いてある基本的な構成にしたい -
tests/utils
にテストユーティリティ用のモジュールを作成したい。ただし、全てのtests/*.rs
で import しなくても#[warn(dead_code)]
を出ないようにしたい。
実現する方法をもし知っている方がいらっしゃったらご回答いただければ幸いです!私が仕様を理解していない可能性もあるため、方向性が間違っているとか勘違いしてることがあればご指摘いただければありがたいです。