Learning Rust - CI/CD, Testing, and Release Workflow
Episode 17 of 19

Learning Rust - CI/CD, Testing, and Release Workflow

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

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 and Integration Testing

Unit Tests in the Module

Unit tests are written inside the source file, usually in a tests module that is kept out of the production build:

Unit test
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

Integration tests live in tests/ and test the crate from the outside, exactly like a real user:

Integration test
cat > tests/integrasi.rs <<'EOF'
use perpustakaan::hitung_total;
 
#[test]
fn pemakaian_dari_luar() {
    assert_eq!(hitung_total(10, 4), 40);
}
EOF
cargo test

Files 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.

Property-Based Testing with Proptest

Testing Many Cases at Once

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:

Adding proptest
cargo add --dev proptest
Property test
cat > 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 test

The 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.

Choosing a Testing Strategy

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.

CI/CD Pipelines with GitHub Actions

The Build and Test Workflow

Every push to the repository triggers a pipeline: format check, lint, test, then build. GitHub Actions is the most common choice for Rust projects:

.github/workflows/ci.yml
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 --release

dtolnay/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.

GitLab CI and Tekton

The same principles apply in GitLab CI with gitlab-ci.yml:

.gitlab-ci.yml
test:
  image: rust:latest
  script:
    - cargo fmt --check
    - cargo clippy -- -D warnings
    - cargo test

Tekton 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.

Release Management and Semantic Versioning

Semantic Versioning

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.

Release Automation

cargo release manages the release steps: bumping the version in Cargo.toml, creating the git tag, and running the publish:

Releasing a minor version
cargo install cargo-release
cargo release minor

cargo 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.

Binary Distribution

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.

Closing

Key takeaways:

  • cargo test runs unit and integration tests in a single command.
  • Unit tests in the tests module; integration tests in the tests/ directory.
  • Proptest generates random inputs to verify properties.
  • Required CI steps: fmt check, clippy, test, and a release build.
  • Semver determines compatibility and dependency resolution.
  • 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.

Learning Rust - CI/CD, Testing, and Release Workflow | Learning Rust