Explore all Code Coverage Tools open source software, libraries, packages, source code, cloud functions and APIs.

Popular New Releases in Code Coverage Tools

coverlet

v5.7.2

codecov-action

v3.1.0

grcov

code-coverage

v3.10.0-dev.1

coveralls.net

3.0.0

Popular Libraries in Code Coverage Tools

coverlet

by coverlet-coverage doticoncsharpdoticon

star image 2329 doticonMIT

Cross platform code coverage for .NET

codecov-action

by codecov doticontypescriptdoticon

star image 944 doticonMIT

GitHub Action that uploads coverage to Codecov :open_umbrella:

grcov

by mozilla doticonrustdoticon

star image 687 doticonMPL-2.0

Rust tool to collect and aggregate code coverage data for multiple source files

code-this-not-that-js

by codediodeio doticonjavascriptdoticon

star image 421 doticon

JavaScript Pro Tips - Code This, Not That

JSCover

by tntim96 doticonjavadoticon

star image 385 doticonGPL-2.0

JSCover is a JavaScript Code Coverage Tool that measures line, branch and function coverage

learn-istanbul

by dwyl doticonjavascriptdoticon

star image 333 doticon

:checkered_flag: Learn how to use the Istanbul JavaScript Code Coverage Tool

sourcecode

by rabbitinaction doticonpythondoticon

star image 330 doticonNOASSERTION

Examples and source code from the book RabbitMQ in Action.

code-coverage

by cypress-io doticonjavascriptdoticon

star image 296 doticonMIT

Saves the code coverage collected during Cypress tests

istanbul-instrumenter-loader

by webpack-contrib doticonjavascriptdoticon

star image 274 doticonMIT

Istanbul Instrumenter Loader

Trending New libraries in Code Coverage Tools

istanbul-badges-readme

by olavoparno doticontypescriptdoticon

star image 74 doticonMIT

Creates and updates README testing coverage badges with your json-summary

octocov

by k1LoW doticongodoticon

star image 57 doticon

octocov is a toolkit for collecting code metrics (code coverage, code to test ratio and test execution time).

jacoco-badge-generator

by cicirello doticonpythondoticon

star image 28 doticonMIT

Coverage badges, and pull request coverage checks, from JaCoCo reports in GitHub Actions

cococo

by mysugr doticonswiftdoticon

star image 26 doticonMIT

Code Coverage Converter from Xcode 11 to SonarQube

splunk-app-testing

by splunk doticonshelldoticon

star image 25 doticonApache-2.0

sample app along with a CICD pipeline for testing multiple versions of splunk

vite-plugin-istanbul

by iFaxity doticontypescriptdoticon

star image 19 doticonMIT

A Vite plugin to instrument code for nyc/istanbul code coverage. In similar way as the Webpack Loader istanbul-instrumenter-loader. Only intended for use in development.

check-code-coverage

by bahmutov doticonjavascriptdoticon

star image 10 doticon

Utilities for checking the coverage produced by NYC against extra or missing files

pkce

by RomeoDespres doticonpythondoticon

star image 8 doticonMIT

Simple Python module to generate PKCE code verifier and code challenge

spm-lcov-action

by maxep doticonshelldoticon

star image 8 doticonMIT

Swift Package Manager Code Coverage Report

Top Authors in Code Coverage Tools

1

codecov

9 Libraries

star icon1637

2

bahmutov

4 Libraries

star icon31

3

ariya

2 Libraries

star icon26

4

microsoft

2 Libraries

star icon55

5

mozilla

2 Libraries

star icon691

6

dwyl

2 Libraries

star icon352

7

CoderK

1 Libraries

star icon8

8

mattpolzin

1 Libraries

star icon13

9

eddyerburgh

1 Libraries

star icon31

10

JoeStead

1 Libraries

star icon5

1

9 Libraries

star icon1637

2

4 Libraries

star icon31

3

2 Libraries

star icon26

4

2 Libraries

star icon55

5

2 Libraries

star icon691

6

2 Libraries

star icon352

7

1 Libraries

star icon8

8

1 Libraries

star icon13

9

1 Libraries

star icon31

10

1 Libraries

star icon5

Trending Kits in Code Coverage Tools

No Trending Kits are available at this moment for Code Coverage Tools

Trending Discussions on Code Coverage Tools

Why is CodeCoverage.exe producing near empty .coverage Files?

GitLab Docker Runner to reuse installed software layers

Convert the last generated .Coverage into coveragexml for SonarQubee in TFS 2017

Making assertions from non-test-case classes

QUESTION

Why is CodeCoverage.exe producing near empty .coverage Files?

Asked 2022-Mar-25 at 19:29

In our Jenkins pipeline, we use SonarQube to report on our code coverage. After running all of our unit/integration tests to produce the .coverage file, we need to analyze this file to create the ".coverage.coveragexml" which is ultimately what is used by SonarQube to interpret the code coverage. We do this by using the CodeCoverage.exe:

