Skip to content

Commit

Permalink
📦 NEW: Version 0.2.0 release
Browse files Browse the repository at this point in the history
* Added style_output and debug_mode configuration options
* Improved documentation
* Fixed a bug with the phrase 'spec'

See Changelog.md for more details
  • Loading branch information
joshmfrankel committed Mar 6, 2019
1 parent 4a66316 commit 4b8a3ee
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 36 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
0.2.0 (2019-03-06)
==================
## Features
* Added style_output configuration option. There are two options: [:simple, :verbose].
:simple is the default, while :verbose outputs line numbers along with source code that
is missing coverage.
* Added debug_mode configuration option for investigating mismatched filenames. Default is disabled

## Improvements
* Improved output of the current tested file to start with the app/ directory instead
of full path
* Slight optimizations done to the way the current filename is detected
* Better documentation added

## Bug fixes
* Files previously containing the phrase 'spec' as part of their name would be
improperly formatted. This caused these files to come up as mismatches.

0.1.0 (2019-02-20)
==================
## Features
Expand Down
43 changes: 42 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Simplecov::Tdd

A SimpleCov formatter for test driven development by displaying code coverage directly in the console for single files
A SimpleCov formatter for test driven development. Displays code coverage results in the console for single files

![Example TDD](https://github.com/joshmfrankel/simplecov-tdd/blob/master/example.gif)

Expand Down Expand Up @@ -28,6 +28,11 @@ Or install it yourself as:
```ruby
require "simplecov/tdd"
SimpleCov.formatter = Simplecov::Formatter::Tdd
# OR use multi-formatter
SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([
SimpleCov::Formatter::HTMLFormatter,
Simplecov::Formatter::Tdd
])
```

Simple Setup:
Expand All @@ -44,6 +49,42 @@ SimpleCov.start 'rails'
4. Fix the missing coverage
5. 💰 Profit! 💰

## Configuration options

There are a few configuration options that may be set before formatting is called.
Generally you may place these after `SimpleCov.formatter` or `SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new` in your setup.

### output_style (default: :simple)
When there are lines missing coverage, this option determines how to display output.
The accepted values are `:simple` (default) or `:verbose`.

```ruby
SimpleCov::Formatter::Tdd.output_style = :verbose
```

Here's an example of what :verbose output looks like:

```ruby
app/models/matched_90.rb
90.0% coverage, 167 total lines

The following 2 lines have missing coverage:
[5, 25]

line | source code
-------------------
5 => obj.is_a?(SomeClass)
25 => SomeClass.explode!
```

### debug_mode (default: false)
This is useful for determining if the current file being tested doesn't have
a match from SimpleCov's file list.

```ruby
SimpleCov::Formatter::Tdd.debug_mode = true
```

## Future Features

* Support for minitest
Expand Down
97 changes: 76 additions & 21 deletions lib/simplecov/tdd.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,91 @@

module SimpleCov
module Formatter

# A SimpleCov formatter for generating single file results while using
# test driven development principles
class Tdd
attr_reader :result

def format(coverage_results, debug: false)
# Hook into SimpleCov format by defining specific implementation
# of #format for the TDD formatter
def format(coverage_results)
@result = single_result(coverage_results)

# Is there a matching result?
if result.nil?
raise StandardError, "Could not find #{current_filename} as part of the SimpleCov::FileList results" if debug
return
raise StandardError, "Could not find #{current_filename} as part of the SimpleCov::FileList results" if debug_mode
return # Early return silently dies outside of debug mode
end

puts current_filename
coverage_overview_text
missed_line_numbers_message
puts format_filename_for_output
puts coverage_overview_text
puts missed_line_numbers_message
end

# Class level attr_writer
# @param style [Symbol] the symbol representation of the output_style
# accepted values are: [:simple, :verbose]
def self.output_style=(style)
@output_style = style
end

# Class level attr_reader
def self.output_style
@output_style
end

# Instance level method that defers to class_level variable when
# set via configuration. The default output_style is :simple
def output_style
self.class.output_style || :simple
end

# Class level attr_writer
# @param flag [Boolean] the current status (true/false) of debug mode
def self.debug_mode=(flag)
@debug_mode = flag
end

# Class level attr_reader
def self.debug_mode
@debug_mode
end

# Instance level method that defers to class_level variable when
# set via configuration. The default debug_mode is false
def debug_mode
self.class.debug_mode || false
end

private

# From SimpleCov::FileList match the current_filename to a corresponding
# coverage result
# Match the current file against the SimpleCov::FileList results to determine
# a matching test
# @param coverage_results [SimpleCov::Result] the incoming results from running
# SimpleCov after tests
def single_result(coverage_results)
coverage_results.files.select { |file| file.filename == current_filename }[0]
coverage_results.files.detect { |file| file.filename == current_filename }
end

# Infer the current_filename based on incoming ARGV command line
# arguments and a matching spec type file
def current_filename
@_current_filename ||= begin
# @todo Can we hook into Guard watched file? That would be more accurate
# @TODO support for minitest

argument_index = nil
ARGV.each.with_index do |arg, index|
if arg.match(/_spec.rb/)
argument_index = index
break
end
end
argument_index = ARGV.find_index { |arg| arg.match(/_spec.rb/) }

if argument_index
Dir.pwd + "/" + ARGV[argument_index].sub("spec", "app").sub("_spec", "")
Dir.pwd + "/" + ARGV[argument_index].sub("spec/", "app/").sub("_spec.rb", ".rb")
end
end
end

# Format the currently matched filename to be more human readable
def format_filename_for_output
"app/" + current_filename.split("/app/")[1]
end

# @todo tty support for colors
def coverage_overview_text
covered_percent = result.covered_percent.round(2)
Expand All @@ -67,13 +107,28 @@ def coverage_overview_text
end
end

puts colorized_overview_text
colorized_overview_text
end

# Display the missing line numbers in a summary message
def missed_line_numbers_message
return if result.covered_percent == 100

puts "\nThe following #{missed_line_numbers.size} lines have missing coverage: \n\e[31m#{missed_line_numbers}\e[0m"
"\nThe following #{missed_line_numbers.size} lines have missing coverage: \n\e[31m#{missed_line_numbers}\e[0m".tap do |output|
output << missed_line_numbers_verbose_message
end
end

# When output_style == :verbose, display a table that includes the
# line number missing converage as well as the related code
def missed_line_numbers_verbose_message
return "" if output_style != :verbose

"\nline | source code\n-------------------\n".tap do |output|
result.missed_lines.each do |missed_line|
output << missed_line.line_number.to_s + " => " + missed_line.src.strip + "\n"
end
end
end

def missed_line_numbers
Expand Down
2 changes: 1 addition & 1 deletion lib/simplecov/tdd/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module SimpleCov
module Tdd
VERSION = "0.1.0"
VERSION = "0.2.0"
end
end
68 changes: 55 additions & 13 deletions spec/simplecov/tdd_spec.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
RSpec.describe SimpleCov::Tdd do

let(:formatter) { SimpleCov::Formatter::Tdd.new }
let(:matched_90_filename) { Dir.pwd + "/app/models/matched_90.rb" }
let(:matched_90_filename) { "app/models/matched_90.rb" }
let(:matched_90_filepath) { Dir.pwd + "/" + matched_90_filename }
let(:matched_90_spec) { "spec/models/matched_90_spec.rb" }
let(:matched_file_90_coverage) do
instance_double(
SimpleCov::SourceFile,
filename: matched_90_filename,
filename: matched_90_filepath,
covered_percent: 90.0,
lines_of_code: 167,
missed_lines: [
instance_double(SimpleCov::SourceFile::Line, line_number: 5),
instance_double(SimpleCov::SourceFile::Line, line_number: 25)
instance_double(SimpleCov::SourceFile::Line, line_number: 5, src: " obj.is_a?(SomeClass)"),
instance_double(SimpleCov::SourceFile::Line, line_number: 25, src: " SomeClass.explode!")
]
)
end
Expand All @@ -27,10 +28,22 @@
)
end
let(:unmatched_file) { instance_double(SimpleCov::SourceFile, filename: "spec/models/narp_spec.rb") }
let(:matched_file_with_intersecting_filename) { "app/models/anotherspec_with_spec.rb" }
let(:matched_file_with_intersecting_filepath) { Dir.pwd + "/" + matched_file_with_intersecting_filename }
let(:matched_file_with_intersecting_spec) { "spec/models/anotherspec_with_spec_spec.rb" }
let(:matched_file_with_intersecting_name_source_file) do
instance_double(
SimpleCov::SourceFile,
filename: matched_file_with_intersecting_filepath,
covered_percent: 100.0,
lines_of_code: 53,
missed_lines: []
)
end
let(:simple_cov_result_stub) do
instance_double(
SimpleCov::Result,
files: [matched_file_90_coverage, unmatched_file, matched_file_100_coverage]
files: [matched_file_90_coverage, unmatched_file, matched_file_100_coverage, matched_file_with_intersecting_name_source_file]
)
end

