How to test directory reading and addressing method in ruby? - ruby

I have a ruby class to read directory and do something for each file. But I am not sure how to test it.
class Scanner
def scan(dir)
Dir.glob(File.join(dir, '**', '*')).each do |path|
verify(path)
end
end
private
def verify(path)
do something for the file...
end
end
I want to have a unit-testing for scan method.
Should I mock something?
Should I provide a test directory in spec/fixtures?
What's the expectation?
Cheers

There are few ways to approach that. One (proposed in the comments) is to separate the concerns, e.g.
class Scanner
def scan(dir)
Dir.glob(File.join(dir, '**', '*')).each do |path|
verify(path)
end
end
private
def verify(path)
Veryfier.new.verify(File.read(path))
# you can test veryfier easily by passing different strings into `verify` method
end
end
And go on with your life, as Scanner#scan is not that complicated anymore, and one would not expect many bugs in there.
But, if for whatever reason, you want it tested as well, you can do one of the following:
set up a dir structure under test/features and tread different directories like different cases. This has one drawback: you have to inspect directories plus read the tests to understand the tests. But it's simple to do.
use Dir.tmpdir and File.tmpfile to set up the dir structure inside your tests. This is a bit more involved, but on the plus side you have the structure and the tests for behavior of the Scanner in a single test file.

Related

Best way to generate dynamic tests that don't leak using RSpec (LeakyConstantDeclaration issue)?

