Test all subclasses on file update - ruby

I am learning unit testing with PHP and am following the TDD session on tutsplus: http://net.tutsplus.com/sessions/test-driven-php/
I have set up a ruby watchr script to run the PHPUnit unit tests every time a file is modified using Susan Buck's script: https://gist.github.com/susanBuck/4335092
I would like to change the ruby script so that in addition to testing a file when it is updated it will test all files that inherit from it. I name my files to indicate inheritance (and to group files) as Parent.php, Parent.Child.php, and Parent.Child.GrandChild.php, etc so the watchr script could just search by name. I just have no idea how to do that.
I would like to change:
watch("Classes/(.*).php") do |match|
run_test %{Tests/#{match[1]}_test.php}
end
to something like:
watch("Classes/(.*).php") do |match|
files = get all classes that inherit from {match[1]} /\b{match[1]}\.(.*)\.php/i
files.each do |file|
run_test %{Tests/{file}_test.php}
end
end
How do I do the search for file names in the directory? Or, is there an easier/better way to accomplish this?
Thanks
EDIT
This is what I ended up with:
watch("#{Library}/(.*/)?(.*).php") do |match|
file_moded(match[1], match[2])
end
def file_moded(path, file)
subclasses = Dir["#{Library}/#{path}#{file}*.php"]
p subclasses
subclasses.each do |file|
test_file = Tests + file.tap{|s| s.slice!(".php")}.tap{|s| s.slice!("#{Library}")} + TestFileEnd
run_test test_file
end
end
Where Library, Tests, and TestFileEnd are values defined at the top of the file. It was also changed so that it will detect changes in subfolders to the application library and load the appropriate test file.

I'm not entirely certain, but i think this will work:
watch("Classes/(.*).php") do |match|
subclasses = Dir["Classes/#{match[1]}*.php"]
filenames = subclasses.map do |file|
file.match(/Classes\/(.*)\.php/)[1]
end
filenames.each do |file|
run_test "Tests/#{file}_test.php"
end
end
It's probably not the cleaneast way, but it should work.
The first line saves all the relative paths to files in the Classes directory beginning with the changed filename in subclasses.
in the map block I use a regex to only get the filename, without any folder names or the .php extensions.
Hope this helps you

Related

Set order of RSpec tests

I'm building a test suite to teach OOP. I want the files in my spec/lib folder to be executed in a specific order.
I would like to define an array of class names, and their test suite would be executed in order. For example:
spec_order = %w(
FirstClass
SecondClass
ThirdClass
)
How might I accomplish this?
Name the _spec.rb files numerically:
01_first_spec.rb
02_second_spec.rb
...
Create a .rspec file
# .rspec
--order defined
Now, when running rspec the files should be executed in sorted order.
Ordering tests is not recommended by almost all testing frameworks. That is part of making sure that tests are independent and don't yield unexpected behavior when order changes.
However, if you want to run test files in specific order then you can accomplish that by writing scripts:
1. Shell Script
Consider the following script (ordered_test_script.sh):
for f in file1_spec.rb file2_spec.rb file3_spec.rb
do
rspec $f
done
Make sure the script is executable:
chmod +x ordered_test_script.sh
Then you can run the script:
./ordered_test_script.sh
2. Ruby Script
First, you might want to extend the String class to include an underscore method:
class String
def underscore
self.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
end
This will turn camelcase class names to underscore ones. For example (MyClass => my_class)
classes = %w(MyClass1 MyClass2 MyClass3)
classes.each do |c|
system("rspec #{c.to_s.underscore}_spec.rb")
end
Hope this helps.
Of course it's good practice to have randomization by default, but in some cases, such as when trying to track down order-related issues in your specs, or for documentation generation, it might be nice to specify a temporary order.
You can specify a "global" ordering for some (or all) of your spec files as follows:
config.register_ordering(:global) do |items|
files = <<END.split("\n")
./spec/some_spec.rb
./spec/another_spec.rb
END
# Original RSpec random order
randomized = RSpec::Core::Ordering::Random.new(config).order(items)
# Now do a stable sort by filename index
randomized.sort_by.with_index do |example_group, index|
files.index(example_group.file_path) || files.size + index
end
end
Documentation for register_ordering is available on Relish.

Trouble Creating Directories with mkdir

New to Ruby, probably something silly
Trying to make a directory in order to store files in it. Here's my code to do so
def generateParsedEmailFile
apath = File.expand_path($textFile)
filepath = Pathname.new(apath + '/' + #subject + ' ' + #date)
if filepath.exist?
filepath = Pathname.new(filepath+ '.1')
end
directory = Dir.mkdir (filepath)
Dir.chdir directory
emailText = File.new("emailtext.txt", "w+")
emailText.write(self.generateText)
emailText.close
for attachment in #attachments
self.generateAttachment(attachment,directory)
end
end
Here's the error that I get
My-Name-MacBook-2:emails myname$ ruby etext.rb email4.txt
etext.rb:196:in `mkdir': Not a directory - /Users/anthonydreessen/Developer/Ruby/emails/email4.txt/Re: Make it Brief Report Wed 8 May 2013 (Errno::ENOTDIR)
from etext.rb:196:in `generateParsedEmailFile'
from etext.rb:235:in `<main>'
I was able to recreate the error - it looks like email4.txt is a regular file, not a directory, so you can't use it as part of your directory path.
If you switched to mkdir_p and get the same error, perhaps one of the parents named in '/Users/anthonydreessen/Developer/Ruby/emails/email4.txt/Re: Make it Brief Report Wed 8 May 2013' already exists as a regular file and can't be treated like a directory. Probably that last one named email.txt
You've got the right idea, but should be more specific about the files you're opening. Changing the current working directory is really messy as it changes it across the entire process and could screw up other parts of your application.
require 'fileutils'
def generate_parsed_email_file(text_file)
path = File.expand_path("#{#subject} #{date}", text_file)
while (File.exist?(path))
path.sub!(/(\.\d+)?$/) do |m|
".#{m[1].to_i + 1}"
end
end
directory = File.dirname(path)
unless (File.exist?(directory))
FileUtils.mkdir_p(directory)
end
File.open(path, "w+") do |email|
emailText.write(self.generateText)
end
#attachments.each do |attachment|
self.generateAttachment(attachment, directory)
end
end
I've taken the liberty of making this example significantly more Ruby-like:
Using mixed-case names in methods is highly irregular, and global variables are frowned on.
It's extremely rare to see for used, each is much more flexible.
The File.open method yields to a block if the file could be opened, and closes automatically when the block is done.
The ".1" part has been extended to keep looping until it finds an un-used name.
FileUtils is employed to makes sure the complete path is created.
The global variable has been converted to an argument.

How can I make Aruba use a different ENV["HOME"]?

From the docs:
Per default Aruba will create a directory tmp/aruba where it performs its file operations.
However, my application uses ENV["HOME"] to create and read a file (~/.foorc), so I need Aruba to use a fake ENV["HOME"].
Do I need to set it in some support-file, or is there a way to tell Aruba to its tmp/aruba for files in ENV["HOME"]?
Here is an excerpt of my code that I am testing (obviously I am testing this with Cucumber/Aruba on a much higher level, but the usage of ENV["HOME"] is what is important here.):
def initialize config_path = ""
if config_path.empty?
#config_path = File.join ENV["HOME"], ".todotxt.cfg"
else
#config_path = config_path
end
if file_exists?
super #config_path
validate
end
end
def file_exists?
File.exists? #config_path
end
#....
ask_to_create unless #config.file_exists?
#...
The Specification:
Scenario: todotxt
Given an empty installation
When I run `todotxt`
Then it should pass with:
"""
Should I create a sample config file? [Y/n]
"""
Looking into the implementation in Aruba itself, I could craft something very similar:
File features/support/aruba.rb, is autoloaded by cucumber and implements the Around hook:
# Temporarily enforce an isolated, fake, homedir.
around do |scenario, block|
#__aruba_original_home = ENV["HOME"]
ENV["HOME"] = File.expand_path(File.join("tmp", "aruba"))
block.call
ENV["HOME"] = #__aruba_original_home
end
From now on, a directory tmp/aruba is used as $HOME.
Note that in aruba, this temporary path is configurable, and that above code does not take that into consideration. It will break when the tmp path is configured elsewhere.
Aruba offers a step for just that:
Given a mocked home directory

How to test a script that generates files

I am creating a Rubygem that will let me generate jekyll post files. One of the reasons I am developing this project is to learn TDD. This gem is strictly functional on the command line, and it has to make a series of checks to make sure that it finds the _posts directory. This depends on two things:
Wether or not a location option was passed
Is that location option valid?
A location option was not passed
Is the posts dir in the current directory?
Is the posts dir the current working directory?
At that point, I am really having a hard time testing that part of the application. So I have two questions:
is it acceptable/okay to skip tests for small parts of the application like the one described above?
If not, how do you test file manipulation in ruby using minitest?
Some projects I've seen implement their command line tools as Command objects (for example: Rubygems and my linebreak gem). These objects are initialized with the ARGV simply have a call or execute method which then starts the whole process. This enables these projects to put their command line applications into a virtual environment. They could, for example hold the input and output stream objects in instance variables of the command object to make the application independant of using STDOUT/STDIN. And thus, making it possible to test the input/output of the command line application. In the same way I imagine, you could hold your current working directory in an instance variable to make your command line application independent of your real working directory. You could then create a temporary directory for each test and set this one as the working directory for your Command object.
And now some code:
require 'pathname'
class MyCommand
attr_accessor :input, :output, :error, :working_dir
def initialize(options = {})
#input = options[:input] ? options[:input] : STDIN
#output = options[:output] ? options[:output] : STDOUT
#error = options[:error] ? options[:error] : STDERR
#working_dir = options[:working_dir] ? Pathname.new(options[:working_dir]) : Pathname.pwd
end
# Override the puts method to use the specified output stream
def puts(output = nil)
#output.puts(output)
end
def execute(arguments = ARGV)
# Change to the given working directory
Dir.chdir(working_dir) do
# Analyze the arguments
if arguments[0] == '--readfile'
posts_dir = Pathname.new('posts')
my_file = posts_dir + 'myfile'
puts my_file.read
end
end
end
end
# Start the command without mockups if the ruby script is called directly
if __FILE__ == $PROGRAM_NAME
MyCommand.new.execute
end
Now in your test's setup and teardown methods you could do:
require 'pathname'
require 'tmpdir'
require 'stringio'
def setup
#working_dir = Pathname.new(Dir.mktmpdir('mycommand'))
#output = StringIO.new
#error = StringIO.new
#command = MyCommand.new(:working_dir => #working_dir, :output => #output, :error => #error)
end
def test_some_stuff
#command.execute(['--readfile'])
# ...
end
def teardown
#working_dir.rmtree
end
(In the example I'm using Pathname, which is a really nice object oriented file system API from Ruby's standard library and StringIO, which is useful for for mocking STDOUT as it's an IO object which streams into a simple String)
In the acutal test you could now use the #working_dir variable to test for existence or content of files:
path = #working_dir + 'posts' + 'myfile'
path.exist?
path.file?
path.directory?
path.read == "abc\n"
From my experience (and thus this is VERY subjective), I think it's ok sometimes to skip unit testing in some areas which are difficult to test. You need to find out what you get in return and the cost for testing or not. My rule of thumb is that the decision to not test a class should be very unusual (around less than 1 in 300 classes)
If what you're trying to test is very difficult, because of the dependencies with the file system, I think you could try to extract all the bits that interact with the file system.

Good Way to Handle Many Different Files?

I'm building a specialized pipeline, and basically, every step in the pipeline involves taking one file as input and creating a different file as output. Not all files are in the same directory, all output files are of a different format, and because I'm using several different programs, different actions have to be taken to appease the different programs.
This has led to some complicated file management in my code, and the more I try to organize the file directories, the more ugly it's getting. Just about every class involves some sort of code like the following:
#fileName = File.basename(file)
#dataPath = "#{$path}/../data/"
MzmlToOther.new("mgf", "#{#dataPath}/spectra/#{#fileName}.mzML", 1, false).convert
system("wine readw.exe --mzXML #{#file}.raw #{$path}../data/spectra/#{File.basename(#file + ".raw", ".raw")}.mzXML 2>/dev/null")
fileName = "#{$path}../data/" + parts[0] + parts[1][6..parts[1].length-1].chomp(".pep.xml")
Is there some sort of design pattern, or ruby gem, or something to clean this up? I like writing clean code, so this is really starting to bother me.
You could use a Makefile.
Make is essential a DSL designed for handling converting one type of file to another type via running an external program. As an added bonus, it will handle only performing the steps necessary to incrementally update your output if some set of source files change.
If you really want to use Ruby, try a rakefile. Rake will do this, and it's still Ruby.
You can make this as sophisticated as you want but this basic script will match a file suffix to a method which you can then call with the file path.
# a conversion method can be used for each file type if you want to
# make the code more readable or if you need to rearrange filenames.
def htm_convert file
"HTML #{file}"
end
# file suffix as key, lambda as value, the last uses an external method
routines = {
:log => lambda {|file| puts "LOG #{file}"},
:rb => lambda {|file| puts "RUBY #{file}"},
:haml => lambda {|file| puts "HAML #{file}"},
:htm => lambda {|file| puts htm_convert(file) }
}
# this loops recursively through the directory and sub folders
Dir['**/*.*'].each do |f|
suffix = f.split(".")[-1]
if routine = routines[suffix.to_sym]
routine.call(f)
else
puts "UNPROCESSED -- #{f}"
end
end

Resources