How to design a Ruby command line interface that can be tested with RSpec? - ruby

I'm trying to create a Ruby command line interface with the following options:
ruby cli.rb -g # Prints a list of people sorted by gender
ruby cli.rb -n # Prints a list of people sorted by last name
ruby cli.rb -b # Prints a list of people sorted by birth date
ruby cli.rb -a LastName FirstName DateOfBirth # Add a new person to the list of people
I have already created a Directory class that stores a list of people and also has methods to sort people and add people. The next step is to build a CLI around this class.
How can I create this in a way that can be tested with RSpec? My initial attempt involved a looped gets.chomp instead of running a Ruby file with flags, but this loop does not play well with RSpec. The examples I've found online just use Ruby's OptionParser in a simple script and not inside an actual class. Also, how would I handle creating ARGV parameters in RSpec? I'm just looking for tips on the general structure of this CLI class so that I can write tests.
I do not want to use any gems for this.

I'm not sure what the context of your problem is, so I'm just going to give you a little brain dump, the merit of which is yours to determine:
A cold hard fact: In the wild, I've seen few people write unit tests for command line arguments. The reason for this probably has to do with the fact that a well-written command line tool is a lot like a very skinny router (in the MVC sense). It looks at the arguments, and routes them to the proper function (eg, "-g" routes to something like MyObject.print_by_gender). And if you use a battle-tested options parser (like OptionParser) on the incoming side and a well-tested piece of code that the router calls (eg, MyObject.print_by_gender, assuming that's the name of a function, and that you've tested it well), then there's very little to test in the command line file itself.
That context in place, let's consider your problem. You want to test an object that interacts with the external world in 2 ways. Namely, stdin and stdout. This means that you could create an integration-style test, and actually pass thing in on stdin and test the output the command creates on stdout - as you mentioned - or you can mock stdin and stdout. In my experience, mocking external things like stdin and stdout are a better approach. That said, I'll recommend these two SO questions/answers to explain how to mock STDIN (this and this).

Related

Testing a whole script in Ruby

What is the best way of testing a whole script? I want to test if a script returns (prints) a correct result. So far what I have is another script that more or less does just:
# test.rb
expected = 'test'
output = `script.rb --many params`
if output != expected
puts "Error"
end
In this example, the script I want to test is called script.rb.
Cheers.
Using test-unit you end up with code like this:
class TestScript < Test::Unit::TestCase
def test_output
assert_equal 'test', `script --options`
end
end
You'll probably want to use the open3 library when dealing with output from programs as that gives you more control over how that's handled. You could write your own wrapper for that as well, like assert_output_equal:
def assert_output_equal(output, command, message = nil, &block)
Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
assert_equal output, stdout.read, message, &block
end
end
You can add on to that to test for the status from your process, ensuring it succeeded, and that there wasn't any output on STDERR and such.
When it comes to testing things that produce a lot of textual output (e.g. JSON data, formatted lists) I find it's best to create files in a place like test/expect and compare the file to the output rather than defining the text in the source. This makes it easy to create the files in the first place:
old_script > test/expected/old_script.default
old_script --option > test/expected/old_script.with-option
It also makes your version control diffs easier to read when something needs to be adjusted in there.
Define Test Fixtures
What is the best way of testing a whole script? I want to test if a script returns (prints) a correct result.
You need a test fixture that includes a known input value and an expected result. In other words:
Given a known input value,
When you run the program
Then you will receive your expected answer as output.
Note that testing an entire program this way can tell you if you got the right results from a set of inputs, but it will generally not provide insight into where the problem is. However, many forms of acceptance testing using this "black box" approach to testing, so it's certainly a reasonable approach as long as you understand the limitations.
Possible Frameworks
The list of possible testing frameworks is not infinite, but if you don't want to write your own test harness from scratch you may want to try one of the more popular ones. Some possibilities to kick-start your own research include:
Bats
Cucumber
Aruba
There are of course plenty of others, but per your original question you will probably want to focus on ATDD or BDD testing tools, rather than unit testing, if you want to test the entire script rather than its component parts.

Using Ruby to execute arbitrary system calls

This problem is to get into an internship within a devops department:
"Write a ruby library that executes arbitrary system calls (eg: “dmesg", "ping -c 1 www.google.com”) and provides separated output streams of stderr and stdout as well are providing the final return code of the process. Show your work with unit tests.”
Am I supposed to use already established system calls and replicate them in Ruby code? That seems silly to me. Am I supposed to come up with my own arbitrary calls and write a library complete with errors and status calls?
I am not looking for someone to write this for me. I feel that the first step to solving this problem is understanding it.
Get Clarification from the Source
The assignment is poorly worded, and misuses a number of terms. In addition, we can only guess what they really expect; the appropriate thing to do would be to ask the company directly for clarification.
Re-Implement Open3
With that said, what they probably want is a way to process any given command and its arguments as a decorated method, akin to the way Open3#capture3 from the standard library works. That means the code you write should take the command and any arguments as parameters.
For example, using Open3#capture3 from the standard library:
require 'open3'
def command_wrapper cmd, *args
stdout_str, stderr_str, status = Open3.capture3 "#{cmd} #{args.join ' '}"
end
command_wrapper "/bin/echo", "-n", "foo", "bar", "baz"
#=> ["foo bar baz", "", #<Process::Status: pid 31661 exit 0>]
I sincerely doubt that it's useful to re-implement this library, but that certainly seems like what they're asking you to do. Shrug.
You're also supposed to write unit tests for the re-implementation, so you will have to swot something up with a built-in framework like Test::Unit or MiniTest, or an external testing framework like RSpec, or Wrong. See the Ruby Toolbox for a more comprehensive list of available unit testing frameworks.
Luckily for you, Ruby makes it very easy to interact with the underlying operative system.
You can start by reading the documentation for these methods:
Kernel#`
Kernel#system
Kernel#exec
Kernel#spawn
IO#popen
Also, there is the Open3 module from the stdlib.