1"C:\Program Files (x86)\Microsoft Visual Studio\2022\TestAgent\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe" analyze /output:"{somePath}\{someName}.coverage.coveragexml" "{somePath}\{someName}.coverage"
2

This command appears to be working, but when you run dir /s *.coveragexml (within the directory), it displays something like:

1"C:\Program Files (x86)\Microsoft Visual Studio\2022\TestAgent\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe" analyze /output:"{somePath}\{someName}.coverage.coveragexml" "{somePath}\{someName}.coverage"
2Directory of C:\jenkins\path\to\TestResults\coverageFile
303/22/2022  04:59 PM                64 ContainerAdministrator_DC420D3FA0BA_2022-03-22.16_46_43.coverage.coveragexml
41 File(s)             64 bytes
5

64 bytes is practically nothing - and I believe this is the reason why our SonarQube metrics show we have 0 coverage now.

I added the same dir command, only this time to check for the .coverage file(s), and those come back with only 10 bytes in them - making me think that these files are essentially empty. I saw this post that seems to be a similar issue. The accepted answer said to change the platform type from x86 to x64, but that did not work in my case.

The vstest.console command for running our tests is:

1"C:\Program Files (x86)\Microsoft Visual Studio\2022\TestAgent\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe" analyze /output:"{somePath}\{someName}.coverage.coveragexml" "{somePath}\{someName}.coverage"
2Directory of C:\jenkins\path\to\TestResults\coverageFile
303/22/2022  04:59 PM                64 ContainerAdministrator_DC420D3FA0BA_2022-03-22.16_46_43.coverage.coveragexml
41 File(s)             64 bytes
5vstest.console /Parallel /EnableCodeCoverage /Logger:trx /Platform:x86 ".\somePath\Test.dll"
6

This issue originally started back when we made a change to our Jenkinsfile for it to use Visual Studio 2022 instead of 2019 (the base image was updated) in the command that started the CodeCoverage executable.

What could be causing the coverage files to be nearly/completely empty and how can I fix it?

ANSWER

Answered 2022-Mar-25 at 19:29

It seems the base image we use must have a non-enterprise edition of the Code Coverage tools (which is a requirement). We tested our SonarQube projects commands locally using an enterprise edition of the tools (I have Visual Studio 2022 Enterprise installed on my machine), and the coverage files produced contain the correct data. However, when we used a Visual Studio Professional install, the files are empty just like our Jenkins pipeline.

As stated, this started happening when the base image was updated - in particular it was around November 8th 2021. It seems the base docker image we were using (mcr.microsoft.com/dotnet/framework/sdk:4.8-20220210-windowsservercore-ltsc2019) has the latest 2022 tools, but it must not be an enterprise edition - hence the empty files.

We switched our pipeline over to using dotCover instead to perform the analysis, which works as expected and our SonarQube coverage is back to normal.

Source https://stackoverflow.com/questions/71587878

QUESTION

GitLab Docker Runner to reuse installed software layers

Asked 2020-Jan-29 at 15:42

A very typical scenario with GitLab CI is to install a few packages you need for your jobs (linters, code coverage tools, deployment-specific helpers and so on) and to then run your actual stages/steps of a building, testing and deploying your software.

The Docker runner is a very neat and clean solution, but it seems very wasteful to always run the steps that install the base software. Normally, Docker is able to cache such layers, but with the way the GitLab Docker runner works, that doesn't happen.

Do we realize that setting up another project to produce pre-configured Docker images would be one solution, but are there any better ones? Basically, what we want to say is: "If the before section hasn't changed, you can reuse the image from last time, no need to reinstall wget or whatever".

Any solution like that out there?

ANSWER

Answered 2020-Jan-29 at 14:23

You can use the registry of your gitlab project.

eg.

1images:
2    stage: build
3    image: docker
4    services:
5        - docker:dind
6    script:
7        - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY # login
8        # pull the current image or in case the image does not exit, do not stop the script:
9        - docker pull $CI_REGISTRY_IMAGE:latest || true
10        # build with the pulled image as cache:
11        - docker build --pull --cache-from $CI_REGISTRY_IMAGE:latest -t "$CI_REGISTRY_IMAGE:latest" .
12        # push the final image:
13        - docker push "$CI_REGISTRY_IMAGE:latest"
14
15

This way docker build will profit from the work done by the last run of the job. See the docs. Maybe you want to avoid unnecessary runs by some rules.

Source https://stackoverflow.com/questions/59964671

QUESTION

Convert the last generated .Coverage into coveragexml for SonarQubee in TFS 2017

Asked 2020-Jan-29 at 09:54

I am using .Net Core Test --collect "Code coverage" to generate a coverage file, I need to convert this for sonarqube, the issue is I do not nave the name of the file thats generated as its placed in a folder with a guid name and the file name itself is a GUID all under the TestResults folder

The following script works to convert .coverage files into coveragexml, but its for the whole working directory