Am taking over some Ruby code which includes a fairly large test suite. One thing that's been missing that I'm adding is rubocop to fix some problems. One thing I've noticed is that numerous tests have been set up to be dynamically generated in a way like so:
describe 'some test' do
SOME_CONSTANT = { some hash }
SOME_CONSTANT.each do |param1, param2|
it 'does #{param1} and successfully checks #{param2} do
# do something with #{param1} or #{param2}
expect(param2).to eq "blahblah"
end
end
end
The issue here is SOME_CONSTANT. With rubocop this fails the RSpec/LeakyConstantDeclaration cop rule. The way these tests are set up these constants can re-assign a global constant by accident and result in random spec failures elsewhere if folks aren't paying attention.
The only workable solution to I've found is to change these constants into instance variables. For example:
describe 'some test' do
#some_constant = { some hash }
#some_constant.each do |param1, param2|
it 'does #{param1} and successfully checks #{param2} do
# do something with #{param1} or #{param2}
expect(param2).to eq "blahblah"
end
end
end
There is a danger that these instance vars can leak into other it/example specs too (within the same spec file if a single test changes it), but at least it's limited to the individual *_spec.rb files, and won't impact global scope of the entire test suite. This also fixes the RSpec/LeakyConstantDeclaration.
Would anyone have any better suggestions? One that does not use instance variables, and is more modern RSpec friendly? I've tried using let, and let! but the way the tests are setup any variables set this way are only accessible within the it blocks. Have also tried using stub_const in a before(:context) block, but run into the same issue where the stubbed constant is only accessible within the it/example context. I also even tried RSpec.Mocks.with_temporary_scope and same issue. Instance variables seem to be the only thing that works in this set up.
Thanks in advance for any helpful suggestions!
The example you provided feels a little too much like programming, rather than a test; e.g. I'd try to be a little bit more explicit rather than adding a loop of the behaviors.
Two constructs come to mind:
Using the let (which I know you said you did), but with additional nesting. IIRC, you might be able to add another describe block outside of your each block
Shared examples. This is what I'd try first. I added some pseudocode below
describe 'my test' do
shared_examples 'does the thing' do |arg1, arg2|
before { arg1.call }
it "does #{arg1} and returns #{arg2}" do
expect(arg2).to eq(true)
end
end
it_behaves_like 'does the thing', Foo.new, bar
it_behaves_like 'does the thing', Blarge.new, blah
end
You can also combine these with lets and/or a block (and refactor the shared example to reference these rather than passing in the arguments explicitly
it_behaves_like 'does the thing' do
let(:method) { :foo }
let(:result) { 42 }
end
Agree w/ Jay, I try to steer away from dynamic tests and prefer to make them simpler and explicit (if more repetitive). However that may be more of a refactor than you're looking to take on. To only address the issue of variable names I would consider not assigning the data to a variable at all e.g:
[:each, :attribute, :to, :test].each do |attr|
it "does a thing with #{attr}"...
end
and if it's a larger array/hash:
{
some
larger
hash
}.each do |param1, param2|
it 'does #{param1} and successfully checks #{param2}' do
# do something with #{param1} or #{param2}
expect(param2).to eq "blahblah"
end
end

What are all the ways to check if a file exists in ruby without shelling out?

What are all the ways to check if a file exists using Ruby's core classes/modules without shelling out?
Would also appreciate reasons why choosing one method over another makes sense. For example: Using Dir['**/*'].grep(/foo/) is the shortest way I've found to match paths using a regex.
However, I think Pathname.new('.').find.any? { |pn| pn.fnmatch? "*foo*" } is a good option because Pathname is a cross-platform solution that usually seems to "just work".
Are there any solutions/classes/modules I've missed? Also, would appreciate answers that involve speed/efficiency analysis.
require 'minitest/autorun'
require 'pathname'
class TestTouch < Minitest::Test
include FileUtils
attr_reader :foo
def setup
#foo = Pathname.new('foo')
foo.delete if foo.exist?
end
def teardown
foo.delete if foo.exist?
end
def test_touch
touch foo
cwd = Pathname.new('.')
assert cwd.find.to_a.map(&:to_s).grep(/foo/).any?
assert cwd.find.any? { |pn| pn.fnmatch? "*foo*" }
assert cwd.join('foo').exist?
assert Dir['**/*'].grep(/foo/)
assert Dir.glob('**/*').grep(/foo/)
assert !Dir.glob('foo').empty?
assert File.exist?('foo')
end
end
Try this
File.exist?(fname)
File.file?(fname)
Sometimes though, if you need to check existence and open the file in an atomic operation is can be best to just open the file and handle the case of a missing file by rescuing the exception.
Why would it be a good idea? This mostly applies to infrastructure code on the backend when you deal with databases and caching layers. Sometimes it can be critical that your code is not affected if the file is deleted or replaced between taking the branch and consuming the content—when a file is deleted the handle remains open and can still be used!
begin
File.open(fname) { ... }
rescue Errno::ENOENT => e
...
end
ENOENT is the C library error code for "file not found" for a complete list of all error codes see here. Most of Ruby's file handling is basically just a thin wrapper around the underlying C libraries. As you might have already noticed from browsing the File class.
Off the top of my head...
Pathname.exist?(NAME)
FileTest.exist?(NAME)
Pathname.file?(NAME)
FileTest.file?(NAME)
How about
File.exists?(NAME)
? Note that this also returns true if NAME is, for instance, a directory.

Conditionally defining functions in Ruby

I have some code that is run in one of a few different locations: as a command line tool with debug output, as part of a larger program that doesn't take any output, and in a rails environment.
There are occasions where I need to make slight changes to the code based on its location, and I realized the following style seems to work:
print "Testing nested functions defined\n"
CLI = true
if CLI
def test_print
print "Command Line Version\n"
end
else
def test_print
print "Release Version\n"
end
end
test_print()
This results in:
Testing nested functions defined
Command Line Version
I've never come across functions that are defined conditionally in Ruby. Is this safe to do?
This isn't how I'm structuring most of my code, but there are a few functions that require complete rewrites per-system.
I don't think that is a clean way.
My suggestion is to define the same sets of methods (with different definition bodies) in different modules, and conditionally include the relevant module into the class/module you are going to call the methods from.
module CLI
def test_print
... # definition for CLI
end
end
module SomeOtherMode
def test_print
... # definition for some other mode
end
end
class Foo
include some_condition ? CLI : SomeOtherMode
end
Foo.new.test_print
If you are only going to use only one mode per run, and think that it is a waste to define the modules that end up not being used, then you can take a further step; define respective modules (CLI, SomeOtherMode, ...) in separate files, and use autoload.
autoload :CLI, "path/to/CLI"
autoload :SomeOtherMode, "path/to/SomeOtherMode"
It's a form of meta-programming and is generally safe. The real risk is not if it will work as expected, but in testing all the variations you create.
The example you've given here makes it impossible to execute the alternate version. To properly exercise both methods you need a way to force the injection of one or the other.

Generating many almost identical ruby unit tests

I have a number of ruby files (a.rb, b.rb, c.rb) which define very similar classes. (They should test the same)
I've written a unit test to test these subclasses and I've generated contexts for each of the classes programatically (see below) — should I instead programatically create entire Test Classes instead? If so why and how?
I'm using the shoulda unit test extensions so my files look something like this:
a.rb
class ItsA
def number
1123
end
end
b.rb
class ItsB
def number
6784
end
end
test_letters.rb
require 'rubygems'
require 'test/unit'
require 'shoulda'
class LettersTest < Test::Unit::TestCase
Dir.glob('letters/*.rb') do |letter|
context "The #{letter} letter file"
setup do
# Here I require the ruby file and allocate
# #theclass to be an instance of the class in the file.
# I'm actually testing JavaScript using Harmony, but
# putting those details in might complicate my question.
end
should "return a number" do
assert #theclass.number.is_a? Number
end
end
end
This does the job reasonably well, but should I do some other jiggerypokery and create LetterATest, LetterBTest etc. automatically instead? If so, how would you go about doing it and why?
This really depends on how similar your classes are, but assuming they're pretty much identical and require a few small tests, and you're using shoulda, you could do something like:
class LettersTest < Test::Unit::TestCase
context "The letter classes"
setup do
#instances = # your code to get a list of the instances
end
should "return a number" do
#instances.each do |instance|
assert instance.number.is_a?(Number), "#{instance.class}.number should be a number"
end
end
end
end
In our codebase we've found that a large number of auto-generated contexts and tests leads to pretty slow test execution, so we favor the approach above to minimize the number of contexts/tests. You may not have this issue.

is it OK to use begin/end in Ruby the way I would use #region in C#?

I've recently moved from C# to Ruby, and I find myself missing the ability to make collapsible, labelled regions of code. It just occurred to me that it ought to be OK to do this sort of thing:
class Example
begin # a group of methods
def method1
..
end
def method2
..
end
end
def method3
..
end
end
...but is it actually OK to do that? Do method1 and method2 end up being the same kind of thing as method3? Or is there some Ruby idiom for doing this that I haven't seen yet?
As others have said this doesn't change the method definition.
However, if you want to label groups of methods, why not use Ruby semantics to label them? You could use modules to split your code into chunks that belong together. This is good practice for large classes, even if you don't reuse the modules. When the code grows, it will be easy to split the modules into separate files for better organisation.
class Example
module ExampleGroup
def method1
# ..
end
def method2
# ..
end
end
include ExampleGroup
def method3
# ..
end
end
Adding arbitrary Ruby code in order to please your editor doesn't seem reasonable to me at all. I think you should try to get a decent text editor, one that would be able to configure to use some keyword you put in your comments to enable code folding there, for example:
# region start AAA
some(:code, :here)
# region end AAA
And if you allow me to be sarcastic: you should know that Ruby is lot more expressive, elegant and pretty than C#, and chances are that there isn't any Ruby code ought to be hidden in the first place! ;)
I know this might be a bit old but I ran into this issue and found a killer solution. If you're using "Rubymine" you can do #regions.
This is where they talk about navigating regions
https://www.jetbrains.com/ruby/help/navigating-to-custom-region.html
#region Description
Your code goes here...
#endregion
So with the above in your code you will see the below:
Rubymine code editor - With a sample method using regions un-folded
Rubymine code editor - With a sample method using regions folded
I think it should be OK. I haven't seen it a lot though. Another way you could accomplish the same thing, while using less indentation, is to close and reopen the class, like so:
class Example
def method1
..
end
def method2
..
end
end
# optionally put some other code here
class Example
def method3
..
end
end
I think this is often used so that you can put other code in the middle, such as when there's a constant defined in the first class Example block, which is used to define a constant in the middle code, which is used to define a constant in the second class Example block. That way you never have to refer to a constant before it's defined.
Yes, it is okay to do that (i.e. it doesn't change the semantics of your code).
However this isn't a common pattern and it might be confusing to readers of your code who wouldn't understand what the purpose of the begin ... end block is.

Resources