Ruby Load multiple scripts from directory with foreach loop - ruby

I'm using a loop to load and execute Ruby scripts in a directory. At the moment the script will load the script, but how do I execute it when the only reference to it is the filename in the form of a string?
Dir.foreach('tests') do |item|
next if item == '.' or item == '..' #removes extra "." or ".."
load dirname + '/' + item #successfully loads the script
if item # the scripts return true/false
numberPassed+=1
else
numberFailed+=1
failed.push(item)
end
numberTested+=1
end
For some reason I'm getting 2 Passed, but it never actually runs the scripts "item" represents.
EDIT: here is an example of a script that would need to be loaded. They all follow this format:
require "watir-webdriver"
class TestScript
puts 'Testing etc etc"...'
browser = Watir::Browser.new :ie
browser.goto "webpage.htm"
browser.text_field(:name => "j_username").set "username"
browser.text_field(:name => "j_password").set "password"
browser.link(:id, "watSubmitLogin").click
browser.wait
browser.link(:id=> 'watCommDir').fire_event("onmouseover")
browser.link(:id=> 'watAddFi').click
browser.wait
...
browser.link(:href, "javascript: submitForm();").click
browser.wait
if browser.text.include?( 'The user already Exists')
puts 'Passed'
browser.close
return true
else
puts 'Failed'
browser.close
return false
end
end
I need to somehow tell the main script whether the sub-scripts pass or fail so I can keep track of how many pass/fail/error/total and create a report of all the tests that failed.

Looks like you are doing acceptance testing with Watir and try to do custom test results reporting.
I would recommend to use existing test runners to run all your tests and build custom output formatter for your needs. Existing test runners already solve a lot of issues you will encounter during creation of your own test runner (like how to run tests from specified folder, how to identify failing/successful test etc).
One of the commmon test runners for acceptance tests in Ruby community is Cucumber. Another good alternative is RSpec. Both these libraries support custom formatters:
In RSpec you would need to subclass RSpec::Core::Formatters::BaseFormatter.
In Cucumber you would need to implement class with methods specified in this documentation.
If you want to stay with the current simple implementation, here is one possible approach that is inpired by ruby Regexps: Inside the test set global variable, e.g. $test_succeeded (like $~, $& etc. global variables generated by ruby regular expressions) and then examine this value in your test runner.
In tests
if browser.text.include?( 'The user already Exists')
puts 'Passed'
browser.close
$test_succeeded = true
# ...
In tests runner
Dir.foreach('tests') do |item|
next if item == '.' or item == '..' #removes extra "." or ".."
load dirname + '/' + item #successfully loads the script
if $test_succeeded
# ...
If you have problems running the script then I can recommend to define special method to run tests (similar to RSpec approach):
def test
test_res = yield # call test
$test_results ||= {}
$test_results << test_res # and store its result in arra of test results
end
Then your tests will look like:
require 'file_with_test_method'
require 'watir-webdriver'
test do
# your test code
browser.text.include?( 'The user already Exists') # last expression in the block will be the test result
end

Related

How to test OptionParser with Rspec - RSpec options are stored in ARGV array during testing

