Overview
Automated testing in CI executes test suites automatically when code changes enter a repository. The CI system detects commits or pull requests, provisions a clean environment, runs the test suite, and reports results. This process catches bugs before they reach production and prevents broken code from merging into main branches.
CI testing differs from local testing by running in isolated, reproducible environments that match production configurations. Developers may pass tests locally due to cached dependencies or environmental quirks, but CI testing exposes these hidden failures. The automated nature removes human error from the testing process and creates a consistent quality gate.
The practice originated with Extreme Programming methodologies in the late 1990s, where teams integrated code multiple times daily. Modern CI testing extends this concept by automating the entire validation workflow. When a developer pushes code, the CI system automatically builds the project, runs tests, checks code coverage, and performs static analysis.
# Example of a simple RSpec test that would run in CI
describe PaymentProcessor do
it 'charges the correct amount' do
processor = PaymentProcessor.new
result = processor.charge(amount: 100, currency: 'USD')
expect(result.charged_amount).to eq(100)
expect(result.status).to eq('success')
end
end
The CI system executes this test along with thousands of others, aggregates results, and marks the build as passing or failing. Failed builds prevent merging and trigger notifications to the development team.
Key Principles
Test Execution Isolation ensures each CI run starts from a clean state. The system provisions fresh containers or virtual machines, installs dependencies from scratch, and runs tests without any artifacts from previous builds. This isolation prevents tests from passing due to cached data or leftover files. Ruby projects specify dependencies in Gemfiles, and CI systems install these dependencies in each build to guarantee consistency.
Fast Feedback Loops minimize the time between pushing code and receiving test results. Developers need quick feedback to maintain flow and fix issues while the code remains fresh in their minds. CI pipelines optimize for speed through parallel test execution, dependency caching, and selective test running. A build that takes 30 minutes provides less value than one completing in 5 minutes.
Comprehensive Coverage means testing all code paths and integration points. CI runs the complete test suite including unit tests, integration tests, and system tests. While local testing might skip slow integration tests, CI executes everything to catch bugs that only appear when components interact. Ruby applications typically run RSpec or Minitest suites covering models, controllers, services, and background jobs.
Deterministic Results require tests to produce identical outcomes given identical inputs. Flaky tests that pass and fail randomly undermine confidence in the CI system. Tests depending on external services, random data, or time-based logic create non-determinism. Ruby CI configurations use mocking libraries like WebMock to stub external requests and Timecop to control time-dependent behavior.
# Deterministic test using WebMock to stub HTTP requests
require 'webmock/rspec'
describe WeatherService do
it 'fetches temperature data' do
stub_request(:get, 'https://api.weather.com/current')
.to_return(body: { temperature: 72 }.to_json)
service = WeatherService.new
temp = service.current_temperature
expect(temp).to eq(72)
end
end
Build Reproducibility allows recreating any historical build exactly. CI systems preserve build artifacts, dependency versions, and environment configurations. When a bug appears in production, teams can reproduce the exact build that deployed to understand what tested code looked like. Ruby applications pin dependency versions in Gemfile.lock, which CI systems commit to version control.
Failure Visibility surfaces test failures immediately to relevant stakeholders. CI systems send notifications through Slack, email, or GitHub status checks. Failed builds block pull request merges and trigger alerts. The visibility creates accountability and ensures failures receive prompt attention rather than accumulating.
Implementation Approaches
Branch-Based Testing runs tests on feature branches before merging to main. Developers create branches for each feature or bug fix, push changes, and the CI system automatically tests the branch. This approach catches issues early while changes remain isolated. GitHub Actions and GitLab CI detect branch pushes and trigger test runs. Teams configure branch protection rules requiring passing tests before merging.
The branch-based approach scales well for teams practicing trunk-based development or Git Flow. Each branch gets independent test runs, and status badges on pull requests show whether tests pass. The main branch maintains a clean history of passing builds.
# GitHub Actions workflow for branch testing
name: Test
on:
push:
branches: ['**']
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- run: bundle exec rspec
Matrix Testing executes tests across multiple configurations simultaneously. Ruby applications often need testing against multiple Ruby versions, database types, or dependency versions. CI systems create a matrix of combinations and run tests for each. This approach exposes compatibility issues before users encounter them.
Matrix testing consumes more CI resources but provides comprehensive validation. A library maintainer might test against Ruby 3.0, 3.1, and 3.2 with both PostgreSQL and MySQL. The matrix produces nine separate test runs (3 Ruby versions × 3 database configurations), catching version-specific bugs.
# Matrix testing across Ruby versions and databases
jobs:
test:
strategy:
matrix:
ruby: ['3.0', '3.1', '3.2']
database: ['postgresql', 'mysql', 'sqlite3']
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
mysql:
image: mysql:8
steps:
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- run: bundle exec rspec
Staged Pipelines organize tests into sequential stages with different purposes. Fast unit tests run first, followed by slower integration tests, and finally deployment steps. This approach provides quick feedback for common failures while deferring expensive operations. A failed unit test stops the pipeline before running 20-minute integration tests.
Stages represent quality gates, each validating different aspects. The first stage might run linters and fast unit tests completing in 2 minutes. The second stage runs integration tests taking 10 minutes. The final stage performs security scans and builds deployment artifacts. CircleCI and GitLab CI provide explicit stage configurations.
Parallel Execution splits test suites across multiple workers to reduce overall runtime. Ruby test runners like Parallel Tests or Knapsack Pro distribute specs across workers, running them simultaneously. A suite taking 40 minutes on one worker completes in 10 minutes across four workers.
The approach requires tests to be independent without shared state. Database-touching tests need separate schemas or transactions. File-based tests need unique directories. The parallelization investment pays off for large test suites where sequential execution creates bottlenecks.
Ruby Implementation
Ruby CI testing centers on RSpec or Minitest as the test framework, with SimpleCov for coverage reporting and Rubocop for linting. The CI system installs Ruby using version managers, installs gems with Bundler, prepares databases, and executes the test command.
Test Framework Configuration defines how tests run and report results. RSpec configurations specify formatters, failure reporting, and filtering. CI environments typically use the documentation formatter for readable output and JUnit formatter for test result integration.
# spec/spec_helper.rb
RSpec.configure do |config|
config.formatter = :documentation if ENV['CI']
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
config.filter_run_when_matching :focus unless ENV['CI']
end
The configuration detects CI environments through environment variables and adjusts behavior accordingly. Local development might use focused test runs, but CI always runs the complete suite. Database cleaning strategies ensure test isolation.
Dependency Management installs gems consistently across builds. The Gemfile specifies required gems, and Gemfile.lock pins exact versions. CI systems cache installed gems between builds to accelerate installation.
# Gemfile
source 'https://rubygems.org'
ruby '3.2.0'
gem 'rails', '~> 7.0.0'
gem 'pg', '~> 1.4'
group :test do
gem 'rspec-rails', '~> 6.0'
gem 'factory_bot_rails', '~> 6.2'
gem 'faker', '~> 3.0'
gem 'simplecov', '~> 0.22', require: false
end
The CI workflow installs dependencies with bundle install --jobs=4 --retry=3, using parallel installation and retries for network failures. Bundler's cache key includes the Gemfile.lock hash, invalidating cache when dependencies change.
Database Preparation creates test databases and runs migrations before executing tests. Rails applications use rails db:create db:schema:load to prepare databases faster than running all migrations. The CI configuration starts database services as containers alongside the test runner.
# config/database.yml
test:
adapter: postgresql
encoding: unicode
database: app_test_<%= ENV['TEST_ENV_NUMBER'] %>
pool: 5
username: <%= ENV.fetch('POSTGRES_USER', 'postgres') %>
password: <%= ENV.fetch('POSTGRES_PASSWORD', '') %>
host: <%= ENV.fetch('POSTGRES_HOST', 'localhost') %>
The database configuration reads connection details from environment variables set by the CI system. Parallel test execution appends worker numbers to database names, preventing conflicts between concurrent test runs.
Coverage Reporting tracks which code lines execute during tests. SimpleCov instruments Ruby code and generates coverage reports after test runs. CI systems upload these reports to services like CodeCov or Coveralls for historical tracking.
# spec/spec_helper.rb
if ENV['CI']
require 'simplecov'
SimpleCov.start 'rails' do
add_filter '/spec/'
add_filter '/config/'
minimum_coverage 90
end
end
Coverage thresholds enforce quality standards. Builds fail if coverage drops below the minimum, preventing untested code from merging. The CI system stores coverage reports as artifacts for later analysis.
Tools & Ecosystem
GitHub Actions provides CI directly integrated with GitHub repositories. Workflows define jobs using YAML syntax, and Actions marketplace offers reusable components. Ruby projects use the ruby/setup-ruby action for Ruby installation and automatic Bundler caching.
GitHub Actions bills based on runner minutes, with Linux runners costing less than macOS or Windows. Free tiers provide 2,000 minutes monthly for private repositories. The tight GitHub integration enables status checks on pull requests and automatic deployment on merge.
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Setup Database
env:
RAILS_ENV: test
POSTGRES_HOST: postgres
run: |
bundle exec rails db:create
bundle exec rails db:schema:load
- name: Run Tests
env:
RAILS_ENV: test
run: bundle exec rspec
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/.resultset.json
CircleCI specializes in fast, parallelized test execution. The platform offers sophisticated caching, Docker layer caching, and test splitting across containers. Ruby orbs provide pre-configured job templates for common workflows.
CircleCI configurations use a different YAML structure with explicit steps, caching directives, and parallelism controls. The platform excels at reducing build times through intelligent test distribution and aggressive caching strategies.
# .circleci/config.yml
version: 2.1
orbs:
ruby: circleci/ruby@2.0
jobs:
test:
docker:
- image: cimg/ruby:3.2
- image: cimg/postgres:14.0
environment:
POSTGRES_USER: circleci
POSTGRES_DB: app_test
parallelism: 4
steps:
- checkout
- ruby/install-deps
- run:
name: Wait for DB
command: dockerize -wait tcp://localhost:5432 -timeout 1m
- run:
name: Database setup
command: bundle exec rake db:schema:load
- run:
name: Run tests
command: |
bundle exec rspec --profile 10 \
--format RspecJunitFormatter \
--out test_results/rspec.xml \
--format progress \
$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
- store_test_results:
path: test_results
GitLab CI integrates with GitLab repositories and provides unlimited CI minutes for self-hosted runners. The platform uses .gitlab-ci.yml configurations and supports advanced features like dynamic child pipelines and environment-specific deployments.
GitLab CI includes built-in container registry, artifact storage, and merge request integration. Teams running GitLab instances gain full control over CI infrastructure without external service dependencies.
Jenkins offers extensive customization through plugins but requires self-hosting and maintenance. Ruby projects use Jenkins with the RSpec plugin for test result visualization and the Cobertura plugin for coverage reports. Jenkinsfiles define pipelines as code using Groovy syntax.
While Jenkins provides maximum flexibility, it demands significant infrastructure investment and ongoing maintenance compared to cloud CI services.
Ruby Gems for CI enhance test execution and reporting. The parallel_tests gem distributes specs across processors, Knapsack Pro optimizes test distribution using historical timing data, and VCR records HTTP interactions for deterministic tests.
# Gemfile additions for CI optimization
group :test do
gem 'parallel_tests'
gem 'rspec_junit_formatter'
gem 'webmock'
gem 'vcr'
gem 'database_cleaner-active_record'
end
The parallel_tests gem splits test files across processes and merges results. VCR cassettes record external API responses during initial test runs, replaying them in CI for faster, deterministic tests without network dependencies.
Practical Examples
Rails Application with GitHub Actions demonstrates a complete CI setup for a production Rails application. The workflow runs on every push and pull request, testing against PostgreSQL with full coverage reporting.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
env:
RUBY_VERSION: 3.2.0
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
RAILS_ENV: test
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14-alpine
ports:
- 5432:5432
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ env.RUBY_VERSION }}
bundler-cache: true
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'yarn'
- name: Install JavaScript dependencies
run: yarn install --frozen-lockfile
- name: Prepare database
run: |
cp config/database.ci.yml config/database.yml
bundle exec rails db:create db:schema:load
- name: Precompile assets
run: bundle exec rails assets:precompile
- name: Run tests
run: |
bundle exec rspec --format progress \
--format RspecJunitFormatter \
--out tmp/test-results/rspec.xml
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: tmp/test-results
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage.json
fail_ci_if_error: true
The configuration includes database and Redis services, asset precompilation, and comprehensive result reporting. The conditional upload ensures test results save even when tests fail.
Parallel Testing with CircleCI splits a large test suite across four containers, reducing total runtime from 30 minutes to 8 minutes.
# .circleci/config.yml
version: 2.1
orbs:
ruby: circleci/ruby@2.0
node: circleci/node@5.0
jobs:
test:
parallelism: 4
docker:
- image: cimg/ruby:3.2-browsers
environment:
RAILS_ENV: test
RACK_ENV: test
- image: cimg/postgres:14.5
environment:
POSTGRES_USER: circleci
POSTGRES_PASSWORD: ""
POSTGRES_DB: app_test
steps:
- checkout
- ruby/install-deps:
key: gems-v1
- node/install-packages:
pkg-manager: yarn
cache-key: yarn-v1
- run:
name: Setup parallel databases
command: |
bundle exec rake parallel:create parallel:load_schema
environment:
PARALLEL_TEST_PROCESSORS: 4
- run:
name: Run parallel tests
command: |
mkdir -p tmp/test-results
bundle exec parallel_test spec/ \
-n 4 \
--type rspec \
--runtime-log tmp/parallel_runtime_rspec.log \
--test-options '--format progress --format RspecJunitFormatter --out tmp/test-results/rspec-%{ENV["TEST_ENV_NUMBER"]}.xml'
- store_test_results:
path: tmp/test-results
- store_artifacts:
path: tmp/test-results
destination: test-results
- store_artifacts:
path: coverage
destination: coverage
workflows:
test_and_deploy:
jobs:
- test
The parallel execution creates four database instances, distributes tests using historical timing data, and merges results. Each worker generates separate JUnit XML files that CircleCI aggregates for test analytics.
Multi-Ruby Version Testing validates gem compatibility across Ruby versions using matrix builds.
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
strategy:
fail-fast: false
matrix:
ruby:
- '3.0'
- '3.1'
- '3.2'
- '3.3'
os:
- ubuntu-latest
- macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Ruby ${{ matrix.ruby }}
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run tests
run: bundle exec rake test
- name: Run RuboCop
run: bundle exec rubocop
if: matrix.ruby == '3.2' && matrix.os == 'ubuntu-latest'
The matrix generates eight test runs (4 Ruby versions × 2 operating systems). The fail-fast: false setting continues testing remaining combinations even when one fails. Linting runs once on the primary configuration to avoid duplicate reports.
Common Patterns
Pull Request Validation runs tests automatically when developers open pull requests. The CI system posts status checks indicating whether tests pass, and branch protection rules prevent merging until checks succeed.
# GitHub Actions PR validation
name: PR Tests
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for accurate diff
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Run changed specs
run: |
git diff --name-only origin/${{ github.base_ref }}...HEAD \
| grep '_spec\.rb$' \
| xargs -r bundle exec rspec
- name: Check for pending migrations
run: |
bundle exec rails db:migrate:status | grep "down" && exit 1 || exit 0
This pattern optimizes feedback by running only tests affected by changed files. The migration check prevents merging code with unapplied database changes.
Nightly Full Suites supplement fast PR tests with comprehensive nightly builds running the entire test suite with additional checks. These builds catch integration issues and flaky tests without slowing PR feedback.
# Nightly comprehensive testing
name: Nightly Build
on:
schedule:
- cron: '0 2 * * *' # 2 AM UTC daily
jobs:
full_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Run full test suite
run: bundle exec rspec --tag ~@skip_nightly
- name: Run security audit
run: |
bundle exec bundler-audit check --update
bundle exec brakeman -q
- name: Check code quality
run: |
bundle exec rubocop
bundle exec reek
- name: Performance regression tests
run: bundle exec rspec --tag performance
- name: Notify failures
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
Nightly builds run expensive operations like security audits, code quality checks, and performance tests that would slow PR validation. Slack notifications alert teams to failures requiring attention.
Deployment Gates use test results to control deployment pipelines. Passing tests on the main branch trigger automatic deployment to staging environments, while production deployments require manual approval after test verification.
# Deployment with test gates
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
- run: bundle exec rspec
deploy_staging:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Deploy to staging
run: ./deploy.sh staging
deploy_production:
needs: [test, deploy_staging]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
run: ./deploy.sh production
The dependency chain ensures deployment jobs only run after test success. Environment protection rules add manual approval requirements for production, creating a safety gate even after automated tests pass.
Selective Test Execution runs subset of tests based on changed files, accelerating feedback for large codebases.
# lib/tasks/ci.rake
namespace :ci do
desc 'Run specs for changed files'
task :changed_specs do
base_branch = ENV.fetch('BASE_BRANCH', 'main')
changed_files = `git diff --name-only origin/#{base_branch}...HEAD`.split("\n")
spec_files = changed_files.select { |f| f.end_with?('_spec.rb') }
implementation_files = changed_files.select { |f|
f.start_with?('app/') && f.end_with?('.rb') && !f.include?('_spec.rb')
}
# Find specs for changed implementation files
implementation_files.each do |file|
spec_file = file.sub('app/', 'spec/').sub('.rb', '_spec.rb')
spec_files << spec_file if File.exist?(spec_file)
end
if spec_files.any?
sh "bundle exec rspec #{spec_files.join(' ')}"
else
puts 'No specs to run for changed files'
end
end
end
This Rake task identifies changed Ruby files and their corresponding specs, running only relevant tests. The approach works well for incremental validation but should combine with periodic full suite runs.
Common Pitfalls
Flaky Tests pass and fail randomly without code changes, undermining confidence in CI results. Database state leaking between tests, timing dependencies, and external service calls commonly cause flakiness. Tests expecting specific timestamps or relying on sleep statements exhibit non-deterministic behavior.
# Flaky test with timing dependency
it 'expires after one second' do
cache.set('key', 'value', ttl: 1)
sleep 1
expect(cache.get('key')).to be_nil # Flaky: timing might vary
end
# Fixed version
it 'expires after TTL period' do
Timecop.freeze do
cache.set('key', 'value', ttl: 1)
Timecop.travel(2.seconds.from_now)
expect(cache.get('key')).to be_nil
end
end
The fixed version uses Timecop to control time deterministically. Database-related flakiness requires proper transaction rollbacks or database cleaning between tests. External API calls need stubbing with WebMock or VCR to eliminate network dependencies.
Insufficient Environment Isolation occurs when tests depend on specific system configurations or pre-existing data. Tests passing locally but failing in CI often indicate missing environment setup or undeclared dependencies.
# Test assuming file exists
it 'processes configuration' do
config = YAML.load_file('config/settings.yml') # Fails if file missing in CI
expect(config['timeout']).to eq(30)
end
# Fixed with explicit setup
it 'processes configuration' do
File.write('config/settings.yml', { 'timeout' => 30 }.to_yaml)
config = YAML.load_file('config/settings.yml')
expect(config['timeout')).to eq(30)
ensure
File.delete('config/settings.yml') if File.exist?('config/settings.yml')
end
The improved version creates required files explicitly and cleans up afterward. Environment variables should have defaults or CI configurations should set them explicitly.
Slow Test Suites create long feedback loops that reduce developer productivity. Test suites taking over 10 minutes discourage frequent testing and delay bug detection. Common causes include unnecessary database creation, lacking test data factories, and running full integration tests for unit-testable logic.
# Slow test creating database records
it 'calculates total price' do
user = User.create!(email: 'test@example.com')
5.times { |i| user.orders.create!(total: i * 10) }
expect(user.total_spent).to eq(100)
end
# Faster test using in-memory objects
it 'calculates total price' do
user = User.new
user.orders = [
Order.new(total: 0),
Order.new(total: 10),
Order.new(total: 20),
Order.new(total: 30),
Order.new(total: 40)
]
expect(user.total_spent).to eq(100)
end
The optimized version avoids database writes entirely. Parallel test execution, selective test running, and proper test categorization (unit vs integration) reduce suite duration.
Missing Coverage Enforcement allows untested code to merge when CI lacks coverage requirements. Projects without minimum coverage thresholds accumulate untested code paths that harbor bugs.
# spec/spec_helper.rb with coverage enforcement
require 'simplecov'
SimpleCov.start 'rails' do
add_filter '/spec/'
add_filter '/vendor/'
minimum_coverage 85
minimum_coverage_by_file 70
refuse_coverage_drop :total_coverage, 0.5
end
This configuration enforces 85% overall coverage and 70% per-file coverage, failing builds below thresholds. The coverage drop prevention stops builds introducing code that decreases total coverage.
Ignored Warnings accumulate when CI allows builds to pass despite deprecation warnings or lint failures. These warnings signal future breaking changes or code quality issues.
# GitHub Actions enforcing warnings
- name: Run tests with warnings as errors
run: bundle exec rspec
env:
RUBYOPT: '-W2' # Enable warnings
- name: Fail on Rubocop offenses
run: bundle exec rubocop --fail-level warning
The configuration treats Ruby warnings as errors and fails builds on linting violations. Teams should configure warning levels appropriate for their quality standards.
Secrets in Logs expose sensitive data when test output or error messages print credentials. CI logs are often accessible to all repository contributors, creating security risks.
# Dangerous: printing API keys in logs
it 'authenticates with API' do
api_key = ENV['API_KEY']
puts "Using API key: #{api_key}" # Secret exposed in logs
client = APIClient.new(api_key)
expect(client.authenticated?).to be true
end
# Safe: no secret exposure
it 'authenticates with API' do
api_key = ENV['API_KEY']
client = APIClient.new(api_key)
expect(client.authenticated?).to be true
end
Never print environment variables containing secrets. Review test failures carefully to ensure error messages don't reveal sensitive data.
Reference
Test Execution Commands
| Command | Purpose | Use Case |
|---|---|---|
| bundle exec rspec | Run RSpec test suite | Standard test execution |
| bundle exec rspec spec/models | Run specific directory | Testing model layer only |
| bundle exec rspec --tag ~slow | Exclude slow tests | Fast feedback during development |
| bundle exec parallel_test spec/ -n 4 | Parallel execution | Large test suites |
| bundle exec rspec --format RspecJunitFormatter | JUnit output format | CI result reporting |
| bundle exec rails test:system | System tests | Full browser integration tests |
Coverage Configuration Options
| Setting | Description | Typical Value |
|---|---|---|
| minimum_coverage | Minimum total coverage percentage | 85-95% |
| minimum_coverage_by_file | Per-file coverage requirement | 70-85% |
| refuse_coverage_drop | Maximum allowed coverage decrease | 0.5-1.0% |
| add_filter | Directories excluded from coverage | /spec/, /config/ |
| track_files | Include files without tests | Pattern matching app code |
Environment Variables
| Variable | Purpose | Example Value |
|---|---|---|
| CI | Indicates CI environment | true |
| RAILS_ENV | Rails environment | test |
| DATABASE_URL | Database connection | postgresql://localhost/app_test |
| PARALLEL_TEST_PROCESSORS | Number of parallel workers | 4 |
| COVERAGE | Enable coverage reporting | true |
| TEST_ENV_NUMBER | Parallel test worker ID | 1, 2, 3, 4 |
CI Platform Comparison
| Platform | Strengths | Weaknesses | Best For |
|---|---|---|---|
| GitHub Actions | Tight GitHub integration, marketplace | Limited parallelization on free tier | GitHub-hosted projects |
| CircleCI | Advanced caching, parallelization | Cost for private projects | Performance-critical builds |
| GitLab CI | Unlimited minutes on self-hosted | Requires infrastructure management | Self-hosted GitLab |
| Jenkins | Maximum customization | High maintenance overhead | Enterprise with existing Jenkins |
| Travis CI | Simple configuration | Limited features | Open source projects |
Common RSpec Formatters
| Formatter | Output Format | Use Case |
|---|---|---|
| documentation | Readable test descriptions | Human consumption |
| progress | Dot notation | Quick feedback |
| RspecJunitFormatter | JUnit XML | CI integration |
| Fuubar | Progress bar | Visual feedback |
| json | JSON output | Machine parsing |
Database Cleaning Strategies
| Strategy | Speed | Safety | Use Case |
|---|---|---|---|
| transaction | Fast | High | Unit and integration tests |
| truncation | Medium | High | Tests requiring actual commits |
| deletion | Slow | High | Legacy databases without truncate |
| null_strategy | Fastest | Low | Tests not touching database |
Parallel Testing Gems
| Gem | Features | Configuration |
|---|---|---|
| parallel_tests | Process-based parallelization | Environment-based database naming |
| knapsack_pro | Optimal test distribution | API-based timing tracking |
| test-queue | Queue-based distribution | Redis-backed coordination |
| rspec-parallel | RSpec-native parallel support | Thread-based execution |
CI Build Optimization Techniques
| Technique | Impact | Implementation Complexity |
|---|---|---|
| Dependency caching | High | Low |
| Parallel execution | Very High | Medium |
| Docker layer caching | Medium | Medium |
| Selective test running | High | High |
| Precompiled assets | Medium | Low |
| Test result splitting | High | Medium |