mirror of
https://github.com/openai/codex.git
synced 2026-04-28 10:21:06 +03:00
3.8 KiB
3.8 KiB
DOs
- Use Clear Type Names: Prefer
TestCaseover ambiguous names likeCase.
struct TestCase {
name: &'static str,
event: serde_json::Value,
expected: Expected,
}
- Run Each Case Independently: Avoid a single looped test; extract a helper and write one test per case for better failure isolation.
use serde_json::json;
async fn assert_event(event: serde_json::Value, expected: Expected) {
let mut evs = vec![event, json!({"type":"response.completed","response":{"id":"c","output":[]}})];
let out = run_sse(evs).await;
assert_eq!(out.len(), expected.len);
assert!(expected.first.matches(&out[0]));
}
#[tokio::test]
async fn created_emits_created() {
assert_event(json!({"type":"response.created","response":{}}),
Expected { first: Expectation::Created, len: 2 }).await;
}
#[tokio::test]
async fn output_item_done_emits_output() {
assert_event(json!({"type":"response.output_item.done","item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"hi"}]}}),
Expected { first: Expectation::OutputItemDone, len: 2 }).await;
}
- Document Non‑Trivial Tests: Add a concise doc comment explaining what the test validates.
/// Verifies that SSE events map to the correct `ResponseEvent`
/// and that a synthetic `response.completed` ends the stream.
#[tokio::test]
async fn created_emits_created() { /* ... */ }
- Separate Inputs From Expectations: Use distinct structs for what drives the test vs. what should happen.
struct EventArgs {
name: &'static str,
event: serde_json::Value,
}
struct Expected {
first: Expectation,
len: usize,
}
- Prefer Typed Expectations Over Fn Pointers: Use an enum with a matcher method instead of
fn(&ResponseEvent) -> bool.
enum Expectation {
Created,
OutputItemDone,
Completed,
}
impl Expectation {
fn matches(&self, ev: &ResponseEvent) -> bool {
match self {
Expectation::Created => matches!(ev, ResponseEvent::Created),
Expectation::OutputItemDone => matches!(ev, ResponseEvent::OutputItemDone(_)),
Expectation::Completed => matches!(ev, ResponseEvent::Completed { .. }),
}
}
}
- Remove Unneeded Lint Suppressions: Drop
#[allow(dead_code)]once helpers are used.
// Before:
// #[allow(dead_code)]
// pub fn load_sse_fixture(path: impl AsRef<Path>) -> String { ... }
// After:
pub fn load_sse_fixture(path: impl AsRef<std::path::Path>) -> String { /* used in tests */ }
DON’Ts
- Don’t Use Ambiguous Names: Avoid
struct Case; it’s unclear and collides with other languages’ keywords.
// ❌
struct Case { /* ... */ }
// ✅
struct TestCase { /* ... */ }
- Don’t Pack Many Cases Into One Test: A failing early case hides later failures.
// ❌ Single test with loop:
#[tokio::test]
async fn event_kinds_in_one_go() {
for c in cases {
/* first failure stops here */
}
}
- Don’t Assert With Bare Function Pointers: They’re less expressive and harder to extend.
// ❌
expect_first: fn(&ResponseEvent) -> bool
// ✅
expected: Expected { first: Expectation::Created, len: 2 }
- Don’t Leave Dead-Code Allows Behind: If a helper is referenced, remove
#[allow(dead_code)].
// ❌
#[allow(dead_code)]
pub fn load_sse_fixture_with_id(...) -> String { /* actually used */ }
// ✅
pub fn load_sse_fixture_with_id(...) -> String { /* used */ }
- Don’t Skip Test Docs When Behavior Is Subtle: Undocumented tests slow reviews and regressions hunts.
// ❌
#[tokio::test]
async fn table_driven_event_kinds() { /* ... */ }
// ✅
/** Explains mapping and termination behavior. */
#[tokio::test]
async fn table_driven_event_kinds() { /* ... */ }