I am learning ruby and trying to write a unit test with rspec for the following method:
def get()
options = {}
OptionParser.new do |opt|
opt.banner = 'Usage: validate-gitlab-ci [options]'
opt.on('-f', '--yaml YAML-PATH', 'Path to .gitlab-ci.yml') { |o| options[:yamlFile] = o }
opt.on('-l', '--base-url GitLab url', 'GitLab API url') { |o| options[:baseUrl] = o + API_PATH }
opt.on('-t', '--timeout[TIMEOUT]', Integer, 'Api timeout in seconds') { |o| options[:timeout] = o || 10 }
opt.on('-v', '--version', 'Program version') { |o| options[:version] = o }
end.parse!
validateUrl!(options[:baseUrl])
validateYamlFile!(options[:yamlFile])
#baseUrl = options[:baseUrl]
#pathToYamlFile = options[:yamlFile]
end
The code for my unit test so far is:
RSpec.describe Gitlab::Lint::Client::Args do
describe "#get" do
context "when arguments are valid" do
it "sets baseUrl and pathToYamlFile" do
io = StringIO.new
io.puts "glab-lint --base-url=https://example.com --yaml=valid.ym\n"
io.rewind
$stdin = io
args = Gitlab::Lint::Client::Args.new
args.get()
expect(args.baseUrl).to.eq("https://example.com")
end
end
end
end
I am trying to mock STDIN for OptionParser. However, upon executing the test the following error is displayed:
OptionParser::InvalidOption:
invalid option: --pattern
This is raised by the end.parse! line in the get() method
Has anyone managed to test OptionsParser with stdin mocked?
Update
I think what is happening is that some RSpec options, e.g. --pattern?? are being captured in STDIN and passed to script??? Or .... RSpec is consuming the stdin options??
Reading this post seems to suggest that the desired functionality is not possible with RSpec....if this is indeed true then I will migrate over to using alternative test frameworks in future for CLI projects that use ARGV. There is a workaround suggested here but that suggests using environment variables for capturing commmand line arguments. In this case that would require further refactoring of the software under test, purely to suit the capabilities of the RSpec test framework!!
If I add a puts statement to display the contents of ARGV in the test script it confirms this is the case, with this output:
--pattern
spec/**{,/*/**}/*_spec.rb
[--base-url=https://gitlab.com --yaml=valid.ym]
So.....as a complete newbie to RSpec.....my options are:
Update the signature of the get method to accept an args array:
def get(args)
options = {}
OptionParser.new do |opt|
...
end.parse!(args)
end
This delays the issue with testing the code that reads from ARGV further up the call hierarchy
Modify ARGV shifting the first two arguments out of the array and then after the test has completed restore ARGV to original state. Looks like something similar has already been tried here without success.
Some other configuration that I am not aware of as a newbie to RSpec
Investigate alternative options, e.g. minitest, that maybe do not modify the ARGV array??
Further information regarding options 3 and 4 appreciated....
You can use RSpec to mock STDIN. For example:
STDIN.should_receive(:read).and_return("glab-lint --base-url=https://example.com --yaml=valid.yml")
Alternatively, you can invoke your actual command line program using backticks or system, and assert on the response.

RSpec mocking, `name` not available from within an example group

I have the following Ruby code:
def report_deviation(departure)
deviation = departure.fetch('Dev')
trip = departure.fetch('Trip')
run_id = trip.fetch('RunId')
headsign = trip.fetch('InternetServiceDesc')
timestamp = Time.now.strftime '%l:%M %P'
FileUtils.mkdir 'log' unless File.directory? 'log'
File.open DAILY_LOG_FILE, 'a' do |file|
file.puts "#{timestamp}, #{name}: Run #{run_id} (#{headsign}), deviation #{deviation}"
end
end
Tested by the following RSpec code:
describe 'report_deviation' do
let(:departure) { double }
let(:trip) { double }
let(:file) { double }
it 'appends to a log file with the correct entry format' do
expect(departure).to receive(:fetch).with('Trip').and_return trip
expect(departure).to receive(:fetch).with('Dev').and_return 'DEVIATION'
expect(trip).to receive(:fetch).with('RunId')
.and_return 'RUN'
expect(trip).to receive(:fetch).with('InternetServiceDesc')
.and_return 'HEADSIGN'
stub_const 'DeviationValidator::DAILY_LOG_FILE', :log_file
expect(File).to receive(:open).with(:log_file, 'a').and_yield file
timestamp = '12:00 pm: Run RUN (HEADSIGN), deviation DEVIATION'
expect(file).to receive(:puts).with timestamp
Timecop.freeze(Time.new 2017, 7, 31, 12) { report_deviation(departure) }
end
end
But when I run I receive the failure message:
`name` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).
The word name isn't written anywhere in here, and if I remove the final line of the test (which invokes the actual code) I get the test failures I would expect for unsatisfied exceptions. I normally would boil my code down to the pieces that are causing the error, but I have no idea what's causing the error.
For what it's worth, the specific line number mentioned in the backtrace is the file.puts within the File.open block - but I don't understand why that should cause a failure. I've set up test doubles such that those objects are nothing special - File receives open and yields file, whose only job is to listen for receiving puts with the string I expect. So what piece of code is calling what happens to be a keyword RSpec method name?
The problem is from rspec gem, if you are using Rails 6 you need to use gem 'rspec-rails', '~> 4.1.0'
name is not a keyword RSpec method, it's a method that report_deviation is trying to call
file.puts "#{timestamp}, #{name}: Run #{run_id} (#{headsign}), deviation #{deviation}"
but the method is not defined.
You need to define the name method in the class where report_deviation is defined. Or, if report_deviation is defined and used in the spec file, add a simple variable called name:
describe 'report_deviation' do
let(:departure) { double }
let(:trip) { double }
let(:file) { double }
let(:name) { "simple name" }
...
`name` is not available from within an example (e.g. an `it` block) [...]
I had a similar problem today. The final solution for the issue for now with a monkeypatch to go back to using method_name.
Create config/initializers/monkeypatches.rb file and fill inside with the following lines.
# config/initializers/monkeypatches.rb
#
# This fixes what seems to be a bug introduced by
# https://github.com/rails/rails/pull/37770
# "Modify ActiveRecord::TestFixtures to not rely on AS::TestCase:"
#
module ActiveRecord::TestFixtures
def run_in_transaction?
use_transactional_tests &&
!self.class.uses_transaction?(method_name) # this monkeypatch changes `name` to `method_name`
end
end
Credits: https://github.com/graphql-devise/graphql_devise/issues/42

Writing a Rspec test for an error condition finished with exit

I am writing a command line interface to a Ruby gem and I have this method exit_error, which acts as an exit error point to all validations performed while processing.
def self.exit_error(code,possibilities=[])
puts #errormsgs[code].colorize(:light_red)
if not possibilities.empty? then
puts "It should be:"
possibilities.each{ |p| puts " #{p}".colorize(:light_green) }
end
exit code
end
where #errormsgs is a hash whose keys are the error codes and whose values are the corresponding error messages.
This way I may give users customized error messages writing validations like:
exit_error(101,#commands) if not valid_command? command
where:
#errormsgs[101] => "Invalid command."
#commands = [ :create, :remove, :list ]
and the user typing a wrong command would receive an error message like:
Invalid command.
It should be:
create
remove
list
At the same time, this way I may have bash scripts detecting exactly the error code who caused the exit condition, and this is very important to my gem.
Everything is working fine with this method and this strategy as a whole. But I must confess that I wrote all this without writing tests first. I know, I know... Shame on me!
Now that I am done with the gem, I want to improve my code coverage rate. Everything else was done by the book, writing tests first and code after tests. So, it would be great having tests for these error conditions too.
It happens that I really don't know how to write Rspec tests to this particular situation, when I use exit to interrupt processing. Any suggestions?
Update => This gem is part of a "programming environment" full of bash scripts. Some of these scripts need to know exactly the error condition which interrupted the execution of a command to act accordingly.
For example:
class MyClass
def self.exit_error(code,possibilities=[])
puts #errormsgs[code].colorize(:light_red)
if not possibilities.empty? then
puts "It should be:"
possibilities.each{ |p| puts " #{p}".colorize(:light_green) }
end
exit code
end
end
You could write its rspec to be something like this:
describe 'exit_error' do
let(:errormsgs) { {101: "Invalid command."} }
let(:commands) { [ :create, :remove, :list ] }
context 'exit with success'
before(:each) do
MyClass.errormsgs = errormsgs # example/assuming that you can #errormsgs of the object/class
allow(MyClass).to receive(:exit).with(:some_code).and_return(true)
end
it 'should print commands of failures'
expect(MyClass).to receive(:puts).with(errormsgs[101])
expect(MyClass).to receive(:puts).with("It should be:")
expect(MyClass).to receive(:puts).with(" create")
expect(MyClass).to receive(:puts).with(" remove")
expect(MyClass).to receive(:puts).with(" list")
MyClass.exit_error(101, commands)
end
end
context 'exit with failure'
before(:each) do
MyClass.errormsgs = {} # example/assuming that you can #errormsgs of the object/class
allow(MyClass).to receive(:exit).with(:some_code).and_return(false)
end
# follow the same approach as above for a failure
end
end
Of course this is an initial premise for your specs and might not just work if you copy and paste the code. You will have to do a bit of a reading and refactoring in order to get green signals from rspec.

Testing comand-line output using rspec

I'm trying to test a little gem that makes downloads from youtube using 'youtube-dl'.
I want to test the output from the command youtube-dl [url] --get-title but I dont know how I do that.
This is my code:
module Youruby
class Youtube
YT_DL = File.join(File.expand_path(File.dirname(__FILE__)), "../bin/youtube-dl")
def initialize(id)
#id = id
end
def get_title
system(YT_DL, '--get-title', get_url)
end
end
end
And this is my test:
require "spec_helper"
require "youruby"
describe Youruby do
it "get video title" do
video = Youruby::Youtube.new('uaEJvYWc2ag')
video.get_title.should == "FFmpeg-slowmotion.1"
end
end
When I run the tests I get this error:
Failure/Error: video.get_title.should == "FFmpeg-slowmotion.1"
expected: "FFmpeg-slowmotion.1"
got: true (using ==)
Diff:
## -1,2 +1,2 ##
-"FFmpeg-slowmotion.1"
+true
How do I do that?
Seems like your test is OK, and the implementation is failing (so, is OK for the test to report the fail)
On the implementation, Instead of using system method (which return true/false according the return code of the command), use backtick (which return the string with the output of the command)
def get_title
`#{YT_DL} --get-file #{get_url}`
end
ALso, as additional note, is not good for your implementation to depend on external commands (from Unit testing point of view), maybe you want to mock external system command execution (or not, you maybe know what strategy is better for your particular case)

Modifying RakeFile to allow varied targets

I am working on an HTML5/JavaScript app with Ruby packaging to be used on multiple platforms including a dash display unit on a car. The code base is the same across platforms except for a single wrapper file that houses all of the API-specific calls for a single platform: i.e. "sdk.web.js" or "sdk.car.js".
I'm hoping to modify the current PackageTask to allow an input for a target platform. Depending on the target, I want the corresponding wrapper file to be renamed to "sdk.js" and included in the package, disregarding the rest to keep the zip archive as small as possible.
From terminal, I want to say something like:
rake target "web"
which would put together a package including "sdk.web.js" renamed "sdk.js". Anyone know if this is possible, and how I would modify my existing RakeFile (below) in order to accomplish this?
require 'fileutils'
require 'rake/packagetask'
VERSION = ""
task :default => [:package]
def parse_version(filename)
f = File.open(filename, "rb")
contents = f.read
m = contents.match('var version = "([0-9.]+)";')
if m
return m[1]
else
return nil
end
end
desc "Package up the app into a zip file"
Rake::PackageTask.new("myApp") do |p|
p.version = parse_version("js/application.js")
p.need_zip = true
p.package_files = FileList["*", "**/*"]
p.package_files.exclude(".git", "pkg/*", "Rakefile", ".rvmrc", "Gemfile", "Gemfile.lock", ".project", ".settings", ".gitignore", "data/store/*", "docs", "docs/*")
end
In general, you can pass arguments to rake tasks. See for instance this page.
However, you don't have access to the package task from PackageTask. A solution would be to define your own packaging task which would add the right js script and then invoke package manually. For instance (untested):
Rake::PackageTask.new("myApp") do |p|
[snip]
p.package_files << "sdk.js"
end
task 'custom_packaging', :sdk do |t, args|
# Copy the right file to sdk.js
FileUtils.cp_f "sdk.#{args[:sdk]}.js", "sdk.js"
Rake::Task["package"].invoke
end

Resources