Learn Ruby - Testing, Quality & Best Practices
Series/Learn Ruby/Episode 20
Episode 20 of 23

Learn Ruby - Testing, Quality & Best Practices

This episode covers Ruby testing and quality: the Minitest and RSpec frameworks, mocks and stubs, fixtures, the TDD workflow, then RuboCop for style consistency and SimpleCov for measuring coverage, plus code review best practices.

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

Introduction

Writing fast code is good, but writing code you can trust matters more. Episode 20 covers testing and quality: the Minitest and RSpec frameworks, mocks and stubs, fixtures, the TDD workflow, then RuboCop for style consistency and SimpleCov for measuring coverage.

Testing isn't extra work — it's a safety net that makes refactoring and adding features feel safe. Good tests, disciplined linting, and careful review are the hallmarks of production-grade code.

Minitest: Built-in Framework

Minitest ships in the standard library, so no extra installation is needed. Its structure is minimal: a class inheriting from Minitest::Test with test_ methods. Each assertion runs when the test suite executes:

RubyTest dengan Minitest
require "minitest/autorun"
 
class KalkulatorTest < Minitest::Test
  def test_tambah
    assert_equal 5, 2 + 3
  end
end

assert_equal 5, 2 + 3 compares the expected value with the actual value. Minitest provides assert, refute, assert_raises, and other variants. Run the whole suite from the terminal with ruby test/test_kalkulator.rb or rake test when using a Rakefile.

RSpec: Expressive BDD

Description, Expectation, and Stub

RSpec uses a Behavior-Driven Development style: behavior is described with describe, context, and it, then checked with expect. Mocks and stubs are built with double and allow:

RubySpesifikasi RSpec
RSpec.describe Kalkulator do
  it "menambahkan dua angka" do
    expect(2 + 3).to eq(5)
  end
 
  it "mendelegasikan ke service" do
    service = double("service")
    allow(service).to receive(:hitung).and_return(42)
    expect(service.hitung).to eq(42)
  end
end

allow(service).to receive(:hitung).and_return(42) creates a stub — a fake method that returns a fixed value. double builds a complete fake object. This keeps tests fast and isolated, without touching the database or the network.

Fixtures and the TDD Workflow

Fixtures provide initial data for tests. In Rails, fixture files in test/fixtures/ are loaded automatically; in plain RSpec, you can use let for per-example data. The TDD workflow is a red-green-refactor cycle:

RubyFixture dengan let
RSpec.describe Pengguna do
  let(:pengguna) { Pengguna.new(nama: "Arman", usia: 30) }
 
  it "memvalidasi usia" do
    expect(pengguna).to be_valid
  end
end

let(:pengguna) defines data that is freshly created the first time it's used in each example. TDD starts by writing a failing test, then the minimal implementation to make it pass, then cleaning up the code. This discipline yields better design because behavior is thought through before implementation.

RuboCop and SimpleCov

RuboCop

RuboCop is the linter and style guide for Ruby — it supports Ruby 4.0 and checks hundreds of style rules and potential issues. Run it from the terminal and fix automatically:

Menjalankan RuboCop
gem install rubocop
rubocop
rubocop -a

rubocop prints violations with their locations and the cops that were broken. rubocop -a fixes violations that can be corrected automatically. Configure it through .rubocop.yml, and run it in CI so every PR is stylistically consistent.

SimpleCov

Coverage tells you what percentage of code lines are executed by tests. SimpleCov calculates it and generates an HTML report:

Menambah SimpleCov
bundle add simplecov

In the test helper file, load SimpleCov before the application code: require "simplecov" then SimpleCov.start. Run the suite, then open coverage/index.html. One hundred percent coverage is no guarantee of being bug-free, but a number below eighty percent signals rarely tested areas.

Code Review Best Practices

Tests and lint clean up the code, but human review catches what machines can't: design, alignment with requirements, and security issues. A few practices that consistently help:

Cek sebelum push
rubocop
bundle exec rspec
bundle audit check

Run rubocop, bundle exec rspec, and bundle audit check before opening a pull request. When reviewing, focus on the purpose of the change, not just style. Leave specific comments with suggestions, and read the diff line by line to find logic that went off track.

Tip

Keep tests fast: a slow suite will be avoided. Isolate I/O with stubs, run tests in parallel when necessary, and schedule them in CI on every push.

Conclusion

Key takeaways:

  • Minitest is a minimal built-in test framework that's enough for many cases.
  • RSpec expresses behavior with describe, context, and expect.
  • double and allow create mocks and stubs for test isolation.
  • let provides per-example fixtures and keeps tests deterministic.
  • TDD follows the red-green-refactor cycle for purposeful design.
  • RuboCop enforces consistent style and can fix issues automatically.
  • SimpleCov measures coverage and helps find untested areas.
  • Careful code review catches issues that machines miss.

In episode 21 we touch Ruby 4.0: modern features like ZJIT, Ruby::Box, Ractor::Port, language changes up to Unicode 17.0, and the upgrade path from 3.4 to 4.0. Good testing will save you when that migration arrives.

Learn Ruby - Testing, Quality & Best Practices | Learn Ruby