<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Omri Gabay</title>
    <description>The latest articles on DEV Community by Omri Gabay (@omrisama).</description>
    <link>https://dev.to/omrisama</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F119214%2F20a61379-3afe-4df3-a853-a9b0f0321d1e.jpg</url>
      <title>DEV Community: Omri Gabay</title>
      <link>https://dev.to/omrisama</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/omrisama"/>
    <language>en</language>
    <item>
      <title>Getting Started with SimpleCov in GitLab CI</title>
      <dc:creator>Omri Gabay</dc:creator>
      <pubDate>Fri, 29 Nov 2019 21:16:19 +0000</pubDate>
      <link>https://dev.to/omrisama/getting-started-with-simplecov-in-gitlab-ci-259e</link>
      <guid>https://dev.to/omrisama/getting-started-with-simplecov-in-gitlab-ci-259e</guid>
      <description>&lt;p&gt;&lt;strong&gt;EDIT (10/04/2020)&lt;/strong&gt;: Edited to reflect slightly cleaner practices, and use&lt;br&gt;
the &lt;code&gt;collate&lt;/code&gt; method in SimpleCov that does a lot of work for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/colszowka/simplecov" rel="noopener noreferrer"&gt;&lt;code&gt;simplecov&lt;/code&gt;&lt;/a&gt; is a useful code coverage tool built for Ruby projects. It encapsulates Ruby's own &lt;a href="https://ruby-doc.org/stdlib-2.7.2/libdoc/coverage/rdoc/Coverage.html" rel="noopener noreferrer"&gt;Coverage&lt;/a&gt; API and is, by far, &lt;a href="https://www.ruby-toolbox.com/categories/code_coverage" rel="noopener noreferrer"&gt;the most popular gem and library for code-coverage&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/simplecov-ruby" rel="noopener noreferrer"&gt;
        simplecov-ruby
      &lt;/a&gt; / &lt;a href="https://github.com/simplecov-ruby/simplecov" rel="noopener noreferrer"&gt;
        simplecov
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Code coverage for Ruby with a powerful configuration library and automatic merging of coverage across test suites
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SimpleCov &lt;a href="https://badge.fury.io/rb/simplecov" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e0bcb2d8961fc9342d9b8597b1063382e5b4c98c8465f2a94ac3d7356ab202f6/68747470733a2f2f62616467652e667572792e696f2f72622f73696d706c65636f762e737667" alt="Gem Version"&gt;&lt;/a&gt; &lt;a href="https://github.com/simplecov-ruby/simplecov/actions?query=workflow%3Astable" title="SimpleCov is built around the clock by github.com" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/simplecov-ruby/simplecov/workflows/stable/badge.svg?branch=main" alt="Build Status"&gt;&lt;/a&gt; &lt;a href="https://codeclimate.com/github/simplecov-ruby/simplecov/maintainability" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9dc263e1ab6e6bcc0d74d6a632c9fb4e5c69afb052109a32113f490926634c3b/68747470733a2f2f6170692e636f6465636c696d6174652e636f6d2f76312f6261646765732f63303731643139376436313935336137653438322f6d61696e7461696e6162696c697479" alt="Maintainability"&gt;&lt;/a&gt; &lt;a href="http://inch-ci.org/github/simplecov-ruby/simplecov" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/522acfb22ac12dd11bed652ba6f1fcda26ffc0499f4a564fa074f96d2908a004/687474703a2f2f696e63682d63692e6f72672f6769746875622f73696d706c65636f762d727562792f73696d706c65636f762e7376673f6272616e63683d6d61696e" alt="Inline docs"&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Code coverage for Ruby&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/simplecov-ruby/simplecov" title="Source Code @ GitHub" rel="noopener noreferrer"&gt;Source Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://rubydoc.info/gems/simplecov/frames" title="RDoc API Documentation at Rubydoc.info" rel="nofollow noopener noreferrer"&gt;API documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/simplecov-ruby/simplecov/blob/main/CHANGELOG.md" title="Project Changelog" rel="noopener noreferrer"&gt;Changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://rubygems.org/gems/simplecov" title="SimpleCov @ rubygems.org" rel="nofollow noopener noreferrer"&gt;Rubygem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/simplecov-ruby/simplecov/actions?query=workflow%3Astable" title="SimpleCov is built around the clock by github.com" rel="noopener noreferrer"&gt;Continuous Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SimpleCov is a code coverage analysis tool for Ruby. It uses &lt;a href="https://docs.ruby-lang.org/en/3.2/Coverage.html" title="API doc for Ruby's Coverage library" rel="nofollow noopener noreferrer"&gt;Ruby's built-in Coverage&lt;/a&gt; library to gather code
coverage data, but makes processing its results much easier by providing a clean API to filter, group, merge, format
and display those results, giving you a complete code coverage suite that can be set up with just a couple lines of
code
SimpleCov/Coverage track covered ruby code, gathering coverage for common templating solutions like erb, slim and haml is not supported.&lt;/p&gt;
&lt;p&gt;In most cases, you'll want overall coverage results for your projects, including all types of tests, Cucumber features,
etc. SimpleCov automatically takes care of this by caching and merging results when generating reports, so your
report actually includes coverage across your test suites and thereby gives you a better picture of blank spots.&lt;/p&gt;
&lt;p&gt;The official formatter of SimpleCov…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/simplecov-ruby/simplecov" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I spent a good chunk of this past weekend trying to integrate SimpleCov into a framework called &lt;a href="https://dev.tobased%20on%20Rails%20and%20another%20library%20[Netzke]"&gt;Marty&lt;/a&gt; that we work on at my company, &lt;a href="https://www.pennymacusa.com/" rel="noopener noreferrer"&gt;PennyMac Loan Services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://about.gitlab.com/product/continuous-integration/" rel="noopener noreferrer"&gt;GitLab CI&lt;/a&gt; at PennyMac for continuous development workflows, and while it's a fantastic piece of software, getting it to work with a code coverage tool like SimpleCov is not quite a "batteries included" situation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo7ztuscuyltcj919ja36.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo7ztuscuyltcj919ja36.png" alt="GitLab coverage in merge request"&gt;&lt;/a&gt;&lt;/p&gt;
Example of GitLab CI Coverage percentage in a Merge Request



