How to create a Ruby gem: Testing suite

In the previous part of this series we learned the basics of creating a Ruby gem. We created an initial directory structure, defined a gemspec, installed dependencies, and started to write the actual code. Today, we are going to continue creating the Ruby gem and, specifically, we will take care of the testing suite. You will learn how to setup RSpec, create a Rails dummy application, define and test generator tasks, and how to test Rake tasks. Also, we will integrate your code with Travis CI and Codecov services. Sound good? Then let’s get started!

First part of the series: lokalise.com/blog/create-a-ruby-gem-basics

Third part of the series: lokalise.com/blog/how-to-create-a-ruby-gem-publishing

Setting up the testing suite

Adding development dependencies

So, first of all let’s add some more development dependencies into the gemspec file, like so:

Gem::Specification.new do |spec|
  spec.add_development_dependency 'codecov', '~> 0.1'
  spec.add_development_dependency 'dotenv', '~> 2.5'
  spec.add_development_dependency 'rails', '~> 6.0.3'
  spec.add_development_dependency 'rake', '~> 13.0'
  spec.add_development_dependency 'rspec', '~> 3.6'
  spec.add_development_dependency 'rspec-rails', '~> 4.0'
  # rubocop dependencies...
  spec.add_development_dependency 'simplecov', '~> 0.16'
  spec.add_development_dependency 'vcr', '~> 6.0'
end
  • Codecov is an integration for the Codecov.io service which displays test coverage results in a fancy way. We will return to it later.
  • Dotenv allows us to set environment variables (ENV) using the values inside a separate file. In our case these variables will contain the Lokalise API token and project ID.
  • We will employ Rails to run a dummy application.
  • Rake can be used to run certain tasks.
  • RSpec is our TDD framework. Of course, you can stick to Minitest, but I really love RSpec which seems more expressive to me.
  • Simplecov will allow us to measure code coverage for the testing suite.
  • VCR is a tool to record interactions with third-party services within special files (called cassettes) and replay them during subsequent test runs. This allows us to use pre-recorded fixture data instead of sending real requests every time.

Don’t forget to run:

bundle i

before proceeding to the next section.

Creating a Rails dummy application

We will require a special “dummy” Rails app that will be used to test various features of our gem. This dummy will reside inside the spec/ folder. It should load only the bare minimum as we will not need features like ActionCable, ActiveJob, Spring, etc. Actually, we won’t even require ActiveRecord. Here are the commands to generate the app:

mkdir spec
cd spec
rails new dummy --skip-spring --skip-listen --skip-bootsnap --skip-action-text --skip-active-storage --skip-action-cable --skip-action-mailer --skip-action-mailbox --skip-test --skip-system-test --skip-active-job --skip-active-record --skip-javascript

Once the above command succeeds, open the spec/dummy/config/application.rb file and remove the following line:

config.load_defaults 6.0

We don’t need it because later I would like to run the testing suite using both Rails 5 and 6.

Remove the Gemfile and Gemfile.lock from the spec/dummy folder because we will employ the Gemfile which resides in the project root. Open the spec/dummy/config/boot.rb file and replace its contents with the following:

ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)

This way we are pointing the Rails app toward the proper Gemfile.

Let’s also add a tzinfo-data gem that should be loaded for Windows and JRuby users. Add the following to the Gemfile:

# ...

group :test do
  gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
end

Setting up RSpec

Next, create a newspec/spec_helper.rb file with the following contents:

require 'dotenv/load' # <============= 1
require 'simplecov'

SimpleCov.start 'rails' do # <============= 2
  add_filter 'spec/'
  add_filter '.github/'
  add_filter 'lib/generators/templates/'
  add_filter 'lib/lokalise_rails/version'
end

if ENV['CI'] == 'true' # <============= 3
  require 'codecov'
  SimpleCov.formatter = SimpleCov::Formatter::Codecov
end

Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f } # <============= 4

ENV['RAILS_ENV'] = 'test' # <============= 5

require_relative '../spec/dummy/config/environment' # <============= 6
ENV['RAILS_ROOT'] ||= "#{File.dirname(__FILE__)}../../../spec/dummy" # <============= 7
  1. Load ENV variables using Dotenv. The contents for these variables will be hosted inside the .env file (we’ll create it a bit later).
  2. Start to measure code coverage for our testing suite. I’ve also provided some folders to ignore.
  3. If the testing suite runs on a continuous integration service (like Travis CI), the coverage data should then be sent to Codecov.io.
  4. Load support files from the spec/support directory. We’ll also create this directory later.
  5. We are loading the Rails dummy app using “test” environment.
  6. Load the environment.rb file to boot the dummy app.
  7. Set the Rails root.

