用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/mturac/everything-openai-codex --skill rust-testing命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Use this skill to monitor and verify a deployed URL or public OSS launch surface after releases — checks HTTP endpoints, SSE streams, static assets, console errors, performance regressions, PR queue health, maintainer feedback, and listing-review blockers after deploys, merges, submissions, or dependency upgrades. Smoke / canary / post-deploy / PR-watch verification.
Build reputation-safe open-source marketing from verifiable project evidence, not hype, spam, or repeated public pings. Use for launch copy, directory targeting, community posts, proof packets, and maintainer-facing positioning.
Turn public launch, directory, community, or list rejections into repo fixes and better proof without arguing, spamming, or resubmitting blindly. Use after Hacker News, Product Hunt, GitHub list PR, marketplace, or community rejection.
基于 SOC 职业分类
正在显示 SKILL.md
| name | rust-testing |
| description | 単体テスト、統合テスト、非同期テスト、プロパティベーステスト、モック、カバレッジを含むRustテストパターン。TDD方法論に従う。 |
| origin | ecc |
TDD方法論に従って信頼性が高く保守しやすいテストを書くための包括的なRustテストパターン。
#[cfg(test)] モジュール内で #[test] を使用、rstest でパラメータ化テスト、または proptest でプロパティベーステストRED → まず失敗するテストを書く
GREEN → テストを通過する最小限のコードを書く
REFACTOR → テストを通過したままコードをリファクタリングする
REPEAT → 次の要件に進む
// RED: Write test first, use todo!() as placeholder
pub fn add(a: i32, b: i32) -> i32 { todo!() }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() { assert_eq!(add(2, 3), 5); }
}
// cargo test → panics at 'not yet implemented'
// GREEN: Replace todo!() with minimal implementation
pub fn add(a: i32, b: i32) -> i32 { a + b }
// cargo test → PASS, then REFACTOR while keeping tests green
// src/user.rs
pub struct User {
pub name: String,
pub email: String,
}
impl User {
pub fn new(name: impl Into<String>, email: impl Into<String>) -> Result<Self, String> {
let email = email.into();
if !email.contains('@') {
return Err(format!("invalid email: {email}"));
}
Ok(Self { name: name.into(), email })
}
pub fn display_name(&self) -> &str {
&self.name
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creates_user_with_valid_email() {
let user = User::new(, ).();
(user.(), );
(user.email, );
}
() {
= User::(, );
(result.());
(result.().());
}
}
assert_eq!(2 + 2, 4); // Equality
assert_ne!(2 + 2, 5); // Inequality
assert!(vec![1, 2, 3].contains(&2)); // Boolean
assert_eq!(value, 42, "expected 42 but got {value}"); // Custom message
assert!((0.1_f64 + 0.2 - 0.3).abs() < f64::EPSILON); // Float comparison
Result の戻り値のテスト#[test]
fn parse_returns_error_for_invalid_input() {
let result = parse_config("}{invalid");
assert!(result.is_err());
// Assert specific error variant
let err = result.unwrap_err();
assert!(matches!(err, ConfigError::ParseError(_)));
}
#[test]
fn parse_succeeds_for_valid_input() -> Result<(), Box<dyn std::error::Error>> {
let config = parse_config(r#"{"port": 8080}"#)?;
assert_eq!(config.port, 8080);
Ok(()) // Test fails if any ? returns Err
}
#[test]
#[should_panic]
fn panics_on_empty_input() {
process(&[]);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn panics_with_specific_message() {
let v: Vec<i32> = vec![];
let _ = v[0];
}
my_crate/
├── src/
│ └── lib.rs
├── tests/ # 統合テスト
│ ├── api_test.rs # 各ファイルが独立したテストバイナリ
│ ├── db_test.rs
│ └── common/ # 共有テストユーティリティ
│ └── mod.rs
// tests/api_test.rs
use my_crate::{App, Config};
#[test]
fn full_request_lifecycle() {
let config = Config::test_default();
let app = App::new(config);
let response = app.handle_request("/health");
assert_eq!(response.status, 200);
assert_eq!(response.body, "OK");
}
#[tokio::test]
async fn fetches_data_successfully() {
let client = TestClient::new().await;
let result = client.get("/data").await;
assert!(result.is_ok());
assert_eq!(result.unwrap().items.len(), 3);
}
#[tokio::test]
async fn handles_timeout() {
use std::time::Duration;
let result = tokio::time::timeout(
Duration::from_millis(100),
slow_operation(),
).await;
assert!(result.is_err(), "should have timed out");
}
rstest を使用したパラメータ化テストuse rstest::{rstest, fixture};
#[rstest]
#[case("hello", 5)]
#[case("", 0)]
#[case("rust", 4)]
fn test_string_length(#[case] input: &str, #[case] expected: usize) {
assert_eq!(input.len(), expected);
}
// Fixtures
#[fixture]
fn test_db() -> TestDb {
TestDb::new_in_memory()
}
#[rstest]
fn test_insert(test_db: TestDb) {
test_db.insert("key", "value");
assert_eq!(test_db.get("key"), Some("value".into()));
}
#[cfg(test)]
mod tests {
use super::*;
/// Creates a test user with sensible defaults.
fn make_user(name: &str) -> User {
User::new(name, &format!("{name}@test.com")).unwrap()
}
#[test]
fn user_display() {
let user = make_user("alice");
assert_eq!(user.display_name(), "alice");
}
}
proptest を使用したプロパティベーステストuse proptest::prelude::*;
proptest! {
#[test]
fn encode_decode_roundtrip(input in ".*") {
let encoded = encode(&input);
let decoded = decode(&encoded).unwrap();
assert_eq!(input, decoded);
}
#[test]
fn sort_preserves_length(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
let original_len = vec.len();
vec.sort();
assert_eq!(vec.len(), original_len);
}
#[test]
fn sort_produces_ordered_output(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
vec.sort();
for window in vec.windows(2) {
assert!(window[0] <= window[1]);
}
}
}
use proptest::prelude::*;
fn valid_email() -> impl Strategy<Value = String> {
("[a-z]{1,10}", "[a-z]{1,5}")
.prop_map(|(user, domain)| format!("{user}@{domain}.com"))
}
proptest! {
#[test]
fn accepts_valid_emails(email in valid_email()) {
assert!(User::new("Test", &email).is_ok());
}
}
mockall を使用したモックuse mockall::{automock, predicate::eq};
#[automock]
trait UserRepository {
fn find_by_id(&self, id: u64) -> Option<User>;
fn save(&self, user: &User) -> Result<(), StorageError>;
}
#[test]
fn service_returns_user_when_found() {
let mut mock = MockUserRepository::new();
mock.expect_find_by_id()
.with(eq(42))
.times(1)
.returning(|_| Some(User { id: 42, name: "Alice".into() }));
let service = UserService::new(Box::new(mock));
let user = service.get_user(42).unwrap();
assert_eq!(user.name, "Alice");
}
#[test]
fn service_returns_none_when_not_found() {
let = MockUserRepository::();
mock.()
.(|_| );
= UserService::(::(mock));
(service.().());
}
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// use my_crate::add;
///
/// assert_eq!(add(2, 3), 5);
/// assert_eq!(add(-1, 1), 0);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// Parses a config string.
///
/// # Errors
///
/// Returns `Err` if the input is not valid TOML.
///
/// ```no_run
/// use my_crate::parse_config;
///
/// let config = parse_config(r#"port = 8080"#).unwrap();
/// assert_eq!(config.port, 8080);
/// ```
///
/// ```no_run
/// use my_crate::parse_config;
///
/// assert!(parse_config("}{invalid").is_err());
/// ```
pub fn parse_config(input: &str) -> Result<Config, ParseError> {
todo!()
}
# Cargo.toml
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "benchmark"
harness = false
// benches/benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 {
match n {
0 | 1 => n,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn bench_fibonacci(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}
criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);
# Install: cargo install cargo-llvm-cov (or use taiki-e/install-action in CI)
cargo llvm-cov # Summary
cargo llvm-cov --html # HTML report
cargo llvm-cov --lcov > lcov.info # LCOV format for CI
cargo llvm-cov --fail-under-lines 80 # Fail if below threshold
| コードの種類 | 目標 |
|---|---|
| クリティカルなビジネスロジック | 100% |
| パブリックAPI | 90%以上 |
| 汎用コード | 80%以上 |
| 生成済み / FFIバインディング | 除外 |
cargo test # Run all tests
cargo test -- --nocapture # Show println output
cargo test test_name # Run tests matching pattern
cargo test --lib # Unit tests only
cargo test --test api_test # Integration tests only
cargo test --doc # Doc tests only
cargo test --no-fail-fast # Don't stop on first failure
cargo test -- --ignored # Run ignored tests
すべきこと:
#[cfg(test)] モジュールを使用するassert! より assert_eq! を優先するResult を返すテストでは ? を使用するすべきでないこと:
Result::is_err() をテストできる場合に #[should_panic] を使用するsleep() を使用する——チャンネル、バリア、または tokio::time::pause() を使用する# GitHub Actions
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- name: Check formatting
run: cargo fmt --check
- name: Clippy
run: cargo clippy -- -D warnings
- name: Run tests
run: cargo test
- uses: taiki-e/install-action@cargo-llvm-cov
- name: Coverage
run: cargo llvm-cov --fail-under-lines 80
覚えておくこと:テストはドキュメントである。コードをどのように使うべきかを示している。明確に書き、最新の状態を保つこと。