Compiling 'hello, world' GNU smalltalk

Whenever I set out to learn a language the first thing I do is produce an executable file written in that language (it could be a compiled program or a script) that when run prints 'hello, world' and a newline to stdout:
theironknuckle#beastbook:~/Code$ ./hello
hello, world
After about an hour of mucking around with GNU Smalltalk, I haven't found out how to do this.
(I know that the hello world program can be expressed from within a session as
'hello, world' printNl
This doesn't meet my stdout requirements)
I understand that there's no mainline in Smalltalk. So I'm quite intrigued by what sort of boilerplate could possibly be necessary to make it happen. Again, the file doesn't necessarily have to be compiled, but the end result of the exercise has to be smalltalk code that results in the above session extract.
PS. yesyesyes I know that I'm doing it wrong by not embracing the "image based programming" philosophy. I don't care. I'm not against learning how to work with the image and IDE and all that, but I really have minimal interest right now. What I care about is the Smalltalk language itself. Syntactically, philosophically and typographically it is rather beautiful. I feel comfortable learning programming languages from a commandline interpreter and a text editor. :)
In GNU Smalltalk, there's pretty much no boilerplate.
You could just put your single line in a .st file, and run it with gst hello.st
If you want to explore using a class instead of directly executed statements, then that's easy too, the following in a file passed to gst will do the trick:
Object subclass: Hello [
greet [
'Hello, World' displayNl
]
].
greeting := Hello new.
greeting greet.
Files passed to gst on the command line are parsed and executed in sequence, so you could split the above listing into two separate files - one to declare / compile the class, and the second to actually run it.
Once you've developed your program, you can use the -S flag to gst to snapshot the image after loading your classes, so that you don't have the compilation overhead each time, and can just run your startup statement instead.
gst also has shebang support, so you can put #! /usr/bin/gst -f at the top of your file if you don't want to pass it to gst manually. (See the documentation on invocation for more, including how to do it without hardcoding the location of gst)

Using Rspec 2 to test a command line application's UI layer

I'm writing a command-line application in Ruby. I'm rather familiar with Rspec 2, as it's used in some Rails applications I'm working on. I'm writing a command-line application and I'm attempting to use Rspec 2 for BDD.
How can I test the user interface layer of my application? I need to give the application interactive input, or check for certain output printed using puts. Also, is there any way to suppress the STDOUT output? When I run autotest, the output of my application gets printed between the status outputs of rspec, making it extremely hard to read.
Here's an example that should help you. I use it in one of my apps that I test with Minitest, but it should be easy to translate to Rspec.
def setup
$stdout = StringIO.new
...
end
This gets rid of the app's output in between the test results, since stdout will be written to a StringIO object. The same way you can also test if something specific got output, since you can check the StringIO object against regular expressions.

ruby idioms for using command-line options

I'm trying to pick up ruby by porting a medium-sized (non-OO) perl program. One of my personal idioms is to set options like this:
use Getopt::Std;
our $opt_v; # be verbose
getopts('v');
# and later ...
$opt_v && print "something interesting\n";
In perl, I kind of grit my teeth and let $opt_v be (effectively) a global.
In ruby,the more-or-less exact equivalent would be
require 'optparse'
opts.on("-v", "--[no-]verbose", TrueClass, "Run verbosely") {
|$opt_verbose|
}
opts.parse!
end
where $opt_verbose is a global that classes could access. Having classes know about global flags like that seems ... er ... wrong. What's the OO-idiomatic way of doing this?
Let the main routine take care of all option-related stuff and have the classes just return things to it that it decides how to deal with?
Have classes implement optional behaviour (e.g., know how to be verbose) and set a mode via an attr_writer sort of thing?
updated: Thanks for the answers suggesting optparse, but I should have been clearer that it's not how to process command-line options I'm asking about, but more the relationship between command-line options that effectively set a global program state and classes that should ideally be independent of that sort of thing.
A while back I ran across this blog post (by Todd Werth) which presented a rather lengthy skeleton for command-line scripts in Ruby. His skeleton uses a hybrid approach in which the application code is encapsulated in an application class which is instantiated, then executed by calling a "run" method on the application object. This allowed the options to be stored in a class-wide instance variable so that all methods in the application object can access them without exposing them to any other objects that might be used in the script.
I would lean toward using this technique, where the options are contained in one object and use either attr_writers or option parameters on method calls to pass relevant options to any additional objects. This way, any code contained in external classes can be isolated from the options themselves -- no need to worry about the naming of the variables in the main routine from within the thingy class if your options are set with a thingy.verbose=true attr_writer or thingy.process(true) call.
The optparse library is part of the standard distribution, so you'll be able to use it without requiring any third party stuff.
I haven't used it personally, but rails seems to use it extensively and so does rspec, which I guess is a pretty solid vote of confidence
This example from rails' script/console seems to show how to use it pretty easily and nicely
The first hit on google for "processing command line options in ruby" is an article about Trollop which seems to be a good tool for this job.

Resources