Expand All @@ -53,17 +66,18 @@

it "raises an Exception" do
stub_const("ARGV", ["misc", "nope", "2", "spec/unfound_spec.rb"])
SimpleCov::Formatter::Tdd.debug_mode = true

expect {
formatter.format(simple_cov_result_stub, debug: true)
formatter.format(simple_cov_result_stub)
}.to raise_error(StandardError)
end
end
end

context "with a matched filename" do

context "and without full 100% coverage" do
context "without full 100% coverage" do
it "displays the coverage overview" do
stub_const("ARGV", ["misc", "nope", "2", matched_90_spec])

Expand All @@ -88,9 +102,23 @@
formatter.format(simple_cov_result_stub)
}.to output(/#{line_number_array}/).to_stdout
end

context "and output_style is :verbose" do

it "displays the line numbers along with matching code" do
stub_const("ARGV", ["misc", "nope", "2", matched_90_spec])
SimpleCov::Formatter::Tdd.output_style = :verbose

expect {
formatter.format(simple_cov_result_stub)
}.to output(
/line \| source code\n-------------------\n#{matched_file_90_coverage.missed_lines.first.line_number} => #{Regexp.quote(matched_file_90_coverage.missed_lines.first.src.strip)}\n#{matched_file_90_coverage.missed_lines[1].line_number} => #{Regexp.quote(matched_file_90_coverage.missed_lines[1].src.strip)}\n/
).to_stdout
end
end
end

context "and with full 100% coverage" do
context "with full 100% coverage" do
it "displays the coverage overview" do
stub_const("ARGV", ["misc", "nope", "2", matched_100_spec])

Expand All @@ -109,6 +137,14 @@
end

context "with different command line argument orders" do
it "displays the matched filename when first argument" do
stub_const("ARGV", [matched_90_spec, "misc", "nope", "2"])

expect {
formatter.format(simple_cov_result_stub)
}.to output(/#{matched_90_filename}/).to_stdout
end

it "displays the matched filename when middle argument" do
stub_const("ARGV", ["misc", matched_90_spec, "nope", "2"])

Expand All @@ -125,11 +161,17 @@
}.to output(/#{matched_90_filename}/).to_stdout
end
end

context "when filename contains the phrase 'spec'" do

it "displays the matched filename" do
stub_const("ARGV", [matched_file_with_intersecting_spec, "misc", "nope", "2"])

expect {
formatter.format(simple_cov_result_stub)
}.to output(/#{matched_file_with_intersecting_filename}/).to_stdout
end
end
end
end

# @todo green coverage
# @todo brown coverage
# @todo red coverage
# @todo tty color coverage
end

0 comments on commit 4b8a3ee

Please sign in to comment.