1Get-ChildItem -Recurse -Filter "*.coverage" | % {
2$outfile = "$([System.IO.Path]::GetFileNameWithoutExtension($_.FullName)).coveragexml"
3$output = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($_.FullName), $outfile)
4"Analyse '$($_.Name)' with output '$outfile'..."
5. "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Team Tools\Dynamic Code Coverage Tools\codecoverage.exe" analyze /output:$output $_.FullName
6}
7

But this converts the whole directory and after several days there are many builds, all I want to do it convert the .coverage file generated by the current build, so I need to be able to isolate the .coverage files generated by the current build.

ANSWER

Answered 2020-Jan-29 at 09:52

So you want to take only the last created code coverage file, you can filter the Get-ChiledItem results to get the last one:

1Get-ChildItem -Recurse -Filter "*.coverage" | % {
2$outfile = "$([System.IO.Path]::GetFileNameWithoutExtension($_.FullName)).coveragexml"
3$output = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($_.FullName), $outfile)
4"Analyse '$($_.Name)' with output '$outfile'..."
5. "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Team Tools\Dynamic Code Coverage Tools\codecoverage.exe" analyze /output:$output $_.FullName
6}
7Get-ChildItem -Recurse -Filter "*.coverage" | sort LastWriteTime | select -last 1
8

Source https://stackoverflow.com/questions/59962345

QUESTION

Making assertions from non-test-case classes

Asked 2020-Jan-16 at 02:47
Background

I have a rails model that contains an ActiveRecord::Enum. I have a view helper that takes a value of this enum, and returns one of several possible responses. Suppose the cases were called enum_cases, for example:

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11

I want to unit-test this code. Checking the happy paths is trivial:

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11class FooHelperTests < ActionView::TestCase
12  test "foo handles all enum cases" do
13    assert_equal foo(:a), 1
14    assert_equal foo(:b), 2
15    assert_equal foo(:c), 3
16    assert_raises NotImplementedError do
17        foo(:d)
18    end
19  end
20end
21

However, this has a flaw. If new cases are added (e.g. :z), foo will raise an error to bring our attention to it, and add it as a new case. But nothing stops you from forgetting to update the test to test for the new behaviour for :z. Now I know that's mainly the job of code coverage tools, and we do use one, but just not to such a strict level that single-line gaps will blow up. Plus this is kind of a learning exercise, anyway.

So I amended my test:

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11class FooHelperTests < ActionView::TestCase
12  test "foo handles all enum cases" do
13    assert_equal foo(:a), 1
14    assert_equal foo(:b), 2
15    assert_equal foo(:c), 3
16    assert_raises NotImplementedError do
17        foo(:d)
18    end
19  end
20end
21test "foo handles all enum cases" do
22  remaining_cases = enum_cases.to_set
23
24  tester = -> (arg) do
25    remaining_cases.delete(arg)
26    foo(arg)
27  end
28
29  assert_equal tester.call(:a), 1
30  assert_equal tester.call(:b), 2
31  assert_equal tester.call(:c), 3
32  assert_raises NotImplementedError do
33    tester.call(:d)
34  end
35
36  assert_empty remaining_cases, "Not all cases were tested! Remaining: #{remaining_cases}"
37end
38

This works great, however it's got 2 responsibilities, and it's a pattern I end up copy/pasting (I have multiple functions to test like this):

  1. Perform the actual testing of foo
  2. Do book keeping to ensure all params were exhausitvely checked.

I would like to make this test more focused by removing as much boiler plate as possible, and extracting it out to a place where it can easily be reused.

Attempted solution

In another language, I would just extract a simple test helper:

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11class FooHelperTests < ActionView::TestCase
12  test "foo handles all enum cases" do
13    assert_equal foo(:a), 1
14    assert_equal foo(:b), 2
15    assert_equal foo(:c), 3
16    assert_raises NotImplementedError do
17        foo(:d)
18    end
19  end
20end
21test "foo handles all enum cases" do
22  remaining_cases = enum_cases.to_set
23
24  tester = -> (arg) do
25    remaining_cases.delete(arg)
26    foo(arg)
27  end
28
29  assert_equal tester.call(:a), 1
30  assert_equal tester.call(:b), 2
31  assert_equal tester.call(:c), 3
32  assert_raises NotImplementedError do
33    tester.call(:d)
34  end
35
36  assert_empty remaining_cases, "Not all cases were tested! Remaining: #{remaining_cases}"
37end
38class ExhaustivityChecker
39  def initialize(all_values, proc)
40    @remaining_values = all_values.to_set
41    @proc = proc
42  end
43
44  def run(arg, allow_invalid_args: false)
45    assert @remaining_values.include?(arg) unless allow_invalid_args 
46    @remaining_values.delete(arg)
47    @proc.call(arg)
48  end
49
50  def assert_all_values_checked
51    assert_empty @remaining_values, "Not all values were tested! Remaining: #{@remaining_values}"
52  end
53end
54

