Language Internals
Part 11 of 11 · Rust Language ProficiencyTesting, Clippy & Production Patterns
cargo test, clippy, production API patterns.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
Where does a unit test live?
Answer
In the same crate, usually a tests module gated by cfg test, so it can see private items.
L2
Where does an integration test live?
Answer
Under tests/ at the crate root. It links the library as an external crate and sees only the public API.
L3
What does clippy add beyond rustc?
Answer
Semantic lints: needless clones, awkward APIs, and patterns that compile but fight the language.
L4
What is an ergonomic signature?
Answer
Accept impl AsRef of str or Path, or impl Into, so callers do not allocate to match your exact type. Return Result from a library.
L5
Why a newtype instead of a raw id?
Answer
The type system stops you passing a user id where an order id belongs. A u64 cannot do that.
L6
What belongs in CI?
Answer
fmt check, clippy with warnings denied, and cargo test, on the pinned stable toolchain.
L7
What do you document about panics?
Answer
Every public function that can panic says so. Prefer Result if the caller can recover.
Failure modes
Tests that only call private helpers
The public API drifts and the integration surface is untested.
clippy allow as a habit
A lint that named a real clone or a collapsible match stays silenced.
Panic in a library
Callers cannot recover, and the panic is undocumented.
Always-on heavy dependencies
A default feature pulls a runtime the embedder did not ask for.
Misconceptions
If it compiles, clippy has nothing to say.
rustc checks safety and types. clippy checks local quality, including allocations and API shape.
A unit test replaces an integration test.
Unit tests see private items. Integration tests are the client's view.
Taking String by reference is the flexible API.
Prefer a shared str or impl AsRef so a literal does not need an owned String.
Interviewer traps
Comparing cargo test to a separate runner you must wire by hand.
Cargo builds the tests. You still choose unit versus integration placement.
Denying clippy once and then allowing the noisy lint forever.
Fix the clone or the signature. Allow only with a reason tied to a measurement.
Design scenario
Same prompt for every reader.
Requirements
Edition 2024, rust-version for MSRV, Result errors, AsRef paths, unit tests for the parser, an integration test that uses the crate as a client.
Traffic / scale
Every pull request runs the three commands.
Latency
Tests stay in-process. No network.
Consistency
fmt and clippy match what laptops run.
Availability
A warning denied in CI cannot ship.
Failure assumptions
- Only doctests exist.
- The path parameter is a String reference.
- clippy is optional locally.
Constraints
- Stay on test and API shape. Do not design a deploy system.
Prompt
A library crate parses ids and reads paths. CI must fail on format drift, clippy warnings, and a broken public API.
One Cargo flow
Prefer
fmt, clippy, test
The compiler already ran. clippy and cargo test are the rest of the gate.
- Unit tests can see private items.
- Integration tests use the public API.
- Warnings can be denied.
Alternative
A separate runner and linter
Jest or pytest, plus eslint or ruff, wired beside the typechecker.
- The typechecker is a different command.
- Lint may not know ownership.
- You still need both behavior and types.
Overview
cargo test is the behavior gate. rustc already refused the memory bugs. clippy refuses a class of designs that still compile. rustfmt keeps the diff about the change. Public APIs take wide inputs, return Result, and document any panic.
Comparative
| Gate | TypeScript | Python | Rust |
|---|---|---|---|
| Unit tests | Jest or Vitest | pytest | cargo test |
| Types | tsc | mypy or pyright | rustc |
| Lint | eslint | ruff | clippy |
| Format | prettier | ruff | rustfmt |
| Prefer | Avoid |
|---|---|
| shared str, or impl AsRef of Path | only a reference to String or PathBuf |
| Newtype ids | a raw integer for every id |
| Result in libraries | panics in libraries |
| Feature flags | a giant dependency on by default |
Decisions
- 1
Step 1 Unit tests in a cfg(test) module
- nextStep 2 Integration tests in tests/ - public API only
- 2
Step 2 Integration tests in tests/ - public API only
- nextStep 3 Doc tests run the examples
- 3
Step 3 Doc tests run the examples
- nextStep 4 cargo clippy -D warnings
- 4
Step 4 cargo clippy -D warnings
- nextStep 5 cargo fmt --check
- 5
Step 5 cargo fmt --check
- nextStep 6 All gates green
- ?
Step 6 All gates green
- yesStep 7 Merge and release
- noFailure path - CI blocks the merge
- 7
Step 7 Merge and release
- 8
Failure path - CI blocks the merge
Lesson map
Testing, Clippy & Production Patterns
cargo test, clippy, production API patterns.
Architecture. Step 1 Unit tests in a cfg(test) module Ready. Step 2 Integration tests in tests/ - public API only Ready. Step 3 Doc tests run the examples Ready. Step 4 cargo clippy -D warnings Ready. Step 5 cargo fmt --check Ready. Step 6 All gates green Ready. Step 7 Merge and release Ready. Failure path - CI blocks the merge Ready
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB A["Step 1 Unit tests in a cfg(test) module Ready"] B["Step 2 Integration tests in tests/ - public API only Ready"] C["Step 3 Doc tests run the examples Ready"] D["Step 4 cargo clippy -D warnings Ready"] E["Step 5 cargo fmt --check Ready"] F["Step 6 All gates green Ready"] G["Step 7 Merge and release Ready"] X["Failure path - CI blocks the merge Ready"] A -->|continues| B B -->|continues| C C -->|continues| D D -->|continues| E E -->|continues| F F -->|yes| G F -->|no| X
Press Run. Snippets must be self-contained — no network, files, or native modules.
Rosetta — a unit test
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adds() {
assert_eq!(add(2, 2), 4);
}
}export function add(a: number, b: number): number {
return a + b;
}
if (add(2, 2) !== 4) throw new Error("add");def add(a: int, b: int) -> int:
return a + b
def test_adds() -> None:
assert add(2, 2) == 4The Rust module can see add even if it were private. A file under tests/ could not. TypeScript and pytest tests are arranged by the runner, not by a cfg gate inside the compiler.
Rosetta — a fallible API under test
pub fn parse_port(s: &str) -> Result<u16, std::num::ParseIntError> {
s.parse()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses() {
assert_eq!(parse_port("8080").unwrap(), 8080);
}
}export function parsePort(s: string): number {
const n = Number.parseInt(s, 10);
if (Number.isNaN(n)) throw new Error(s);
return n;
}def parse_port(s: str) -> int:
return int(s)
def test_parse_port() -> None:
assert parse_port("8080") == 8080unwrap in the test is fine. unwrap in parse_port would be the library bug from the error-handling page.
Rosetta — the three commands
cargo fmt --check
cargo clippy -- -D warnings
cargo testnpx prettier --check .
npx eslint .
npx tsc --noEmitruff format --check .
ruff check .
pytestclippy is semantic. An allow attribute is debt. rustfmt is the formatter, closer to prettier or ruff format.
Production checklist
edition = "2024"and arust-versionMSRV, with CI on rustc 1.98.1 or newer.- Features stay intentional.
- Libraries log through a facade, not
println. - Typed errors inside the crate, context at the binary.
- CI runs fmt, clippy with warnings denied, and test.
- Examples exist for the public API.
- Release profile for binaries you benchmark.
- Audit dependencies.
- Timeouts and shutdown for anything that serves.
- SemVer and a license.
Interview Q&A
Unit test or integration test?
Answer
A unit test sits in the crate and may touch private items. An integration test is an external client of the public API.
What is an ergonomic Rust API?
Answer
Wide inputs such as AsRef or Into, ownership that is obvious, panics that are documented, and Result so callers can use the question-mark operator.
What does clippy catch that rustc does not?
Answer
Patterns that are safe but hostile: needless clones, collapsed matches you missed, and APIs that fight the guidelines.
Why not take a String reference?
Answer
A literal is a str. Callers should not have to allocate a String to satisfy the signature. impl AsRef of str or a shared str is the wider door.
Where is unwrap allowed?
Answer
Inside the test, after you expect Ok. Not in the library function the test calls.
How does this differ from Jest?
Answer
Jest is a separate runner. cargo test builds the harness with the crate. You still separate private tests from public-API tests.
How does this differ from pytest and ruff?
Answer
pytest collects tests. ruff lints and formats. Rust splits rustc, clippy, rustfmt, and cargo test, and Cargo runs the tests.
Which toolchain is the CI pin?
Answer
Stable rustc 1.98.1 or a newer stable you have recorded as MSRV, with edition 2024 for new crates. Nightly is not the default gate.
Pitfalls
- Only testing private helpers.
- Allowing clippy instead of changing the clone.
- A public function that panics on ordinary input.
- A path argument that requires PathBuf.
- Default features that pull a runtime.
A public function takes a reference to String and panics on a bad port. Rewrite the signature and name the test file that should call it as a client.