Now let’s also create a .rspec file inside the project root (outside of the spec/ folder). This file contains the RSpec options:

--color
--require spec_helper
--order rand
--format doc
  • Test results should be printed in color.
  • The spec_helper.rb file loads automatically.
  • The order of the tests is random.
  • The output formatter is doc.

Setting ENV variables

The next step is to provide values for the ENV variables. These variables, as you already know, are set by the Dotenv gem. So, create an .env file inside the project root:

LOKALISE_API_TOKEN=123abc
LOKALISE_PROJECT_ID=456.def

Replace these sample values with the proper ones.

Make sure to add this file to .gitignore. We have already done so in the previous part, but I’ll provide the .gitignore contents again just in case:

*.gem
coverage/*
Gemfile.lock
*~
.bundle
.rvmrc
log/*
measurement/*
pkg/*
.DS_Store
.env
spec/dummy/tmp/*
spec/dummy/log/*.log

Remember that the .env file must not be tracked by Git because otherwise your API token will be exposed!

Finally, create the .env.sample file which will provide sample contents:

LOKALISE_API_TOKEN=123abc
LOKALISE_PROJECT_ID=456.def

Developers will then copy-paste this file as .env on their local machines and replace the sample contents with the real values.

Adding Rake tasks

Let’s also add RSpec-related Rake tasks to the Rakefile. Here’s the new version of this file:

require 'rake'

begin
  require 'bundler/setup'
  Bundler::GemHelper.install_tasks
rescue LoadError
  puts 'although not required, bundler is recommended for running the tests'
end

task default: :spec

require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)

require 'rubocop/rake_task'
RuboCop::RakeTask.new do |task|
  task.requires << 'rubocop-performance'
  task.requires << 'rubocop-rspec'
end

Configuration options: Tests

Testing config method

Okay, so at this point we are ready to write some tests! In the previous part we provided some config options for our gem (api_token, locales_path, and others). Therefore, let’s make sure that it is possible to adjust these options. Create a new spec/lib/lokalise_rails_spec.rb file:

describe LokaliseRails do
  it 'is possible to provide config options' do
    described_class.config do |c|
      expect(c).to eq(described_class)
    end
  end
end

This test makes sure that when the config method is run, the LokaliseRails module is passed properly. This allows us to provide config in the following way:

LokaliseRails.config do |c|
  c.api_token = '123'
end

Now change directory to the root of your project and run:

rspec .

This command will run all the tests inside the spec/ directory.

Testing mandatory options

Next, I would like to add a couple of tests for the mandatory options, namely the project_id and api_token. Specifically, we need to make sure that these options can be provided using the corresponding writers. However, I would like these tests to be isolated — in other words, they should not change the real config which will be loaded by the test dummy Rails app later. Therefore, let’s take advantage of a class double and create a fake class:

describe LokaliseRails do
  # ...

  describe 'parameters' do
    let(:fake_class) { class_double('LokaliseRails') }
  end
end

Now provide the test case, like so:

describe LokaliseRails do
  # ...

  describe 'parameters' do
    let(:fake_class) { class_double('LokaliseRails') }

    it 'is possible to set project_id' do
      expect(fake_class).to receive(:project_id=).with('123.abc')
      fake_class.project_id = '123.abc'
    end
  end
end

So, we are expecting the fake class to receive a writer method with the proper argument. Then we are actually calling this method. This test will pass if the project_id= writer method was actually provided for the LokaliseRails module.

To avoid false positives, let’s start by making this test fail (the “red” phase, as some devs would say). Open the lib/lokalise_rails.rb file and remove the project_id accessor:

module LokaliseRails
  class << self
    attr_accessor :api_token # remove project_id
    # ... other code...
  end
end

Now run rspec . and you’ll see the following output:

Randomized with seed 7575

LokaliseRails
  is possible to provide config options
  parameters
    is possible to set project_id (FAILED - 1)

Failures:

  1) LokaliseRails parameters is possible to set project_id
     Failure/Error: expect(fake_class).to receive(:project_id=).with('123.abc')
       the LokaliseRails class does not implement the class method: project_id=
     # ./spec/lib/lokalise_rails_spec.rb:12:in `block (3 levels) in <top (required)>'

Finished in 0.058 seconds (files took 6.06 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/lib/lokalise_rails_spec.rb:11 # LokaliseRails parameters is possible to set project_id

Great! This means our test actually does its job. Now add the project_id to the list of accessors to make the test pass (“green” phase):

module LokaliseRails
  class << self
    attr_accessor :api_token, :project_id
  end
end

Run rspec . again. Here’s the result:

Randomized with seed 3205

LokaliseRails
  is possible to provide config options
  parameters
    is possible to set project_id

Finished in 0.055 seconds (files took 6.07 seconds to load)
2 examples, 0 failures

Randomized with seed 3205

Coverage report generated for RSpec to f:/rails/lokalise/lokalise_rails/coverage. 12 / 18 LOC (66.67%) covered.

Note that we also can see the test coverage percentage. To learn more about the test coverage, open the coverage/index.html file in your favorite browser. This provides detailed information for each monitored file line by line.

Now, using the same approach we can add the test for the api_token= method:

describe LokaliseRails do
  # ...

  describe 'parameters' do
    let(:fake_class) { class_double('LokaliseRails') }

    # ...

    it 'is possible to set api_token' do
      expect(fake_class).to receive(:api_token=).with('abc')
      fake_class.api_token = 'abc'
    end
  end
end

While we could also test the reader methods, it is not really necessary: they will be tested implicitly in other specs. Therefore, let’s take care of other methods.

Testing other options

Use the below approach to test other options:

describe LokaliseRails do
  describe 'parameters' do    
    # ...

    it 'is possible to set file_ext_regexp' do
      expect(fake_class).to receive(:file_ext_regexp=).with(Regexp.new('.*'))
      fake_class.file_ext_regexp = Regexp.new('.*')
    end

    it 'is possible to set import_opts' do
      expect(fake_class).to receive(:import_opts=).with(duck_type(:each))
      fake_class.import_opts = {
        format: 'json',
        indentation: '8sp'
      }
    end

    it 'is possible to set export_opts' do
      expect(fake_class).to receive(:export_opts=).with(duck_type(:each))
      fake_class.export_opts = {
        convert_placeholders: true,
        detect_icu_plurals: true
      }
    end

    it 'is possible to set import_safe_mode' do
      expect(fake_class).to receive(:import_safe_mode=).with(true)
      fake_class.import_safe_mode = true
    end

    it 'is possible to override locales_path' do
      expect(fake_class).to receive(:locales_path=).with('/demo/path')
      fake_class.locales_path = '/demo/path'
    end

    it 'is possible to set skip_file_export' do
      cond = ->(f) { f.nil? }
      expect(fake_class).to receive(:skip_file_export=).with(cond)
      fake_class.skip_file_export = cond
    end
  end
end

Nice!

Installation task (Rails generator)

So, we have created basic unit tests for the gem options. To adjust these options, the user should create a config/lokalise_rails.rb with the following content:

LokaliseRails.config do |c|
  c.api_token = '123'
  c.project_id = '345.abc'
  # ... other options
end

While we may instruct the user to create this file manually, it’s not very convenient. What I would like to do is introduce a command like rails g lokalise_rails:install that creates the configuration file containing the list of all supported options and usage instructions. To achieve this, we need to define a special generator to run the installation task.

Creating the generator

Our generator will live under the lib folder, therefore create the following path: lib/generators/lokalise_rails/install_generator.rb. This new file should contain the following code:

require 'rails/generators'

module LokaliseRails
  module Generators
    class InstallGenerator < Rails::Generators::Base
      source_root File.expand_path('../templates', __dir__)

      desc 'Creates a LokaliseRails config file.'

      def copy_config
        template 'lokalise_rails_config.rb', "#{Rails.root}/config/lokalise_rails.rb"
      end
    end
  end
end

This code will simply take the template lokalise_rails_config.rb from the ..templates/ directory and copy-paste it to the config/ folder of the Rails app. The target file will be named lokalise_rails.rb. You can find additional explanations for these methods in the official documentation.

Next we’ll need a template to copy, thus create a new path: lib/generators/templates/lokalise_rails_config.rb. This is the file that will be copied in the user’s application:

require 'lokalise_rails'

LokaliseRails.config do |c|
  # These are mandatory options that you must set before running rake tasks:
  # c.api_token = ENV['LOKALISE_API_TOKEN']
  # c.project_id = ENV['LOKALISE_PROJECT_ID']

  # Provide a custom path to the directory with your translation files:
  # c.locales_path = "#{Rails.root}/config/locales"

  # Import options have the following defaults:
  # c.import_opts = {
  #   format: 'yaml',
  #   placeholder_format: :icu,
  #   yaml_include_root: true,
  #   original_filenames: true,
  #   directory_prefix: '',
  #   indentation: '2sp'
  # }

  # Safe mode for imports is disabled by default:
  # c.import_safe_mode = false

  # Additional export options (only filename, contents, and lang_iso params are provided by default)
  # c.export_opts = {}

  # Provide additional file exclusion criteria for exports (by default, any file with the proper extension will be exported)
  # c.skip_file_export = ->(file) { file.split[1].to_s.include?('fr') }

  # Regular expression to use when choosing the files to extract from the downloaded archive and upload to Lokalise
  # c.file_ext_regexp = /\.ya?ml\z/i
end

We provide explanations for each option, which makes the lives of our users a bit easier. Now let’s add some tests for our new installation task.

Testing the installation task

Create a new spec/lib/generators/lokalise_rails/install_generator_spec.rb file. Our test case will be very simple because we only need to make sure that running the given task results in creating the proper file. To run the task programmatically, you can call the start method:

require 'generators/lokalise_rails/install_generator'

describe LokaliseRails::Generators::InstallGenerator do
  it 'installs config file properly' do
    described_class.start
    expect(File.file?(config_file)).to be true
  end
end

config_file is a helper method that is going to return path to the lokalise_rails.rb configuration file. Such methods are usually defined in the support folder. Therefore, create a new  spec/support/file_manager.rb file:

module FileManager
  def config_file
    "#{Rails.root}/config/lokalise_rails.rb"
  end
end

We also need to include this module in our tests, so adjust the spec/spec_helper.rb file in the following way:

# ... other code ...

RSpec.configure do |config|
  config.include FileManager
end

Now you can run the rspec . command and observe test results. The lokalise_rails.rb file should be created inside the config/ folder of your dummy Rails app.

The problem, however, is that we are not doing any cleanup after running this test. In other words, the created config file is not being removed afterwards. Also, it might be a good idea to try and remove the config before running the test to make it isolated. This problem can be easily solved with before and after hooks:

require 'generators/lokalise_rails/install_generator'

describe LokaliseRails::Generators::InstallGenerator do
  before :all do
    remove_config
  end

  after :all do
    remove_config
  end

  # your test...
end

remove_config is also a helper method that we can define inside the support/file_manager.rb file:

require 'fileutils'

module FileManager
  def remove_config
    FileUtils.remove_file config_file if File.file?(config_file)
  end
end

Note that I’m utilizing the remove_file method provided by the FileUtils module. This method allows us to delete a file by providing full path to it. Don’t forget to import the FileUtils module before using it.

And that’s it: our installation task is done!

Rake tasks

Creating and registering a Rails task with railtie

The next step is to define and register two Rake tasks that the user will run to export or import translation files. These tasks will reside inside the lib/tasks/lokalise_rails_tasks.rake, for instance:

require 'rake'
require "#{Rails.root}/config/lokalise_rails" # <======= 1

namespace :lokalise_rails do
  task :import do # <======= 2
    LokaliseRails::TaskDefinition::Importer.import!
  end

  task :export do # <======= 3
    LokaliseRails::TaskDefinition::Exporter.export!
  end
end
  1. Before running the tasks, we are trying to load the config file. This file is created by the installation task we added a minute ago.
  2. To call this task, one should write rails lokalise_rails:import. The task delegates all work to the import! method which we are going to create later.
  3. Use the rails lokalise_rails:export command to run this task.

We must register these tasks before using them. To register a task, you need to create a railtie — a block of code that extends Rails core functionality. We are going to place our railtie inside the lib/lokalise_rails/railtie.rb file:

module LokaliseRails
  class Railtie < Rails::Railtie
    rake_tasks do
      load 'tasks/lokalise_rails_tasks.rake'
    end
  end
end

We also need to load this railtie inside the lib/lokalise_rails.rb file and make sure that the Rails framework is loaded:

require 'lokalise_rails/railtie' if defined?(Rails)

module LokaliseRails
  # ... options here ...
end

Rake task definitions

So, we’ve created the tasks, but all the heavy lifting will be done by the import! and export! methods, therefore let’s draft them now. Start by creating a new lib/lokalise_rails/task_definition/base.rb file. This file will host a base class. The import and export task will inherit from it:

module LokaliseRails
  module TaskDefinition
    class Base
    end
  end
end

Now create a lib/lokalise_rails/task_definition/importer.rb file:

module LokaliseRails
  module TaskDefinition
    class Importer < Base
      class << self
        def import!
          $stdout.print 'Task complete!'
          true
        end
      end
    end
  end
end

We’ll take care of the actual implementation later. Note that I’m explicitly using the $stdout variable to print the success message. This is required for our testing purposes.

Finally, create a lib/lokalise_rails/task_definition/exporter.rb file:

module LokaliseRails
  module TaskDefinition
    class Exporter < Base
      class << self
        def export!
          $stdout.print 'Task complete!'
          true
        end
      end
    end
  end
end

It’s not much, but I’d like to create a minimal setup required to make the tests pass.

Load these three files inside the lib/lokalise_rails.rb file:

require 'lokalise_rails/task_definition/base'
require 'lokalise_rails/task_definition/importer'
require 'lokalise_rails/task_definition/exporter'
require 'lokalise_rails/railtie' if defined?(Rails)

module LokaliseRails
  # ... options ...
end

Now we can proceed to testing our Rake tasks.

Testing Rake tasks

First and foremost, we need to load the available Rake tasks in the spec/spec_helper.rb before running them programmatically. Add the following line to the bottom of the spec_helper.rb file:

# ... other code

Rails.application.load_tasks

Create a new spec/lib/tasks/import_task_spec.rb file. What should we test for? Well, the import task should output the success message to STDOUT if is everything is okay. This can be tested using the following code snippet:

expect(import_task_call_here).to output(/complete!/).to_stdout

Thing is, this expectation requires a callable object (lambda or procedure). To run a Rake task programmatically, you can use the following approach:

Rake::Task['lokalise_rails:import'].execute

Which means that the expectation transforms to the this:

expect(-> { Rake::Task['lokalise_rails:import'].execute }).to output(/complete!/).to_stdout

This construct is pretty complex, so let’s create a new support file: spec/support/rake_utils.rb with the following content:

module RakeUtils
  def import_executor
    -> { Rake::Task['lokalise_rails:import'].execute }
  end

  def export_executor
    -> { Rake::Task['lokalise_rails:export'].execute }
  end
end

These methods return callable objects to run import and export tasks. Include this module inside the spec/spec_helper.rb:

# ... other code 

RSpec.configure do |config|
  config.include FileManager
  config.include RakeUtils # <=======
end

Rails.application.load_tasks

Now, create a new test case inside the import_task_spec.rb file:

RSpec.describe LokaliseRails do
  it 'import rake task is callable' do
    expect(import_executor).to output(/complete!/).to_stdout
  end
end

Also, add another spec/lib/tasks/export_task_spec.rb file:

RSpec.describe LokaliseRails do
  it 'runs export rake task properly' do
    expect(export_executor).to output(/complete!/).to_stdout
  end
end

The tests are ready but there’s one problem. Our Rake tasks try to load the config/lokalise_rails.rb file containing the config options. This means that this file has to be created before running all the tests. Let’s take care of this issue now.

Creating a sample config file

Open the spec/support/file_manager.rb file and add a new method:

require 'fileutils'

module FileManager
  def add_config
    data = <<~DATA
      require 'lokalise_rails'
      LokaliseRails.config do |c|
        c.api_token = ENV['LOKALISE_API_TOKEN']
        c.project_id = ENV['LOKALISE_PROJECT_ID']
      end
    DATA

    File.open(config_file, 'w+:UTF-8') do |f|
      f.write data
    end
  end

  # ... other methods... 
end

This method will create a new config file with the required options. I’m using heredoc to provide the file contents.

Now tweak the spec/spec_helper.rb in the following way:

# ... other code 

include FileManager

add_config

Rails.application.load_tasks

We are creating our configuration file before any test is run. Problem is, the test for the installation task removes the config inside the after hook. Therefore, let’s re-create the config by tweaking the spec/lib/generators/lokalise_rails/install_generator_spec.rb, for instance:

require 'generators/lokalise_rails/install_generator'

describe LokaliseRails::Generators::InstallGenerator do
  before :all do
    remove_config
  end

  after :all do
    remove_config
    add_config # <==========
  end

  # ...
end

Now you may run the rspec . command and observe the test results!

Integrating with Travis CI and Codecov

Before wrapping up this article, I also wanted to show how to integrate your plugin with Travis CI and Codecov services. Travis CI is a continuous integration service that specifically allows you to run your test suite using different versions of Ruby and Rails. It also enables us to deploy the code automatically to the target server. Codecov, in turn, monitors test coverage and displays results in a beautiful way.

Setting up Travis CI

To get started, create a new repository on GitHub (Bitbucket or GitLab). Next, open travis-ci.org and sign up using your GitHub account (it’s free for open source projects). Then click on your avatar in the top right corner and open ‘Settings’. Click ‘Sync account’ to load the available repositories. After the sync is done, find your new repository in the list and enable it by toggling the switch next to its name. Click ‘Settings’ next to your repository and find the ‘Environment variables’ section. Create LOKALISE_API_TOKEN and LOKALISE_PROJECT_ID variables with the proper values. The values won’t be shown in the logs so you’re safe.

Now we need to create a new .travis.yml file in the root of your project:

language: ruby

rvm:
- 2.5.8
- 2.6.6
- 2.7.1

install: bundle install --retry=3

before_install:
  - gem update bundler

env:
  - 'TEST_RAILS_VERSION="~> 5.1.6"'
  - 'TEST_RAILS_VERSION="~> 5.2.3"'
  - 'TEST_RAILS_VERSION="~> 6.0.3"'

We are instructing it to use Ruby language and run the testing suite with Ruby 2.5.8, 2.6.6, and 2.7.1. Also, I would like to use different versions of Rails for testing. To properly install these Rails versions, update the gemspec, like so:

if ENV['TEST_RAILS_VERSION'].nil?
  spec.add_development_dependency 'rails', '~> 6.0.3'
else
  spec.add_development_dependency 'rails', ENV['TEST_RAILS_VERSION'].to_s
end

So, on Travis CI we’ll use three different Rails versions to run the tests. Locally you’ll utilize Rails 6.0.x only.

Setting up Codecov

Navigate to codecov.io and sign up with your GitHub account (this service is free for open source projects). After signing up, click on your account name and search for your new repository. Basically, this is it: no additional configuration is needed as we’ve already added Codecov-related code to the spec_helper.rb file.

At this point we are ready to get rolling. Commit your changes and push them to GitHub (make sure that the spec/dummy folder does not have its own .git directory!). Return to Travis CI and open the ‘Current’ tab for your repository. You will see something like this:

So, the tests are being run for every Ruby and Rails version we’ve specified. To get more information on any job, click on the specific build. After all tests have passed, return to Codecov and update the page. You’ll see the code coverage results:

You can find detailed information by clicking on the lib folder and proceeding to individual files. Codecov provides more insights (especially when you have added more commits), so make sure to browse other tabs.

Badges

We can also add fancy-looking badges to the README of our gem. These badges will display test coverage and the results of the most recent test run. To do that, add the following contents to the README.md:

[![Build Status](https://travis-ci.org/USERNAME/REPO_NAME.svg?branch=master)](https://travis-ci.org/USERNAME/REPO_NAME)
[![Test Coverage](https://codecov.io/gh/USERNAME/REPO_NAME/graph/badge.svg)](https://codecov.io/gh/USERNAME/REPO_NAME)

Make sure to replace USERNAME with your name (in my case that’s bodrovis) and REPO_NAME with the name of your repository (lokalise_rails in my case). Commit the changes and push to GitHub:

git add .
git commit -am "added badges [skip ci]"
git push

Note the [skip ci] part of the commit message. It makes sure that Travis CI does not re-run all the tests again because we have not modified any codebase.

Return to your GitHub repo and check out your cool new badges:

Conclusion

That’s it for today, guys. In this article we have seen how to create a Ruby gem and introduce a testing functionality. We have created a new generator, two Rake tasks, added tests for them, and integrated our repository with Travis CI and Codecov. Not bad, eh? In the next part of this series we will flesh out the gem and see how to test third-party APIs and work with ZIP files. See you really soon!

Return to the first part

Proceed to the third part

Related posts

Sign up to our newsletter

Get the latest articles on all things localization and translation management delivered straight to your inbox.

Read also
Localization made easy. Why wait?
The preferred localization tool of 1500+ leading global companies