Which I could easily use like:

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11class FooHelperTests < ActionView::TestCase
12  test "foo handles all enum cases" do
13    assert_equal foo(:a), 1
14    assert_equal foo(:b), 2
15    assert_equal foo(:c), 3
16    assert_raises NotImplementedError do
17        foo(:d)
18    end
19  end
20end
21test "foo handles all enum cases" do
22  remaining_cases = enum_cases.to_set
23
24  tester = -> (arg) do
25    remaining_cases.delete(arg)
26    foo(arg)
27  end
28
29  assert_equal tester.call(:a), 1
30  assert_equal tester.call(:b), 2
31  assert_equal tester.call(:c), 3
32  assert_raises NotImplementedError do
33    tester.call(:d)
34  end
35
36  assert_empty remaining_cases, "Not all cases were tested! Remaining: #{remaining_cases}"
37end
38class ExhaustivityChecker
39  def initialize(all_values, proc)
40    @remaining_values = all_values.to_set
41    @proc = proc
42  end
43
44  def run(arg, allow_invalid_args: false)
45    assert @remaining_values.include?(arg) unless allow_invalid_args 
46    @remaining_values.delete(arg)
47    @proc.call(arg)
48  end
49
50  def assert_all_values_checked
51    assert_empty @remaining_values, "Not all values were tested! Remaining: #{@remaining_values}"
52  end
53end
54test "foo handles all enum cases" do
55    tester = ExhaustivityChecker.new(enum_cases, -> (arg) { foo(arg) })
56
57    assert_equal tester.run(:a), 1
58    assert_equal tester.run(:b), 2
59    assert_equal tester.run(:c), 3
60    assert_raises NotImplementedError do
61        tester.run(:d, allow_invalid_args: true)
62    end
63
64    tester.assert_all_values_checked
65end
66

I could then reuse this class in other tests, just by passing it different all_values and proc arguments, and remembering to call assert_all_values_checked.

Issue

However, this breaks because I can't call assert and assert_empty from a class that isn't a subclass of ActionView::TestCase. Is it possible to subclass/include some class/module to gain access to these methods?

ANSWER

Answered 2020-Jan-16 at 01:36

enum_cases must be kept up to date when the production logic changes violating the DRY principle. This makes it more likely for there to be a mistake. Furthermore it is test code living in production, another red flag.

We can solve this by refactoring the case into a Hash lookup making it data driven. And also giving it a name describing what it's associated with and what it does, these are "handlers". I've also turned it into a method call making it easier to access and which will bear fruit later.

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11class FooHelperTests < ActionView::TestCase
12  test "foo handles all enum cases" do
13    assert_equal foo(:a), 1
14    assert_equal foo(:b), 2
15    assert_equal foo(:c), 3
16    assert_raises NotImplementedError do
17        foo(:d)
18    end
19  end
20end
21test "foo handles all enum cases" do
22  remaining_cases = enum_cases.to_set
23
24  tester = -> (arg) do
25    remaining_cases.delete(arg)
26    foo(arg)
27  end
28
29  assert_equal tester.call(:a), 1
30  assert_equal tester.call(:b), 2
31  assert_equal tester.call(:c), 3
32  assert_raises NotImplementedError do
33    tester.call(:d)
34  end
35
36  assert_empty remaining_cases, "Not all cases were tested! Remaining: #{remaining_cases}"
37end
38class ExhaustivityChecker
39  def initialize(all_values, proc)
40    @remaining_values = all_values.to_set
41    @proc = proc
42  end
43
44  def run(arg, allow_invalid_args: false)
45    assert @remaining_values.include?(arg) unless allow_invalid_args 
46    @remaining_values.delete(arg)
47    @proc.call(arg)
48  end
49
50  def assert_all_values_checked
51    assert_empty @remaining_values, "Not all values were tested! Remaining: #{@remaining_values}"
52  end
53end
54test "foo handles all enum cases" do
55    tester = ExhaustivityChecker.new(enum_cases, -> (arg) { foo(arg) })
56
57    assert_equal tester.run(:a), 1
58    assert_equal tester.run(:b), 2
59    assert_equal tester.run(:c), 3
60    assert_raises NotImplementedError do
61        tester.run(:d, allow_invalid_args: true)
62    end
63
64    tester.assert_all_values_checked
65end
66def foo_handlers
67  {
68    a: 1,
69    b: 2,
70    c: 3
71  }.freeze
72end
73
74def foo(input)
75  foo_handlers.fetch(input)
76rescue KeyError
77  raise NotImplementedError, "Unhandled new case: #{input}"
78end
79

Hash#fetch is used to raise a KeyError if the input is not found.

