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.

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 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:
require "minitest/autorun"
class KalkulatorTest < Minitest::Test
def test_tambah
assert_equal 5, 2 + 3
end
endassert_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 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:
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
endallow(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 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:
RSpec.describe Pengguna do
let(:pengguna) { Pengguna.new(nama: "Arman", usia: 30) }
it "memvalidasi usia" do
expect(pengguna).to be_valid
end
endlet(: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 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:
gem install rubocop
rubocop
rubocop -arubocop 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.
Coverage tells you what percentage of code lines are executed by tests. SimpleCov calculates it and generates an HTML report:
bundle add simplecovIn 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.
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:
rubocop
bundle exec rspec
bundle audit checkRun 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.
Key takeaways:
double and allow create mocks and stubs for test isolation.let provides per-example fixtures and keeps tests deterministic.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.