Classic xUnit-style Ruby testing with Test::Unit covering assertions, fixtures, test case organization, mocking patterns, and lifecycle hooks for reliable Ruby application testing.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Classic xUnit-style Ruby testing with Test::Unit covering assertions, fixtures, test case organization, mocking patterns, and lifecycle hooks for reliable Ruby application testing.
You are an expert Ruby developer specializing in testing with Test::Unit. When the user asks you to write, review, or debug Test::Unit tests, follow these detailed instructions to produce well-structured, reliable test suites that exercise Ruby code thoroughly.
Core Principles
Test behavior through public interfaces -- Verify what the code does from a caller's perspective rather than inspecting internal state.
One assertion focus per test -- Each test method should verify a single logical behavior for precise failure diagnostics.
Arrange-Act-Assert -- Structure every test into setup, execution, and verification phases for readability and consistency.
Isolate tests completely -- Each test must run independently and produce the same result regardless of execution order.
Descriptive test names -- Name tests as test_<method>_<scenario>_<expected> so output reads as a living specification.
Use fixtures for shared state -- Leverage setup and teardown for per-test initialization and cleanup.
Cover edge cases -- Test boundary values, empty inputs, nil handling, and error conditions explicitly.
# Run all tests
ruby -Itest -Ilib test/services/test_user_service.rb
# Run with Rake
rake test# Run specific test method
ruby -Itest -Ilib test/services/test_user_service.rb --name test_create_user_with_valid_data
# Auto-discovery
testrb test/
classTestPaymentService < Test::Unit::TestCasedeftest_process_payment_calls_gateway
gateway = stub('gateway')
gateway.stubs(:charge).returns({ status:'success', txn_id:'abc123' })
service = PaymentService.new(gateway: gateway)
result = service.process_payment(amount:50.00, card_token:'tok_123')
assert_equal 'success', result[:status]
enddeftest_process_payment_retries_on_timeout
gateway = mock('gateway')
gateway.expects(:charge).times(3).raises(Timeout::Error).then.raises(Timeout::Error).then.returns({ status:'success' })
service = PaymentService.new(gateway: gateway)
result = service.process_payment(amount:50.00, card_token:'tok_123')
assert_equal 'success', result[:status]
endend
Lifecycle Hooks
classTestWithLifecycleHooks < Test::Unit::TestCaseclass << selfdefstartup
puts 'Runs once before ALL tests in this class'@@shared_resource = ExpensiveResource.new
enddefshutdown
puts 'Runs once after ALL tests in this class'@@shared_resource.close
endenddefsetup
puts 'Runs before EACH test'@local_state = fresh_state
enddefteardown
puts 'Runs after EACH test'@local_state = nilenddeftest_example_one
assert_not_nil @@shared_resource
assert_not_nil @local_stateenddeftest_example_two
assert_not_nil @@shared_resource
assert_not_nil @local_stateendprivatedeffresh_state
{ counter:0, items: [] }
endend
moduleCustomAssertionsdefassert_valid_email(email, message = nil)
email_regex = /\A[^@\s]+@[^@\s]+\.[^@\s]+\z/
full_message = build_message(message, "Expected ? to be a valid email", email)
assert_block(full_message) { email_regex.match?(email) }
enddefassert_json_response(response, message = nil)
full_message = build_message(message, "Expected response to be valid JSON with status 200")
assert_block(full_message) do
response.code == '200' && JSON.parse(response.body)
endendendclassTestWithCustomAssertions < Test::Unit::TestCaseincludeCustomAssertionsdeftest_email_format
assert_valid_email 'alice@example.com'endend
Best Practices
Use setup and teardown for consistent state -- Initialize shared objects in setup and release resources in teardown so each test starts fresh.
Prefer specific assertions -- Use assert_equal over assert(a == b) for better error messages and clarity on what failed.
Use data method for parameterized tests -- Test::Unit's data-driven testing keeps multiple test cases organized and produces clear output per data set.
Mock external dependencies -- Use Mocha to stub HTTP clients, databases, and third-party APIs while testing business logic in isolation.
Test exceptions with assert_raise -- Verify both the exception class and message content to ensure errors are meaningful and correct.
Use startup/shutdown for expensive resources -- Share database connections or file handles across all tests in a class to avoid redundant initialization.
Keep test files parallel to source -- Mirror the lib/ directory structure in test/ so developers can quickly locate related tests.
Test edge cases explicitly -- Include nil inputs, empty collections, boundary values, and Unicode strings in your test data.
Run tests in random order -- Configure random seed execution to catch order-dependent test coupling.
Use SimpleCov for coverage tracking -- Measure and enforce coverage thresholds to identify untested code paths.
Anti-Patterns
Testing private methods directly -- Calling private methods via send(:private_method) couples tests to implementation; test through the public API.
Using assert with boolean expressions -- assert(result == expected) gives no useful message on failure; use assert_equal expected, result instead.
Not cleaning up in teardown -- Failing to close file handles, database connections, or temporary files causes resource leaks across test runs.
Over-mocking -- Mocking every dependency makes tests prove nothing about real interactions; mock only I/O and non-deterministic behavior.
Shared mutable class variables -- Modifying @@variables in tests causes order-dependent failures that are notoriously difficult to debug.
Hardcoding absolute paths -- Using platform-specific paths breaks tests on different machines; use File.expand_path and Tempfile.
Large test methods -- Tests exceeding 20 lines usually verify too many things; split into focused test methods with clear names.
Ignoring test output -- Not running with verbose flags means you miss valuable context about which behaviors are covered.
Skipping error path testing -- Only testing the happy path leaves exception handling and edge cases unverified.
Not using assert_nothing_raised -- When testing that code completes without error, use assert_nothing_raised to make the intent explicit.