Then we can write a data driven test by looping through, not foo_handlers, but a seemingly redundant expected Hash defined in the tests.

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11class FooHelperTests < ActionView::TestCase
12  test "foo handles all enum cases" do
13    assert_equal foo(:a), 1
14    assert_equal foo(:b), 2
15    assert_equal foo(:c), 3
16    assert_raises NotImplementedError do
17        foo(:d)
18    end
19  end
20end
21test "foo handles all enum cases" do
22  remaining_cases = enum_cases.to_set
23
24  tester = -> (arg) do
25    remaining_cases.delete(arg)
26    foo(arg)
27  end
28
29  assert_equal tester.call(:a), 1
30  assert_equal tester.call(:b), 2
31  assert_equal tester.call(:c), 3
32  assert_raises NotImplementedError do
33    tester.call(:d)
34  end
35
36  assert_empty remaining_cases, "Not all cases were tested! Remaining: #{remaining_cases}"
37end
38class ExhaustivityChecker
39  def initialize(all_values, proc)
40    @remaining_values = all_values.to_set
41    @proc = proc
42  end
43
44  def run(arg, allow_invalid_args: false)
45    assert @remaining_values.include?(arg) unless allow_invalid_args 
46    @remaining_values.delete(arg)
47    @proc.call(arg)
48  end
49
50  def assert_all_values_checked
51    assert_empty @remaining_values, "Not all values were tested! Remaining: #{@remaining_values}"
52  end
53end
54test "foo handles all enum cases" do
55    tester = ExhaustivityChecker.new(enum_cases, -> (arg) { foo(arg) })
56
57    assert_equal tester.run(:a), 1
58    assert_equal tester.run(:b), 2
59    assert_equal tester.run(:c), 3
60    assert_raises NotImplementedError do
61        tester.run(:d, allow_invalid_args: true)
62    end
63
64    tester.assert_all_values_checked
65end
66def foo_handlers
67  {
68    a: 1,
69    b: 2,
70    c: 3
71  }.freeze
72end
73
74def foo(input)
75  foo_handlers.fetch(input)
76rescue KeyError
77  raise NotImplementedError, "Unhandled new case: #{input}"
78end
79class FooHelperTests < ActionView::TestCase
80  test "foo handles all expected inputs" do
81    expected = {
82      a: 1,
83      b: 2,
84      c: 3
85    }.freeze
86
87    # Verify expect has all the cases.
88    assert_equal expect.keys.sort, foo_handlers.keys.sort
89
90    # Drive the test with the expected results, not with the production data.
91    expected.keys do |key|
92      # Again, using `fetch` to get a clear KeyError rather than nil.
93      assert_equal foo(key), expected.fetch(value)
94    end
95  end
96
97  # Simplify the tests by separating happy path from error path.
98  test "foo raises NotImplementedError if the input is not handled" do
99    assert_raises NotImplementedError do
100      # Use something that obviously does not exist to future proof the test.
101      foo(:does_not_exist)
102    end
103  end
104end
105

The redundancy between expected and foo_handlers is by design. You still need to change the pairs in both places, there's no way around that, but now you'll always get a failure when foo_handlers changes but the tests do not.

  • When a new key/value pair is added to foo_handlers the test will fail.
  • If a key is missing from expected the test will fail.
  • If someone accidentally wipes out foo_handlers the test will fail.
  • If the values in foo_handlers are wrong, the test will fail.
  • If the logic of foo is broken, the test will fail.

Initially you're just going to copy foo_handlers into expected. After that it becomes a regression test testing that the code still works even after refactoring. Future changes will incrementally change foo_handlers and expected.


But wait, there's more! Code which is hard to test is probably hard to use. Conversely, code which is easy to test is easy to use. With a few more tweaks we can use this data-driven approach to make production code more flexible.

