This episode builds a reliable delivery pipeline: unit, integration, and property-based testing with cargo test and proptest, build and test pipelines with GitHub Actions and GitLab CI, as well as release management with semantic versioning and binary distribution.

Code that runs in production must pass through quality gates. Episode 17 discusses the delivery flow: testing at various levels, CI/CD pipelines that run everything automatically, and release management that produces clear, traceable versions.
Cargo ships with mature testing tooling from the start — cargo test is a gold standard that needs no complex setup. You will see how testing, CI, and releases work together as one continuous flow.
Unit tests are written inside the source file, usually in a tests module that is kept out of the production build:
cat > src/lib.rs <<'EOF'
pub fn hitung_total(harga: u64, jumlah: u64) -> u64 {
harga * jumlah
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn total_benar() {
assert_eq!(hitung_total(5, 3), 15);
}
#[test]
fn total_nol_jika_jumlah_nol() {
assert_eq!(hitung_total(5, 0), 0);
}
}
EOF
cargo test#[cfg(test)] ensures the module only exists when testing. #[test] marks a test function; assert_eq! verifies the result. cargo test compiles and runs all the tests, showing a pass/fail summary.
Integration tests live in tests/ and test the crate from the outside, exactly like a real user:
cat > tests/integrasi.rs <<'EOF'
use perpustakaan::hitung_total;
#[test]
fn pemakaian_dari_luar() {
assert_eq!(hitung_total(10, 4), 40);
}
EOF
cargo testFiles in tests/ are compiled as separate crates. This proves that your public API really can be used from outside. Most projects use both: unit tests for internal logic, integration tests for behavior from the user's point of view.
Regular tests use fixed inputs. Property-based testing generates many random inputs and verifies properties that must always hold. proptest is the most popular implementation:
cargo add --dev proptestcat > src/lib.rs <<'EOF'
pub fn saturasi_kuadrat(x: u32) -> u32 {
x.saturating_mul(x)
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn kuadrat_tidak_overflow(n: u32) {
let hasil = saturasi_kuadrat(n);
assert!(hasil >= n);
}
}
}
EOF
cargo testThe proptest! macro generates hundreds of u32 values and runs the test for each one. saturating_mul prevents overflow. Property-based testing finds edge cases that never cross your mind when writing tests by hand.
Guideline: unit tests for pure functions, integration tests for APIs, and property-based testing for functions with large inputs or complex rules. The combination catches the majority of bugs before they reach CI.
Every push to the repository triggers a pipeline: format check, lint, test, then build. GitHub Actions is the most common choice for Rust projects:
name: ci
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo fmt --check
- run: cargo clippy -- -D warnings
- run: cargo test
- run: cargo build --releasedtolnay/rust-toolchain@stable sets up the stable toolchain. The four main commands: cargo fmt --check ensures consistent formatting, cargo clippy -- -D warnings rejects lints, cargo test runs the tests, and cargo build --release verifies the production build. cargo test with a cache can take a few minutes; use action caching to speed it up.
The same principles apply in GitLab CI with gitlab-ci.yml:
test:
image: rust:latest
script:
- cargo fmt --check
- cargo clippy -- -D warnings
- cargo testTekton is a Kubernetes-native pipeline: each step is a pod. The concept is identical — run fmt, clippy, test, build — only the presentation differs. Choose the pipeline that aligns with your team's platform.
Releases follow semver: major.minor.patch. Patch for bug fixes, minor for compatible features, major for breaking API changes. Cargo uses semver for dependency resolution (episode 7), so choosing versions correctly matters to the ecosystem.
cargo release manages the release steps: bumping the version in Cargo.toml, creating the git tag, and running the publish:
cargo install cargo-release
cargo release minorcargo release minor bumps the minor version, creates a commit and a tag, and marks the milestone. cargo install cargo-release installs the tool once. For crates on crates.io, cargo publish uploads the package; for binaries, distribute them through GitHub releases or architectures such as cargo-binstall.
For distributing binaries across many platforms, the pipeline builds a matrix of targets — Linux, macOS, Windows — and uploads the artifacts to a GitHub Release. Tools such as cargo-dist automate the generation of installers and per-platform formats.
Key takeaways:
cargo test runs unit and integration tests in a single command.tests module; integration tests in the tests/ directory.cargo release and cargo publish automate releases and distribution.In the next episode 18, the series finale, we will discuss modern tooling and the latest stable features — cargo, rustfmt, clippy, rust-analyzer, and cargo nextest, the newest stable features such as async and impl Trait, as well as Rust production trends in full-stack, microservices, embedded, and safe systems programming.