Тестирование рекурсивного ввода-вывода в rspec - PullRequest
1 голос
/ 07 марта 2019

Я пишу игру «Connect-Four» для дополнительной практики OO и rspec.Как часть моей программы, я хотел бы предложить пользователю выбрать столбец, в который он хотел бы поместить свою часть игры. Вот этот метод:

def get_col_choice(input, output)
     output.print "Player #{@player}, choose a column from 0-6: " 
     input_string = input.gets.chomp
     begin
         col_choice = Integer(input_string)
         raise("The column you chose is out of bounds.") if out_of_bounds?(col_choice)
         raise("The column you chose is fully occupied.") if unavailable?(col_choice)
         return col_choice
     rescue TypeError, ArgumentError
         output.puts "Your choice of column is invalid. Try again."
     rescue RuntimeError => err
         puts err.message
     end
     get_col_choice(input, output)
end

В IRB все работает, как я планировал.У меня зависание в rspec, где я столкнулся с NoMethodError, которая, как мне кажется, происходит из-за моего рекурсивного вызова get_col_choice.

Может кто-нибудь помочь мне понять, что я могу сделать, чтобы улучшить get_col_choice или написать правильные тесты в Rspec?Вот как выглядит вывод моей консоли из моего файла rspec:

Failures:

1) ConnectFourGame#get_col_choice notifies users which player gets to choose a column
 Failure/Error: $connect_four.get_col_choice(input, output)

 NoMethodError:
   undefined method `chomp' for nil:NilClass
 # /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:31:in `get_col_choice'
 # /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:42:in `get_col_choice'
 # ./connect_four_game_spec.rb:52:in `block (3 levels) in <top (required)>'

2) ConnectFourGame#get_col_choice returns the player's column choice
 Failure/Error: expect($connect_four.get_col_choice(input, output)).to eq(0)

 NoMethodError:
   undefined method `chomp' for nil:NilClass
 # /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:31:in `get_col_choice'
 # /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:42:in `get_col_choice'
 # ./connect_four_game_spec.rb:57:in `block (3 levels) in <top (required)>'

3) ConnectFourGame#get_col_choice notifies the user if the column they chose is already full of pieces
 Failure/Error: expect(output.string).to include("The column you chose is fully occupied.")
   expected "" to include "The column you chose is fully occupied."
 # ./connect_four_game_spec.rb:62:in `block (3 levels) in <top (required)>'

4) ConnectFourGame#get_col_choice notifies the user their input is invalid when a non-numeric string is entered
 Failure/Error: $connect_four.get_col_choice(input, output)

 NoMethodError:
   undefined method `chomp' for nil:NilClass
 # /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:31:in `get_col_choice'
 # /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:42:in `get_col_choice'
 # ./connect_four_game_spec.rb:67:in `block (3 levels) in <top (required)>'

5) ConnectFourGame#get_col_choice column choice is out of bounds notifies the user their column choice is out of bounds when col is greater than 6
 Failure/Error: $connect_four.get_col_choice(input, output)

 NoMethodError:
   undefined method `chomp' for nil:NilClass
 # /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:31:in `get_col_choice'
 # /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:42:in `get_col_choice'
 # ./connect_four_game_spec.rb:74:in `block (4 levels) in <top (required)>'

6) ConnectFourGame#get_col_choice column choice is out of bounds notifies the user their column choice is out of bounds when col is less than 0
 Failure/Error: $connect_four.get_col_choice(input, output)

 NoMethodError:
   undefined method `chomp' for nil:NilClass
 # /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:31:in `get_col_choice'
 # /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:42:in `get_col_choice'
 # ./connect_four_game_spec.rb:80:in `block (4 levels) in <top (required)>'

Finished in 0.02399 seconds (files took 0.1108 seconds to load)
12 examples, 6 failures

Failed examples:

rspec ./connect_four_game_spec.rb:51 # ConnectFourGame#get_col_choice notifies users which player gets to choose a column
rspec ./connect_four_game_spec.rb:56 # ConnectFourGame#get_col_choice returns the player's column choice
rspec ./connect_four_game_spec.rb:60 # ConnectFourGame#get_col_choice notifies the user if the column they chose is already full of pieces
rspec ./connect_four_game_spec.rb:66 # ConnectFourGame#get_col_choice notifies the user their input is invalid when a non-numeric string is entered
rspec ./connect_four_game_spec.rb:73 # ConnectFourGame#get_col_choice column choice is out of bounds notifies the user their column choice is out of bounds when col is greater than 6
rspec ./connect_four_game_spec.rb:79 # ConnectFourGame#get_col_choice column choice is out of bounds notifies the user their column choice is out of bounds when col is less than 0