&lt;p&gt;While SimpleCov does support merging coverage reports from different test libraries and suites, I ran into some difficulties getting it to merge results from multiple different runs using subsets of the same test suite. This is particularly important for our use case since we parallelize our test suite during CI pipeline runs by category, and we'd like to get an overarching coverage report for the whole suite instead of just the coverage of individual categories.&lt;/p&gt;

&lt;p&gt;This post assumes that you already know what SimpleCov is and have it integrated into a Ruby project, but want to learn how to use it effectively with GitLab CI to get the most out of it. Unfortunately, it won't cover deploying coverage reports to GitLab pages, as &lt;a href="https://about.gitlab.com/blog/2016/11/03/publish-code-coverage-report-with-gitlab-pages/" rel="noopener noreferrer"&gt;there is already a fantastic article&lt;/a&gt; (which has inspired large parts of this post) on the matter. Enjoy!&lt;/p&gt;

&lt;h1&gt;
  
  
  Quick setup details
&lt;/h1&gt;

&lt;p&gt;The most basic way to get started with SimpleCov is to add the Gem to your bundle, and then put something like this at the beginning of your &lt;code&gt;spec_helper.rb&lt;/code&gt;, or whatever conventional pre-test file you load before running your suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/spec_helper.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'simplecov'&lt;/span&gt;
&lt;span class="no"&gt;SimpleCov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;