If we make foo_handlers an accessor with a default that comes from a method, not a constant, now we can change how foo behaves for individual objects. This may or may not be desirable for your particular implementation, but its in your toolbox.

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11class FooHelperTests < ActionView::TestCase
12  test "foo handles all enum cases" do
13    assert_equal foo(:a), 1
14    assert_equal foo(:b), 2
15    assert_equal foo(:c), 3
16    assert_raises NotImplementedError do
17        foo(:d)
18    end
19  end
20end
21test "foo handles all enum cases" do
22  remaining_cases = enum_cases.to_set
23
24  tester = -> (arg) do
25    remaining_cases.delete(arg)
26    foo(arg)
27  end
28
29  assert_equal tester.call(:a), 1
30  assert_equal tester.call(:b), 2
31  assert_equal tester.call(:c), 3
32  assert_raises NotImplementedError do
33    tester.call(:d)
34  end
35
36  assert_empty remaining_cases, "Not all cases were tested! Remaining: #{remaining_cases}"
37end
38class ExhaustivityChecker
39  def initialize(all_values, proc)
40    @remaining_values = all_values.to_set
41    @proc = proc
42  end
43
44  def run(arg, allow_invalid_args: false)
45    assert @remaining_values.include?(arg) unless allow_invalid_args 
46    @remaining_values.delete(arg)
47    @proc.call(arg)
48  end
49
50  def assert_all_values_checked
51    assert_empty @remaining_values, "Not all values were tested! Remaining: #{@remaining_values}"
52  end
53end
54test "foo handles all enum cases" do
55    tester = ExhaustivityChecker.new(enum_cases, -> (arg) { foo(arg) })
56
57    assert_equal tester.run(:a), 1
58    assert_equal tester.run(:b), 2
59    assert_equal tester.run(:c), 3
60    assert_raises NotImplementedError do
61        tester.run(:d, allow_invalid_args: true)
62    end
63
64    tester.assert_all_values_checked
65end
66def foo_handlers
67  {
68    a: 1,
69    b: 2,
70    c: 3
71  }.freeze
72end
73
74def foo(input)
75  foo_handlers.fetch(input)
76rescue KeyError
77  raise NotImplementedError, "Unhandled new case: #{input}"
78end
79class FooHelperTests < ActionView::TestCase
80  test "foo handles all expected inputs" do
81    expected = {
82      a: 1,
83      b: 2,
84      c: 3
85    }.freeze
86
87    # Verify expect has all the cases.
88    assert_equal expect.keys.sort, foo_handlers.keys.sort
89
90    # Drive the test with the expected results, not with the production data.
91    expected.keys do |key|
92      # Again, using `fetch` to get a clear KeyError rather than nil.
93      assert_equal foo(key), expected.fetch(value)
94    end
95  end
96
97  # Simplify the tests by separating happy path from error path.
98  test "foo raises NotImplementedError if the input is not handled" do
99    assert_raises NotImplementedError do
100      # Use something that obviously does not exist to future proof the test.
101      foo(:does_not_exist)
102    end
103  end
104end
105class Thing
106  attr_accessor :foo_handlers
107
108  # This can use a constant, as long as the method call is canonical.
109  def default_foo_handlers
110    {
111      a: 1,
112      b: 2,
113      c: 3
114    }.freeze
115  end
116
117  def initialize
118    @foo_handlers = default_foo_handlers
119  end
120
121  def foo(input)
122    foo_handlers.fetch(input)
123  rescue KeyError
124    raise NotImplementedError, "Unhandled new case: #{input}"
125  end
126end
127

Now individual objects can define their own handlers or change the values.

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11class FooHelperTests < ActionView::TestCase
12  test "foo handles all enum cases" do
13    assert_equal foo(:a), 1
14    assert_equal foo(:b), 2
15    assert_equal foo(:c), 3
16    assert_raises NotImplementedError do
17        foo(:d)
18    end
19  end
20end
21test "foo handles all enum cases" do
22  remaining_cases = enum_cases.to_set
23
24  tester = -> (arg) do
25    remaining_cases.delete(arg)
26    foo(arg)
27  end
28
29  assert_equal tester.call(:a), 1
30  assert_equal tester.call(:b), 2
31  assert_equal tester.call(:c), 3
32  assert_raises NotImplementedError do
33    tester.call(:d)
34  end
35
36  assert_empty remaining_cases, "Not all cases were tested! Remaining: #{remaining_cases}"
37end
38class ExhaustivityChecker
39  def initialize(all_values, proc)
40    @remaining_values = all_values.to_set
41    @proc = proc
42  end
43
44  def run(arg, allow_invalid_args: false)
45    assert @remaining_values.include?(arg) unless allow_invalid_args 
46    @remaining_values.delete(arg)
47    @proc.call(arg)
48  end
49
50  def assert_all_values_checked
51    assert_empty @remaining_values, "Not all values were tested! Remaining: #{@remaining_values}"
52  end
53end
54test "foo handles all enum cases" do
55    tester = ExhaustivityChecker.new(enum_cases, -> (arg) { foo(arg) })
56
57    assert_equal tester.run(:a), 1
58    assert_equal tester.run(:b), 2
59    assert_equal tester.run(:c), 3
60    assert_raises NotImplementedError do
61        tester.run(:d, allow_invalid_args: true)
62    end
63
64    tester.assert_all_values_checked
65end
66def foo_handlers
67  {
68    a: 1,
69    b: 2,
70    c: 3
71  }.freeze
72end
73
74def foo(input)
75  foo_handlers.fetch(input)
76rescue KeyError
77  raise NotImplementedError, "Unhandled new case: #{input}"
78end
79class FooHelperTests < ActionView::TestCase
80  test "foo handles all expected inputs" do
81    expected = {
82      a: 1,
83      b: 2,
84      c: 3
85    }.freeze
86
87    # Verify expect has all the cases.
88    assert_equal expect.keys.sort, foo_handlers.keys.sort
89
90    # Drive the test with the expected results, not with the production data.
91    expected.keys do |key|
92      # Again, using `fetch` to get a clear KeyError rather than nil.
93      assert_equal foo(key), expected.fetch(value)
94    end
95  end
96
97  # Simplify the tests by separating happy path from error path.
98  test "foo raises NotImplementedError if the input is not handled" do
99    assert_raises NotImplementedError do
100      # Use something that obviously does not exist to future proof the test.
101      foo(:does_not_exist)
102    end
103  end
104end
105class Thing
106  attr_accessor :foo_handlers
107
108  # This can use a constant, as long as the method call is canonical.
109  def default_foo_handlers
110    {
111      a: 1,
112      b: 2,
113      c: 3
114    }.freeze
115  end
116
117  def initialize
118    @foo_handlers = default_foo_handlers
119  end
120
121  def foo(input)
122    foo_handlers.fetch(input)
123  rescue KeyError
124    raise NotImplementedError, "Unhandled new case: #{input}"
125  end
126end
127thing = Thing.new
128puts thing.foo(:a) # 1
129puts thing.foo(:b) # 2
130
131thing.foo_handlers = { a: 23 }
132puts thing.foo(:a) # 23
133puts thing.foo(:b) # NotImplementedError
134