Вот тесты, которые я написал для get_col_choice:

describe '#get_col_choice' do
    let(:output) { StringIO.new }
    let(:input) { StringIO.new("0\n") }
    it 'notifies users which player gets to choose a column' do
        $connect_four.get_col_choice(input, output)
        expect(output.string).to include("Player 2, choose a column from 0-6: ")
    end

    it "returns the player's column choice" do
        expect($connect_four.get_col_choice(input, output)).to eq(0)
    end

    it 'notifies the user if the column they chose is already full of pieces' do
        6.times { $connect_four.board.add_game_piece_to(0, "\u2468") }
        expect(output.string).to include("The column you chose is fully occupied.")
    end

    let(:input) { StringIO.new("!\n") }        
    it 'notifies the user their input is invalid when a non-numeric string is entered' do
        $connect_four.get_col_choice(input, output)
        expect(output.string).to include("Your choice of column is invalid. Try again.")
    end

    context 'column choice is out of bounds' do
        let(:input) { StringIO.new("7\n") }
        it 'notifies the user their column choice is out of bounds when col is greater than 6' do
            $connect_four.get_col_choice(input, output)
            expect(output.string).to include("The column you chose is out of bounds.")
        end

        let(:input) { StringIO.new("-1\n") }
        it 'notifies the user their column choice is out of bounds when col is less than 0' do
            $connect_four.get_col_choice(input, output)
            expect(output.string).to include("The column number you chose is out of bounds.")
        end
    end        
end

1 Ответ

0 голосов
/ 08 марта 2019

Я смог решить мою проблему, удалив input и output из get_col_choice:

def get_col_choice
    print "Player #{@player}, choose a column from 0-6: " 
    input_string = gets.chomp
    begin
        col_choice = Integer(input_string)
        raise("The column you chose is out of bounds.") if out_of_bounds?(col_choice)
        raise("The column you chose is fully occupied.") if @board.unavailable?(col_choice)
        return col_choice
    rescue TypeError, ArgumentError
        puts "Your choice of column is invalid. Try again."            
    rescue RuntimeError => err
        puts err.message
    end
    get_col_choice
end

В моем файле rspec вместо использования StringIO для внедрения ожидаемого поведения в get_col_choice я вставил gets, чтобы вернуть значения, необходимые для запуска ожидаемого поведения:

#code left out for brevity
before(:each) do
    @game = ConnectFourGame.new
end

describe '#get_col_choice' do
    before(:each) do
        allow($stdout).to receive(:write)
        @game.player = 1
    end

    context 'valid input' do
        before(:each) do
            allow(@game).to receive(:gets) {"0\n"}
        end

        it 'notifies users which player gets to choose a column' do
            expect{ @game.get_col_choice }.to output('Player 1, choose a column from 0-6: ').to_stdout
        end

        it "returns the user's column choice as an integer" do
            expect(@game.get_col_choice).to eq(0)
        end
    end

    context 'invalid input' do
        output_expectation = Proc.new{ expect { @game.get_col_choice }.to output(output).to_stdout }

        it 'notifies the user if the column they chose is already full of pieces' do
            allow(@game).to receive(:gets).and_return("0\n", "1\n")
            6.times { @game.board.add_game_piece_to(0, "\u2648") }
            output = "Player 1, choose a column from 0-6: The column you chose is fully occupied.\nPlayer 1, choose a column from 0-6: "
            output_expectation
        end

        it 'notifies the user their input is invalid when a non-numeric string is entered' do
            allow(@game).to receive(:gets).and_return("foo\n", "0\n")
            output = "Player 1, choose a column from 0-6: Your choice of column is invalid. Try again.\nPlayer 1, choose a column from 0-6: "
            output_expectation
        end

        it 'notifies the user their column choice is out of bounds when col is greater than 6' do
            allow(@game).to receive(:gets).and_return("7\n", "0\n")
            output = "Player 1, choose a column from 0-6: The column you chose is out of bounds.\nPlayer 1, choose a column from 0-6: "
            output_expectation
        end

        it 'notifies the user their column choice is out of bounds when col is less than 0' do
            allow(@game).to receive(:gets).and_return("-1\n", "0\n")
            output = "Player 1, choose a column from 0-6: The column you chose is out of bounds.\nPlayer 1, choose a column from 0-6: "
            output_expectation
        end
    end        
end

Причина, по которой я получил ошибки в моей предыдущей реализации get_col_choice, заключалась в том, что после того, как StringIO получил мою первую входную строку, она закрылась, что привело к значению nil, которое я пытался вызвать gets.

...