&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RAILS_ENV'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt;
&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'TZ'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'America/Los_Angeles'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fine, although if you need to do any more configuration than this&lt;br&gt;
(which we will need for this scenario), I recommend having a helper file for&lt;br&gt;
SimpleCov. I put mine in &lt;code&gt;lib/&lt;/code&gt; so I could reuse it in different applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/marty/simplecov_helper.rb&lt;/span&gt;
&lt;span class="c1"&gt;# Credit goes to https://gitlab.com/gitlab-org/gitlab-foss/blob/master/spec/simplecov_env.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'simplecov'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'active_support/core_ext/numeric/time'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;SimpleCovHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_profile&lt;/span&gt;
    &lt;span class="no"&gt;SimpleCov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;enable_coverage&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'COVERAGE_METHOD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'line'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;

      &lt;span class="n"&gt;track_files&lt;/span&gt; &lt;span class="s1"&gt;'{app,config,lib,spec}/**/*.rb'&lt;/span&gt;

      &lt;span class="n"&gt;add_filter&lt;/span&gt; &lt;span class="s1"&gt;'db/migrate'&lt;/span&gt;
      &lt;span class="n"&gt;add_filter&lt;/span&gt; &lt;span class="s1"&gt;'vendor/'&lt;/span&gt;
      &lt;span class="n"&gt;add_filter&lt;/span&gt; &lt;span class="s1"&gt;'extjs/'&lt;/span&gt;

      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Channels'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/channels'&lt;/span&gt;
      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Netzke Components'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/components'&lt;/span&gt;
      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Controllers'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/controllers'&lt;/span&gt;
      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Helpers'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/helpers'&lt;/span&gt;
      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Jobs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'app/jobs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/workers'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Mailers'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/mailers'&lt;/span&gt;
      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Models'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/models'&lt;/span&gt;
      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Services'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/services'&lt;/span&gt;
      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Configs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'config/'&lt;/span&gt;
      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Libraries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lib/'&lt;/span&gt;
      &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Specs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'spec/'&lt;/span&gt;

      &lt;span class="n"&gt;use_merging&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;merge_timeout&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start!&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'COVERAGE'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;

    &lt;span class="n"&gt;configure_profile&lt;/span&gt;

    &lt;span class="no"&gt;SimpleCov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick explanation of what's going on here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We expose the &lt;code&gt;SimpleCov.start&lt;/code&gt; function via &lt;code&gt;start&lt;/code&gt; in the module. That allows us to add more options before actually starting coverage tracking.&lt;/li&gt;
&lt;li&gt;We only run coverage if the &lt;code&gt;COVERAGE&lt;/code&gt; environment variable is set. Coverage generation is potentially an expensive operation and can make RSpec suite runs take much longer, so it should be an opt-in feature.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also call the &lt;code&gt;configure_profile&lt;/code&gt; method, which defines some basic but&lt;br&gt;
helpful configuration variables.&lt;/p&gt;

&lt;p&gt;SimpleCov usually recommends using the &lt;code&gt;rails&lt;/code&gt; profile when calling &lt;code&gt;start&lt;/code&gt;.&lt;br&gt;
This works for most people; however, it suppresses coverage for the &lt;code&gt;config/&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;spec/&lt;/code&gt; directories, which I wanted. So I chose instead to build a similiar&lt;br&gt;
configuration to the &lt;code&gt;rails&lt;/code&gt; profile, but leaving in coverage for &lt;code&gt;config/&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;spec/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/marty/simplecov_profile.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'simplecov'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'active_support/core_ext/numeric/time'&lt;/span&gt;

&lt;span class="no"&gt;SimpleCov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="ss"&gt;:marty&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;enable_coverage&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'COVERAGE_METHOD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'line'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;

  &lt;span class="n"&gt;track_files&lt;/span&gt; &lt;span class="s1"&gt;'{app,config,lib,spec}/**/*.rb'&lt;/span&gt;

  &lt;span class="n"&gt;add_filter&lt;/span&gt; &lt;span class="s1"&gt;'db/migrate'&lt;/span&gt;
  &lt;span class="n"&gt;add_filter&lt;/span&gt; &lt;span class="s1"&gt;'vendor/'&lt;/span&gt;
  &lt;span class="n"&gt;add_filter&lt;/span&gt; &lt;span class="s1"&gt;'extjs/'&lt;/span&gt;

  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Channels'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/channels'&lt;/span&gt;
  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Netzke Components'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/components'&lt;/span&gt;
  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Controllers'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/controllers'&lt;/span&gt;
  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Helpers'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/helpers'&lt;/span&gt;
  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Jobs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'app/jobs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/workers'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Mailers'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/mailers'&lt;/span&gt;
  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Models'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/models'&lt;/span&gt;
  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Services'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/services'&lt;/span&gt;
  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Configs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'config/'&lt;/span&gt;
  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Libraries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lib/'&lt;/span&gt;
  &lt;span class="n"&gt;add_group&lt;/span&gt; &lt;span class="s1"&gt;'Specs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'spec/'&lt;/span&gt;

  &lt;span class="n"&gt;use_merging&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;merge_timeout&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows me to reuse this profile in multiple applications with little hassle.&lt;br&gt;
It also makes my &lt;code&gt;SimpleCovHelper&lt;/code&gt; module look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/support/simplecov_helper.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'marty/simplecov_helper'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;SimpleCovHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start!&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'COVERAGE'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;

    &lt;span class="no"&gt;SimpleCov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt; &lt;span class="ss"&gt;:marty&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also like to pre-require my SimpleCov Helper using RSpec's dotfile: &lt;code&gt;.rspec&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--require marty/simplecov_helper