And, more importantly, a subclass can change their handlers. Here we add to the handlers using Hash#merge.

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11class FooHelperTests < ActionView::TestCase
12  test "foo handles all enum cases" do
13    assert_equal foo(:a), 1
14    assert_equal foo(:b), 2
15    assert_equal foo(:c), 3
16    assert_raises NotImplementedError do
17        foo(:d)
18    end
19  end
20end
21test "foo handles all enum cases" do
22  remaining_cases = enum_cases.to_set
23
24  tester = -> (arg) do
25    remaining_cases.delete(arg)
26    foo(arg)
27  end
28
29  assert_equal tester.call(:a), 1
30  assert_equal tester.call(:b), 2
31  assert_equal tester.call(:c), 3
32  assert_raises NotImplementedError do
33    tester.call(:d)
34  end
35
36  assert_empty remaining_cases, "Not all cases were tested! Remaining: #{remaining_cases}"
37end
38class ExhaustivityChecker
39  def initialize(all_values, proc)
40    @remaining_values = all_values.to_set
41    @proc = proc
42  end
43
44  def run(arg, allow_invalid_args: false)
45    assert @remaining_values.include?(arg) unless allow_invalid_args 
46    @remaining_values.delete(arg)
47    @proc.call(arg)
48  end
49
50  def assert_all_values_checked
51    assert_empty @remaining_values, "Not all values were tested! Remaining: #{@remaining_values}"
52  end
53end
54test "foo handles all enum cases" do
55    tester = ExhaustivityChecker.new(enum_cases, -> (arg) { foo(arg) })
56
57    assert_equal tester.run(:a), 1
58    assert_equal tester.run(:b), 2
59    assert_equal tester.run(:c), 3
60    assert_raises NotImplementedError do
61        tester.run(:d, allow_invalid_args: true)
62    end
63
64    tester.assert_all_values_checked
65end
66def foo_handlers
67  {
68    a: 1,
69    b: 2,
70    c: 3
71  }.freeze
72end
73
74def foo(input)
75  foo_handlers.fetch(input)
76rescue KeyError
77  raise NotImplementedError, "Unhandled new case: #{input}"
78end
79class FooHelperTests < ActionView::TestCase
80  test "foo handles all expected inputs" do
81    expected = {
82      a: 1,
83      b: 2,
84      c: 3
85    }.freeze
86
87    # Verify expect has all the cases.
88    assert_equal expect.keys.sort, foo_handlers.keys.sort
89
90    # Drive the test with the expected results, not with the production data.
91    expected.keys do |key|
92      # Again, using `fetch` to get a clear KeyError rather than nil.
93      assert_equal foo(key), expected.fetch(value)
94    end
95  end
96
97  # Simplify the tests by separating happy path from error path.
98  test "foo raises NotImplementedError if the input is not handled" do
99    assert_raises NotImplementedError do
100      # Use something that obviously does not exist to future proof the test.
101      foo(:does_not_exist)
102    end
103  end
104end
105class Thing
106  attr_accessor :foo_handlers
107
108  # This can use a constant, as long as the method call is canonical.
109  def default_foo_handlers
110    {
111      a: 1,
112      b: 2,
113      c: 3
114    }.freeze
115  end
116
117  def initialize
118    @foo_handlers = default_foo_handlers
119  end
120
121  def foo(input)
122    foo_handlers.fetch(input)
123  rescue KeyError
124    raise NotImplementedError, "Unhandled new case: #{input}"
125  end
126end
127thing = Thing.new
128puts thing.foo(:a) # 1
129puts thing.foo(:b) # 2
130
131thing.foo_handlers = { a: 23 }
132puts thing.foo(:a) # 23
133puts thing.foo(:b) # NotImplementedError
134class Thing::More < Thing
135  def default_foo_handlers
136    super.merge(
137      d: 4,
138      e: 5
139    )
140  end
141end
142
143thing = Thing.new
144more = Thing::More.new
145
146puts more.foo(:d)  # 4
147puts thing.foo(:d) # NotImplementedError
148

If a key requires more than a simple value, use method names and call them with Object#public_send. Those methods can then be unit tested.

