我正在为我的一个类编写测试,其中包含以下构造函数:
def initialize(filepath)
@transactions = []
File.open(filepath).each do |line|
next if $. == 1
elements = line.split(/\t/).map { |e| e.strip }
transaction = Transaction.new(elements[0], Integer(1))
@transactions << transaction
end
end
我想通过使用假文件而不是夹具来测试它。所以我写了以下规范:
it "should read a file and create transactions" do
filepath = "path/to/file"
mock_file = double(File)
expect(File).to receive(:open).with(filepath).and_return(mock_file)
expect(mock_file).to receive(:each).with(no_args()).and_yield("phrase\tvalue\n").and_yield("yo\t2\n")
filereader = FileReader.new(filepath)
filereader.transactions.should_not be_nil
end
不幸的是,这失败了,因为我依靠$.
等于1并在每一行上递增,并且由于某些原因在测试期间没有发生。我怎样才能确保它呢?
答案 0 :(得分:2)
全局变量使代码难以测试。您可以使用each_with_index
:
File.open(filepath) do |file|
file.each_with_index do |line, index|
next if index == 0 # zero based
# ...
end
end
但看起来您正在使用标题行解析CSV文件。因此,我使用Ruby CSV library:
require 'csv'
CSV.foreach(filepath, col_sep: "\t", headers: true, converters: :numeric) do |row|
@transactions << Transaction.new(row['phrase'], row['value'])
end
答案 1 :(得分:1)
您可以(并且应该)将IO#each_line
与Enumerable#each_with_index
一起使用,如下所示:
File.open(filepath).each_line.each_with_index do |line, i|
next if i == 1
# …
end
或者您可以删除第一行,并与其他人合作:
File.open(filepath).each_line.drop(1).each do |line|
# …
end
答案 2 :(得分:0)
如果你不想为每个测试捣乱File
,你可以尝试FakeFS实现一个基于StringIO的内存文件系统,它会在测试后自动清理。
这样,如果您的实施发生变化,您的测试就不需要改变。
require 'fakefs/spec_helpers'
describe "FileReader" do
include FakeFS::SpecHelpers
def stub_file file, content
FileUtils.mkdir_p File.dirname(file)
File.open( file, 'w' ){|f| f.write( content ); }
end
it "should read a file and create transactions" do
file_path = "path/to/file"
stub_file file_path, "phrase\tvalue\nyo\t2\n"
filereader = FileReader.new(file_path)
expect( filereader.transactions ).to_not be_nil
end
end
警告:这是Ruby中大多数文件访问的实现,尽可能将其传回原始方法。如果你正在做任何高级文件,你可能会开始遇到FakeFS实现中的错误。我遇到了一些二进制文件字节读/写操作,这些操作在FakeFS中没有实现Ruby的实现方式。