--require spec_helper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This requires &lt;code&gt;marty/simplecov_helper&lt;/code&gt; and then &lt;code&gt;spec_helper&lt;/code&gt; in that order,&lt;br&gt;
allowing me to just throw this at the very beginning of &lt;code&gt;spec_helper&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/spec_helper.rb&lt;/span&gt;

&lt;span class="no"&gt;Marty&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SimpleCovHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start!&lt;/span&gt;

&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RAILS_ENV'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt;
&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'TZ'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'America/Los_Angeles'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Getting Coverage in GitLab CI
&lt;/h1&gt;

&lt;p&gt;If you enable coverage and run any RSpec test (a single file or a whole suite), you'll get something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ COVERAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec
.................................

Finished &lt;span class="k"&gt;in &lt;/span&gt;1 minute 15.14 seconds &lt;span class="o"&gt;(&lt;/span&gt;files took 1.73 seconds to load&lt;span class="o"&gt;)&lt;/span&gt;
249 examples, 0 failures, 8 pending

Coverage report generated &lt;span class="k"&gt;for &lt;/span&gt;RSpec to &lt;span class="c"&gt;#{Rails.root}/coverage. 1170 / 1230 LOC (95.12%) covered.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See that last part down there? The one that says &lt;code&gt;1170 / 1230 LOC&lt;/code&gt;? That's the results of the test coverage report, and it's exactly what we're looking to parse. Specifically, the percentage.&lt;/p&gt;

&lt;p&gt;GitLab CI's configuration will live in a &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file in the root of your project, and there is a &lt;a href="https://docs.gitlab.com/ce/ci/yaml/" rel="noopener noreferrer"&gt;great detail of rigorous configuration&lt;/a&gt; that can go into it. What we specifically care about here is your job that runs your RSpec test. Here is a rudimentary example of such job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;

&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby:2.6.3-buster&lt;/span&gt;

&lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gem install bundler&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle install --jobs $(nproc) --path vendor "${FLAGS[@]}"&lt;/span&gt;

&lt;span class="na"&gt;rspec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After enabling coverage via &lt;code&gt;variables&lt;/code&gt;, we will need to extract it from the output of the job, and this is exactly what the &lt;code&gt;coverage&lt;/code&gt; key is for!&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;coverage&lt;/code&gt; key takes a regular expression that matches the output percentage of your job, and embeds it into the results that we saw in the above screenshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;

&lt;span class="na"&gt;rspec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/LOC\s\(\d+\.\d+%\)\scovered/'&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;COVERAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is most likely not the simplest regular expression to match &lt;code&gt;"LOC (xx.xx%) covered"&lt;/code&gt;, but it's working fine for me so far, so feel free to use it.&lt;/p&gt;

&lt;p&gt;GitLab CI will now pick up parse the output of the job looking for a string matching that regex, and when it finds it, it'll display it in your Merge Request page and job results!&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing Artifacts
&lt;/h2&gt;

&lt;p&gt;Since you may want to view your coverage reports generated by CI in the future, I suggest releasing the reports as job artifacts so they can be cached and downloaded later for review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;

&lt;span class="na"&gt;rspec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/LOC\s\(\d+\.\d+%\)\scovered/'&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;COVERAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coverage/"&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pipeline&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$CI_PIPELINE_ID&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Coverage&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Report"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This'll generate an artifact with a unique, descriptive name for each pipeline that runs, including the pipeline's ID (viewable through your MR or the Pipelines section in CI/CD).&lt;/p&gt;

&lt;h1&gt;
  
  
  Multiple/Parallelized Test Suite
&lt;/h1&gt;

&lt;p&gt;Things get much more interesting if you decide to parallelize your test suite; this means you don't run all of your specs in one go, but instead in different CI jobs. This is fine, but it makes getting complete suite coverage much more difficult.&lt;/p&gt;

&lt;p&gt;To solve for this use case, we'll have to find a way to combine all of our results in one place and merge them (and fortunately, SimpleCov already has us covered on that front).&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting all your coverage results together
&lt;/h2&gt;

&lt;p&gt;Suppose that your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; looks something like the above examples, except that you've decided to use a base job to split off your rather massive test suite into multiple categories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;

&lt;span class="na"&gt;.base-rspec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/LOC\s\(\d+\.\d+%\)\scovered/'&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby:2.6.3-buster&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gem install bundler&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle install --jobs $(nproc) --path vendor "${FLAGS[@]}"&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;COVERAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;