1enum_cases = [:a, :b, :c]
2
3def foo(input)
4    case input
5    when :a then 1
6    when :b then 2
7    when :c then 3
8    else raise NotImplementedError, "Unhandled new case: #{input}"
9    end
10end
11class FooHelperTests < ActionView::TestCase
12  test "foo handles all enum cases" do
13    assert_equal foo(:a), 1
14    assert_equal foo(:b), 2
15    assert_equal foo(:c), 3
16    assert_raises NotImplementedError do
17        foo(:d)
18    end
19  end
20end
21test "foo handles all enum cases" do
22  remaining_cases = enum_cases.to_set
23
24  tester = -> (arg) do
25    remaining_cases.delete(arg)
26    foo(arg)
27  end
28
29  assert_equal tester.call(:a), 1
30  assert_equal tester.call(:b), 2
31  assert_equal tester.call(:c), 3
32  assert_raises NotImplementedError do
33    tester.call(:d)
34  end
35
36  assert_empty remaining_cases, "Not all cases were tested! Remaining: #{remaining_cases}"
37end
38class ExhaustivityChecker
39  def initialize(all_values, proc)
40    @remaining_values = all_values.to_set
41    @proc = proc
42  end
43
44  def run(arg, allow_invalid_args: false)
45    assert @remaining_values.include?(arg) unless allow_invalid_args 
46    @remaining_values.delete(arg)
47    @proc.call(arg)
48  end
49
50  def assert_all_values_checked
51    assert_empty @remaining_values, "Not all values were tested! Remaining: #{@remaining_values}"
52  end
53end
54test "foo handles all enum cases" do
55    tester = ExhaustivityChecker.new(enum_cases, -> (arg) { foo(arg) })
56
57    assert_equal tester.run(:a), 1
58    assert_equal tester.run(:b), 2
59    assert_equal tester.run(:c), 3
60    assert_raises NotImplementedError do
61        tester.run(:d, allow_invalid_args: true)
62    end
63
64    tester.assert_all_values_checked
65end
66def foo_handlers
67  {
68    a: 1,
69    b: 2,
70    c: 3
71  }.freeze
72end
73
74def foo(input)
75  foo_handlers.fetch(input)
76rescue KeyError
77  raise NotImplementedError, "Unhandled new case: #{input}"
78end
79class FooHelperTests < ActionView::TestCase
80  test "foo handles all expected inputs" do
81    expected = {
82      a: 1,
83      b: 2,
84      c: 3
85    }.freeze
86
87    # Verify expect has all the cases.
88    assert_equal expect.keys.sort, foo_handlers.keys.sort
89
90    # Drive the test with the expected results, not with the production data.
91    expected.keys do |key|
92      # Again, using `fetch` to get a clear KeyError rather than nil.
93      assert_equal foo(key), expected.fetch(value)
94    end
95  end
96
97  # Simplify the tests by separating happy path from error path.
98  test "foo raises NotImplementedError if the input is not handled" do
99    assert_raises NotImplementedError do
100      # Use something that obviously does not exist to future proof the test.
101      foo(:does_not_exist)
102    end
103  end
104end
105class Thing
106  attr_accessor :foo_handlers
107
108  # This can use a constant, as long as the method call is canonical.
109  def default_foo_handlers
110    {
111      a: 1,
112      b: 2,
113      c: 3
114    }.freeze
115  end
116
117  def initialize
118    @foo_handlers = default_foo_handlers
119  end
120
121  def foo(input)
122    foo_handlers.fetch(input)
123  rescue KeyError
124    raise NotImplementedError, "Unhandled new case: #{input}"
125  end
126end
127thing = Thing.new
128puts thing.foo(:a) # 1
129puts thing.foo(:b) # 2
130
131thing.foo_handlers = { a: 23 }
132puts thing.foo(:a) # 23
133puts thing.foo(:b) # NotImplementedError
134class Thing::More < Thing
135  def default_foo_handlers
136    super.merge(
137      d: 4,
138      e: 5
139    )
140  end
141end
142
143thing = Thing.new
144more = Thing::More.new
145
146puts more.foo(:d)  # 4
147puts thing.foo(:d) # NotImplementedError
148def foo_handlers
149  {
150    a: :handle_a,
151    b: :handle_b,
152    c: :handle_c
153  }.freeze
154end
155
156def foo(input)
157  public_send(foo_handlers.fetch(input), input)
158rescue KeyError
159  raise NotImplementedError, "Unhandled new case: #{input}"
160end
161
162def handle_a(input)
163  ...
164end
165
166def handle_b(input)
167  ...
168end
169
170def handle_c(input)
171  ...
172end
173

Source https://stackoverflow.com/questions/59761298

Community Discussions contain sources that include Stack Exchange Network

Tutorials and Learning Resources in Code Coverage Tools

Tutorials and Learning Resources are not available at this moment for Code Coverage Tools

Share this Page

share link

Get latest updates on Code Coverage Tools