14 — Tooling, testing and gems¶
One line: The machinery around a Ruby program — loading files, gems and Bundler, Minitest, the command line, irb, the release history, backtraces and the missing docstring — measured against Python's import, pip, unittest, python3 -i, traceback and __doc__.
A Python programmer arrives with a working model of all of this: import runs a module once and caches it in sys.modules; pip and a venv hold the packages a project needs and pyproject.toml declares them; unittest is a TestCase class with assert* methods; the >>> prompt echoes values; traceback and inspect read the stack; every function has a __doc__. Most of that model carries over with the names changed — require for import, $LOADED_FEATURES for sys.modules, gem/bundle for pip, Gemfile for the dependency list, Minitest::Test for TestCase, irb for the prompt, caller and backtrace for the stack.
Where it breaks is the interesting part. Ruby has three loading verbs to Python's one (require, require_relative, load) plus autoload; RubyGems can activate a gem version at run time, which import cannot; Minitest runs tests in a seeded random order and, as of Ruby 4.0's Minitest 6, no longer ships Minitest::Mock; the -I, -s and -S flags mean different things in the two interpreters; the Ruby prompt echoes assignments and defs because they are expressions; backtrace labels are Klass#method strings; and there is no docstring at all — a method knows only its source_location, and documentation is a comment convention that RDoc, YARD and ri read from the file.
| Lesson | Level | The one thing |
|---|---|---|
require loads once; load runs every time |
201 | require and require_relative share one once-list, load ignores it, autoload defers |
| Gems are versioned; Bundler pins them | 201 | default gems, bundled gems and core classes are three different things; Gem::Version compares as numbers |
| A Minitest test is a plain class | 201 | the seeded, shuffled run and its runs, assertions, failures, errors, skips line, next to unittest |
| The command line is part of the language | 201 | -e -c -w -r -I -n -p -a -l -E --yjit --disable-gems --dump, each run in a child and compared with python3's letters |
irb echoes every value; a script does not |
101 | the read–eval–print loop driven through a pipe, values only, beside python3 -i and code.InteractiveConsole |
| What changed, 1.9 to 4.0 | 201 | a timeline table, and a program that checks which of its features this Ruby and this Python have |
| A backtrace is data you can read | 201 | caller, caller_locations, backtrace, full_message as labels, next to traceback and inspect |
| Ruby has no docstrings | 101 | source_location plus the comment above the def, read from the file, next to __doc__ and inspect.getcomments |
Read more¶
- Ruby:
Kernel#require↗ — the loading rules,$LOAD_PATHand$LOADED_FEATURES - RubyGems guides ↗ — what a gem is,
gemcommands, writing a gemspec - Bundler docs ↗ —
Gemfile,Gemfile.lock,bundle installandbundle exec - Minitest ↗ — assertions, spec DSL, the runner's options
- Ruby: command-line options ↗ — every flag, with
-n,-p,-a,-lexplained - Ruby:
Thread::Backtrace::Location↗ —label,base_label,path,lineno - Ruby: release notes ↗ — each release's "what's new"
- Python: the import system ↗ —
sys.modules, finders, packages and relative imports - Python:
unittest↗ —TestCase,mock, the runner - Python: command line and environment ↗ —
-c,-m,-I,-W,-X - Python:
traceback↗ —extract_stack,extract_tb,format_exception_only - Python: What's New ↗ — the per-release changes the timeline lesson summarizes