&lt;span class="na"&gt;rspec-models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.base-rspec&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec spec/models&lt;/span&gt;

&lt;span class="na"&gt;rspec-controllers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.base-rspec&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec spec/controllers&lt;/span&gt;

&lt;span class="na"&gt;rspec-lib&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.base-rspec&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec spec/lib&lt;/span&gt;

&lt;span class="na"&gt;rspec-features&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.base-rspec&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec spec/features&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each one of these guys will generate its own unique coverage report, but they won't be complete.&lt;br&gt;
Most likely, one category of tests does not span all your project's files and as such will result in odd, incomplete reports of &lt;code&gt;51.43%&lt;/code&gt; or even &lt;code&gt;37.22%&lt;/code&gt; coverage.&lt;/p&gt;

&lt;p&gt;What we actually want is the product of all of our divided test suites together, and this has to be done by &lt;strong&gt;combining them in a single job, and then generating the report&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To do that, create a new stage in your pipeline stages called &lt;code&gt;codecov&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;codecov&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could also use the default &lt;code&gt;deploy&lt;/code&gt; stage, but it has to be any stage that comes after &lt;code&gt;test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To combine all the reports, we're going to want them to have unique names when they all end up together in the code coverage job, so one doesn't overwrite the other. SimpleCov has a mechanism to give jobs unique names, and we can do this based on environment variables.&lt;/p&gt;

&lt;p&gt;Add this to the SimpleCov Profile that we created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/marty/simplecov_helper.rb&lt;/span&gt;

&lt;span class="no"&gt;SimpleCov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="ss"&gt;:marty&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'GITLAB_CI'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'CI_JOB_NAME'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;coverage_dir&lt;/span&gt; &lt;span class="s2"&gt;"coverage/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;command_name&lt;/span&gt; &lt;span class="n"&gt;job_name&lt;/span&gt;
    &lt;span class="no"&gt;SimpleCov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at_exit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;SimpleCov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now, if the job is titled &lt;code&gt;rspec-features&lt;/code&gt;, SimpleCov will spit out the results in a folder called &lt;code&gt;coverage/rspec-features"&lt;/code&gt; in the root.&lt;/p&gt;

&lt;p&gt;Next, we want to make sure we pass on our individual, uniquely named coverage reports to the job that will combine them, and we will do this using artifacts. Take our &lt;code&gt;.base-rspec&lt;/code&gt; job and add the following &lt;code&gt;artifacts&lt;/code&gt; field to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;

&lt;span class="na"&gt;.base-rspec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/LOC\s\(\d+\.\d+%\)\scovered/'&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby:2.6.3-buster&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gem install bundler&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle install --jobs $(nproc) --path vendor "${FLAGS[@]}"&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;COVERAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coverage/$CI_JOB_NAME"&lt;/span&gt; &lt;span class="c1"&gt;# look for the unique CI_JOB_NAME folder for this run&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$CI_JOB_NAME&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Coverage"&lt;/span&gt; &lt;span class="c1"&gt;# Give it a unique artifact name&lt;/span&gt;
    &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt; &lt;span class="c1"&gt;# Always extract the artifact, even if not all tests passed.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have all of our coverage reports being passed on as artifacts into one job, and we just have to take care of merging them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Merging
&lt;/h2&gt;

&lt;p&gt;If you followed my instructions, your hypothetical &lt;code&gt;coverage/&lt;/code&gt; folder in your final, aggregating job should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#{Rails.root}/coverage
 ├─"rspec-models"
 ├─"rspec-controllers"
 ├─"rspec-lib"
 └─"rspec-features"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now want to merge all of our results together. &lt;br&gt;
To do that, we're going to use the wonderful &lt;code&gt;collate&lt;/code&gt; functionality&lt;br&gt;
built into SimpleCov.&lt;/p&gt;

