在尝试模拟Hash Elements的gets和puts时,RSpec无法定义单例错误

时间:2013-07-20 09:35:11

标签: hash io mocking rspec2 ruby-1.9.3

我有一个Book Model,它是一个ruby脚本,可以为程序中提到的某些预定义书籍标题分配价格。以下是图书模型的外观: -

class Book

  attr_accessor :books  
  def initialize books
    puts "Welcome to setting book price program"
    @books = books
  end

  def get_prices
    puts "Please enter appropriate price for each book item:-"
    count = 0
    @books = @books.inject({}) { |hash, book|
      print "#{book.first}: "
      price = STDIN.gets.chomp
      while (price !~ /^[1-9]\d*$/ && price != "second hand")
        puts "Price can't be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate price in integer"
        price = STDIN.gets.chomp #gets.chomp - throws error
      end
      price == "second hand" ? price = "100" : price #takes a default price
      hash[book.first] = price.to_i
      hash
    }
  end

end

books = {"The Last Samurai" => nil,
         "Ruby Cookbook" =>  nil,
         "Rails Recipes" =>  nil,
         "Agile Development with Rails" =>  nil,
         "Harry Potter and the Deathly Hallows" =>  nil}


book_details = Book.new(books)
book_details.get_prices
puts "\n*******Books Details:#{book_details.books}******\n"

我正在尝试编写一个测试用例,检查每个图书项目的价格输入是否正确。如果价格输入不当,则应要求用户重新输入价格。该计划做得很好。但是当我尝试使用RSpec模拟这种行为时,我遇到了困难。

require 'spec_helper'

describe Book do

  before :each do
    books = {"The Last Samurai" => nil,
         "Ruby Cookbook" =>  nil,
         "Rails Recipes" =>  nil,
         "Agile Development with Rails" =>  nil,
         "Harry Potter and the Deathly Hallows" =>  nil}
    @book = Book.new(books)
  end

  describe "#new" do
    it "Should be an instance of the Book" do
      @book.should be_an_instance_of Book
    end
  end

  describe "#getprice" do
    it "Should get the price in the correct format or else return appropriate error" do
      puts "\n************************************************************************\n"
      book_obj = @book
      STDOUT.should_receive(:puts).and_return("Welcome to setting book price program")
      book_obj.get_prices.should_not be_nil
      book_obj.books["The Last Samurai"].stub!(:gets) {"40"} #trying to set the value for one book using Hash
      book_obj.books["The Last Samurai"].should == 40 #verifying the value set for a particular key is accurate
    end
  end

end

你甚至可以从Github clone this code来尝试这个。我正在使用Ruby 1.9.3和rspec 2.11.0

The error that I'm getting currently is:-

Failures:

  1) Book#getprice Should get the price in the correct format or else return appropriate error
     Failure/Error: book_obj.books["The Last Samurai"].stub!(:gets) {"40"} #trying to set the value for one book using Hash
     TypeError:
       can't define singleton
     # ./spec/book_spec.rb:31:in `block (3 levels) in <top (required)>'

Finished in 7.61 seconds
2 examples, 1 failure

Failed examples:

rspec ./spec/book_spec.rb:21 # Book#getprice Should get the price in the correct format or else return appropriate error

更新的问题

对于错误的用户输入,使用以下测试用例我收到以下错误。我怎样才能正确处理这个?我尝试了几种选择,但它们似乎都失败了。请参阅规范摘要中的每个选项的评论。

 it "Incorrect input format should return error message asking user to re input" do
      puts "\n************************************************************************\n"
      book_obj = @book
      STDIN.stub(:gets) { "40abc" }

      #book_obj.get_prices.should be_nil --> adding this line of code goes into an infinite loop with the error message below
      #Price cannot be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate duration in integer\n

      STDOUT.should_receive(:puts).and_return("Price cannot be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate duration in integer\n")

      #the below two tests fails with syntax error - don't seem that easy to figure out what's going wrong

      #STDOUT.should_receive("Price cannot be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate duration in integer\n")
      #STDOUT.should == "Price cannot be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate duration in integer\n"
    end


Failures:

  1) Book#getprice Incorrect input format should return error message asking user to re input
     Failure/Error: STDOUT.should_receive(:puts).and_return("Price cannot be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate duration in integer\n")
       (#<IO:0x00000001c7b298>).puts(any args)
           expected: 1 time
           received: 0 times
     # ./spec/book_spec.rb:40:in `block (3 levels) in <top (required)>'

我真的很感激任何指导如何做到这一点。谢谢。

1 个答案:

答案 0 :(得分:5)

如果使用--backtrace标志运行规范,您将看到https://github.com/rspec/rspec-mocks/blob/v2.13.0/lib/rspec/mocks/method_double.rb#L140上出现错误,其中rspec-mocks正在尝试获取对象的单例类被存根或嘲笑。这适用于大多数类的实例,但规范中存在错误。

代码会将gets发送给STDIN,但该规范正试图在getsbook_obj.books["The Last Samurai"]上发送int,当时为int,并且你无法从$ irb 1.9.3-p392 :001 > class << 1; self; end TypeError: can't define singleton from (irb):1 from /Users/david/.rvm/rubies/ruby-1.9.3-p392/bin/irb:16:in `<main>' 获得单身人士:

gets

如果我理解正确,您需要进行两项更改。首先,将脚本的最后几行移动到一个单独的文件中,该文件在运行规范时不会被加载,例如bin / books(它可能需要book.rb,然后添加这些行)。

接下来,删除在book_obj.books["The Last Samurai"]上存根gets的行,并在STDIN上添加一行get_prices而不是调用STDIN(即发生{/ 1}}交互的时间):

STDIN.stub(:gets) { "40" }
book_obj.get_prices.should_not be_nil

这至少会让你的规格通过。

通常,直接处理w / STDINSTDOUT的代码很难在对象级别进行规范,因为像rspec,minitest等测试工具都使用STDOUT来呈现他们的信息,所以你最终会在shell中产生很多混乱的噪音。我建议更改设计以将输入/输出流注入书类(在运行脚本时可以是STDINSTDOUT,但在运行rspec时测试加倍),或者使用像aruba这样的工具,用于规范交互式shell脚本。