&lt;p&gt;Define this method, &lt;code&gt;merge_all_results!&lt;/code&gt; in your&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/marty/simplecov_helper.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;SimpleCovHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge_all_results!&lt;/span&gt;
    &lt;span class="c1"&gt;# Collate and combine all the previous coverage results that we produced&lt;/span&gt;
    &lt;span class="c1"&gt;# in other RSpec runs. Combine them using the custom profile we created.&lt;/span&gt;
    &lt;span class="c1"&gt;# This method also handles storing them for you&lt;/span&gt;
    &lt;span class="no"&gt;SimpleCov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'coverage/**/.resultset.json'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;:marty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Singleton responsible for keeping track of the last merged result in&lt;/span&gt;
    &lt;span class="c1"&gt;# memory&lt;/span&gt;
    &lt;span class="n"&gt;merged_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SimpleCov&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ResultMerger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merged_result&lt;/span&gt;

    &lt;span class="c1"&gt;# Print out to console all the groups and their percents + hits/line.&lt;/span&gt;
    &lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merged_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;covered_percent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;covered_strength&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Sort by percentage and print everything&lt;/span&gt;
    &lt;span class="n"&gt;sorted_groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_gr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_str&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;sorted_groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;gr_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;
      &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"Group '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;gr_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;': &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;percent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; covered at &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;strength&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; hits/line"&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;merged_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This recursively globs all &lt;code&gt;.resultset.json&lt;/code&gt; files from the &lt;code&gt;coverage/&lt;/code&gt; folder &lt;br&gt;
(which include details of each coverage run), reads and merges them for you.&lt;/p&gt;

&lt;p&gt;We want a way to be able to call this code from our CI job, so let's use a Rake task for that. Insert this into your &lt;code&gt;Rakefile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Rakefile&lt;/span&gt;

&lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s1"&gt;'Merge the results of various SimpleCov coverage reports'&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;merge_coverage_reports: :environment&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'lib/marty/simplecov_helper'&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Merging code coverage reports...'&lt;/span&gt;
    &lt;span class="no"&gt;SimpleCovHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge_all_results!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finishing CI Touches
&lt;/h2&gt;

&lt;p&gt;The last thing we want to do is to create the aggregating &lt;code&gt;code coverage&lt;/code&gt; job. It will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;

&lt;span class="na"&gt;code coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codecov&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt; &lt;span class="c1"&gt;# Always try to generate a code coverage report&lt;/span&gt;
  &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/LOC\s\(\d+\.\d+%\)\scovered/'&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle exec rake merge_coverage_reports&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Release the final, merged coverage for review&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coverage/"&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pipeline&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$CI_PIPELINE_ID&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Merged&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Coverage"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you did everything right, your pipeline should now look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fezkzmedb32cem4s0gp1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fezkzmedb32cem4s0gp1n.png" alt="Final pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Code Coverage is not a perfect metric by any means, but I think it's a fantastic heuristic to figure out where to focus testing efforts, an undertaking that my team will soon begin.&lt;/p&gt;

&lt;p&gt;This is the first article that I've ever written, and I appreciate you for reading it. I truly hope saves you a weekend of work!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>simplecov</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>Can someone help me understand the direction React design patterns are moving in?</title>
      <dc:creator>Omri Gabay</dc:creator>
      <pubDate>Wed, 03 Jul 2019 20:04:43 +0000</pubDate>
      <link>https://dev.to/omrisama/can-someone-help-me-understand-the-direction-react-design-patterns-are-moving-in-im2</link>
      <guid>https://dev.to/omrisama/can-someone-help-me-understand-the-direction-react-design-patterns-are-moving-in-im2</guid>
      <description>&lt;p&gt;Before Hooks were really a thing, the most definitive design pattern I had experienced in React was the "Redux with Container Components that encapsulate Presentational Components" pattern. There was a very clear distinction between Class-based (stateful) components, and Functional (stateless, presentational) components. You'd be encouraged to "Lift state up" and -- from what it seems like to me -- store as much state as you can in a single container component so that it can be drilled down maybe 2 or 3 levels at most to presentational components. More levels than that, and you were probably looking at a new Stateful component.&lt;br&gt;
And of course, all Class/Stateful components were connected to Redux store.&lt;/p&gt;

&lt;p&gt;Then Hooks came about, and the line between "which components should or shouldn't have state" became very blurry. And let's not even bring up Context. &lt;br&gt;
An aside: I'm not even sure if Context is considered a stable enough solution (yet) to replace Redux completely for apps of all sizes. Context usage seems like the wild west at this point between people trying to use it exactly like Redux, to people building libraries like &lt;code&gt;reactn&lt;/code&gt; and &lt;code&gt;constate&lt;/code&gt; around it, and using each Redux reducer as an individual context or something. Who knows.&lt;/p&gt;

&lt;p&gt;Are Hooks supposed to encourage us to decentralize state by writing more stateful components? Or are they just supposed to be a new paradigm for writing stateful components, and the idea of big centralized stateful components isn't going away?&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>design</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
