Не удалось проверить работу приложения: программа которая изменяет в 2D список, основанный на команды


Недавно мне подарили этот вопрос для приложения интервью и мне интересно, где я пошло не так. (Весь ответ содержится в скелете, который они дали мне, что я не включил сюда, так что вполне возможно, мне не удалось из-за ошибки за файлы, размещенные здесь.)

Редактировать: Так что я получил обратную связь от компании:

Минусы:

  • Без использования Перечислимого методов, например,. #map

  • Использование RuntimeError для проверки и сообщения об ошибках. Обработка такого рода логика без исключения, как правило, быть более гибкими.

  • Доступ к переменные экземпляра вне объекта.
  • Не использовать существующие библиотеки имитации.

  • #clear тест не является эффективным тест.

  • Все в одном файле.

  • Конвенций код Руби не последовало.

У меня два вопроса по этому поводу.

  1. Что значит "без использования Перечислимого методов, например,. #map"?
  2. Что я должен делать вместо того, чтобы использовать set_table на мой модульные тесты? Я создал это, потому что я хотел быть в состоянии создать таблицу без create_tableтак мои анализы остаться независимым.

Программа находится в ведении:

bitmap_reader = BitmapEditorFileReader()
bitmap_reader.run()

Программа должна читать файл, содержащий строковые команды, и редактировать список 2D на основе этих команд. Вопрос оформлена в растровом редакторе, т. е. в 2D список представляет собой растровое изображение, с каждого элемента письма, который представляет собой цвет. Команды выглядят следующим образом:

Я Н М - создать новый M х N изображения с пикселей белого цвета (п).

С - очищает таблицу, установив все пиксели Белые (о).

Л Х Й с - Цвет пиксела (Х,У) цветом С.

В X У1 У2 с - нарисовать отрезок вертикально цвет C в колонке "х", между строк Y1 и Y2 (включительно).

Ч х1 х2 м с - нарисуйте горизонтальный отрезок цвет C в строке Г между столбцы Х1 и Х2 (включительно).

S - показать содержимое текущего изображения

class BitmapEditorFileReader
  def run(file)
    return puts "please provide correct file" if file.nil? || !File.exists?(file)

    bitmap_editor = BitmapEditor.new
    command_reader = CommandReader.new(bitmap_editor)
    File.open(file).each do |line|
      command_reader.defer_to_method(line)
    end
  end
end

class BitmapEditor
  def create_table(width, height)
    _validate_height_and_width(width, height)
    @width, @height = width, height
    @table = Array.new(height) {Array.new(width, "O")}
  end

  def clear
    _check_table_exists()
    @table.each do |row|
      row.each do |element|
        element = "O"
      end
    end
  end

  def colour_pixel(x, y, colour)
    _check_table_exists()
    x, y = _change_strings_to_integers([x, y])
    _validate_rows_and_columns([x], [y])
    @table[y-1][x-1] = colour
  end

  def vertical_line(x, y_1, y_2, colour)
    _check_table_exists()
    _validate_rows_and_columns([x], [y_1, y_2])
    min_y, max_y = [y_1, y_2].minmax
    y = min_y
    while y <= max_y
      colour_pixel(x, y, colour)
      y = y + 1
    end
  end

  def horizontal_line(x_1, x_2, y, colour)
    _check_table_exists()
    _validate_rows_and_columns([x_1, x_2], [y])
    min_x, max_x = [x_1, x_2].minmax
    x = min_x
    while x <= max_x
      colour_pixel(x, y, colour)
      x = x + 1
    end
  end

  def display
    _check_table_exists()
    @table.each do |row|
      row.each do |pixel|
        print pixel
      end
      puts ""
    end
  end

  def _check_table_exists
    if @table == nil
      raise "Table has not yet been created. Please create table before other commands"
    end
  end


  def _validate_height_and_width(width, height)
    if width <= 0 or height <= 0
      raise "Height and Width must both be greater than 0"
    end
  end

  def _validate_rows_and_columns(columns, rows)
    rows.each do |row|
      if row < 1 or row > @height
        raise "Row must be between 1 and the height (inclusive)"
      end
    end

    columns.each do |column|
      if column < 1 or column > @width
        raise "Column must be between 1 and the width (inclusive)"
      end
    end
  end

  def _change_strings_to_integers(args)
    integer_args = []
    begin
      args.each do |arg|
        int_arg = arg.to_i
        integer_args.push(int_arg)
      end
    rescue SyntaxError
      raise "All numbers must be integers"
    end

    return integer_args
  end
end


class CommandReader
  def initialize(bitmap_editor)
    @bitmap_editor = bitmap_editor
  end

  @@command_map = {
    "I" => "create_table",
    "C" => "clear",
    "L" => "colour_pixel",
    "V" => "vertical_line",
    "H" => "horizontal_line",
    "S" => "display"
  }

  @@argument_types_map = {
    "I" => ["Integer", "Integer", "String"],
    "C" => [],
    "L" => ["Integer", "Integer", "String"],
    "V" => ["Integer", "Integer", "Integer", "String"],
    "H" => ["Integer", "Integer", "Integer", "String"],
    "S" => []
  }

  def defer_to_method(command_string)
    method_code, *args = command_string.split

    method_string = @@command_map[method_code]

    argument_types = @@argument_types_map[method_code]
    args = _convert_args_types(args, argument_types)

    @bitmap_editor.send(method_string, *args)
  end

  def _convert_args_types(args, argument_types)
    arg_type = nil
    index = nil
    begin
      args.each_with_index do |arg, index|
        arg_type = argument_types[index]
        args[index] = send(arg_type, arg)
      end
    rescue ArgumentError
      raise "Argument number #{index} must be of type #{arg_type}"
    end
    return args
  end
end

Модульные Тесты:

require "bitmap_editor"
require "rspec"

def set_table(bitmap_editor, x, y)
  bitmap_editor.instance_variable_set(:@table, Array.new(y) {Array.new(x, "O")})
  bitmap_editor.instance_variable_set(:@width, x)
  bitmap_editor.instance_variable_set(:@height, y)
end

describe BitmapEditor do

  describe '#CreateTable' do
    before(:each) do
      @bitmap_editor = BitmapEditor.new
    end

    it 'should raise exception on negative dimension argument' do
      expect{@bitmap_editor.create_table(4, -1)}.to raise_error(RuntimeError)
    end

    it 'should raise exception on zero dimension argument' do
      expect{@bitmap_editor.create_table(4, 0)}.to raise_error(RuntimeError)
    end

    it 'should create table of given positive dimensions' do
      @bitmap_editor.create_table(4, 5)
      expect(@bitmap_editor.instance_variable_get(:@table).length).to eq(5)
      expect(@bitmap_editor.instance_variable_get(:@table)[0].length).to eq(4)
    end
  end

  describe '#Clear' do
    before(:each) do
      @bitmap_editor = BitmapEditor.new
    end

    it 'should raise exception if table not created yet' do
      expect{@bitmap_editor.clear()}.to raise_error(RuntimeError)
    end

    it 'should clear table if table created' do
      set_table(@bitmap_editor, 4, 5)
      @bitmap_editor.clear()

      @bitmap_editor.instance_variable_get(:@table).each do |row|
        row.each do |element|
          expect(element).to eq("O")
        end
      end
    end
  end

  describe '#ColourPixel' do

    before(:each) do
      @bitmap_editor = BitmapEditor.new
    end

    it 'should raise exception if table not created yet' do 
      expect{@bitmap_editor.colour_pixel(4, 5, "C")}.to raise_error(RuntimeError)
    end

    it 'should raise exception if negative index given' do
      set_table(@bitmap_editor, 4, 5)

      expect{@bitmap_editor.colour_pixel(2, -1, "C")}.to raise_error(RuntimeError)
    end

    it 'should raise exception if index is larger than table dimension' do
      set_table(@bitmap_editor, 4, 5)

      expect{@bitmap_editor.colour_pixel(2, 6, "C")}.to raise_error(RuntimeError)
    end

    it 'should change colour of element at given location' do
      set_table(@bitmap_editor, 4, 5)

      @bitmap_editor.colour_pixel(2, 2, "C")

      @bitmap_editor.instance_variable_get(:@table).each_with_index do |row, y|
        row.each_with_index do |element, x|
          if x == 1 and y == 1
            expect(element).to eq("C")
          else
            expect(element).to eq("O")
          end
        end
      end
    end
  end

  describe '#VerticalLine' do
    before(:each) do
      @bitmap_editor = BitmapEditor.new
    end

    it 'should raise exception if table not created yet' do 
      expect{@bitmap_editor.vertical_line(2, 1, 3, "C")}.to raise_exception(RuntimeError)
    end

    it 'should raise exception if negative vertical index given' do 
      set_table(@bitmap_editor, 4, 5)
      expect{@bitmap_editor.vertical_line(2, -1, 3, "C")}.to raise_exception(RuntimeError)
    end

    it 'should raise exception if negative horizontal index given' do 
      set_table(@bitmap_editor, 4, 5)
      expect{@bitmap_editor.vertical_line(-2, 1, 3, "C")}.to raise_exception(RuntimeError)
    end

    it 'should raise exception if vertical index larger than table dimension' do 
      set_table(@bitmap_editor, 4, 5)
      expect{@bitmap_editor.vertical_line(2, 1, 6, "C")}.to raise_exception(RuntimeError)
    end

    it 'should raise exception if horizontal index larger than table dimension' do 
      set_table(@bitmap_editor, 4, 5)
      expect{@bitmap_editor.vertical_line(6, 1, 3, "C")}.to raise_exception(RuntimeError)
    end

    it 'should draw vertical line at given location' do
      set_table(@bitmap_editor, 4, 5)

      @bitmap_editor.vertical_line(2, 1, 3, "C")

      @bitmap_editor.instance_variable_get(:@table).each_with_index do |row, y|
        row.each_with_index do |element, x|
          if x == 1 and y >= 0 and y <= 2
            expect(element).to eq("C")
          else
            expect(element).to eq("O")
          end
        end
      end
    end
  end

  describe '#HorizontalLine' do
    before(:each) do
      @bitmap_editor = BitmapEditor.new
    end

    it 'should raise exception if table not created yet' do 
      expect{@bitmap_editor.horizontal_line(1, 3, 2, "C")}.to raise_exception(RuntimeError)
    end

    it 'should raise exception if negative vertical index given' do 
      set_table(@bitmap_editor, 4, 5)
      expect{@bitmap_editor.horizontal_line(1, 3, -2, "C")}.to raise_exception(RuntimeError)
    end

    it 'should raise exception if negative horizontal index given' do 
      set_table(@bitmap_editor, 4, 5)
      expect{@bitmap_editor.horizontal_line(-1, 3, 2, "C")}.to raise_exception(RuntimeError)
    end

    it 'should raise exception if vertical index larger than table dimension' do 
      set_table(@bitmap_editor, 4, 5)
      expect{@bitmap_editor.horizontal_line(1, 3, 6, "C")}.to raise_exception(RuntimeError)
    end

    it 'should raise exception if horizontal index larger than table dimension' do 
      set_table(@bitmap_editor, 4, 5)
      expect{@bitmap_editor.horizontal_line(1, 6, 2, "C")}.to raise_exception(RuntimeError)
    end

    it 'should draw horizontalline at given location' do
      set_table(@bitmap_editor, 4, 5)

      @bitmap_editor.horizontal_line(1, 3, 2, "C")

      @bitmap_editor.instance_variable_get(:@table).each_with_index do |row, y|
        row.each_with_index do |element, x|
          if y == 1 and x >= 0 and x <= 2
            expect(element).to eq("C")
          else
            expect(element).to eq("O")
          end
        end
      end
    end
  end

  describe '#Display' do
    before(:each) do
      @bitmap_editor = BitmapEditor.new
    end

    it 'should raise exception if table not created yet' do
      expect{@bitmap_editor.display()}.to raise_exception(RuntimeError)
    end

    it 'should display table if table created' do
      set_table(@bitmap_editor, 4, 5)
      $stdout = StringIO.new

      @bitmap_editor.display()

      output = "OOOO\nOOOO\nOOOO\nOOOO\nOOOO\n"
      expect(output).to eq($stdout.string)
    end
  end
end

describe CommandReader do

  class MockBitmapEditor
    def initialize
      @call_record = {
        "create_table" => nil,
        "clear" => nil,
        "colour_pixel" => nil,
        "vertical_line" => nil,
        "horizontal_line" => nil,
        "display" => nil
      }
    end

    def create_table(*args)
      @call_record["create_table"] = args
    end

    def clear
      @call_record["clear"] = []
    end

    def colour_pixel(*args)
      @call_record["colour_pixel"] = args
    end

    def vertical_line(*args)
      @call_record["vertical_line"] = args
    end

    def horizontal_line(*args)
      @call_record["horizontal_line"] = args
    end

    def display
      @call_record["display"] = []
    end

    def get_args_of_call(method_string)
      return @call_record[method_string]
    end
  end

  describe '#DeferToMethod' do
    before(:each) do
      @mock_editor = MockBitmapEditor.new
      @command_reader = CommandReader.new(@mock_editor)
    end

    it 'should call create_table method' do
      command = "I 1 2"

      @command_reader.defer_to_method(command)

      expect(@mock_editor.get_args_of_call("create_table")).to eq([1, 2])
    end

    it 'should call clear method' do
      command = "C"

      @command_reader.defer_to_method(command)

      expect(@mock_editor.get_args_of_call("clear")).to eq([])
    end

    it 'should call colour_pixel method' do
      command = "L 3 4 A"

      @command_reader.defer_to_method(command)

      expect(@mock_editor.get_args_of_call("colour_pixel")).to eq([3, 4, "A"])
    end

    it 'should call vertical_line method' do
      command = "V 5 6 7 B"

      @command_reader.defer_to_method(command)

      expect(@mock_editor.get_args_of_call("vertical_line")).to eq([5, 6, 7, "B"])
    end

    it 'should call horizontal_line method' do
      command = "H 8 9 10 C"

      @command_reader.defer_to_method(command)

      expect(@mock_editor.get_args_of_call("horizontal_line")).to eq([8, 9, 10, "C"])
    end

    it 'should call print method' do
      command = "S"

      @command_reader.defer_to_method(command)

      expect(@mock_editor.get_args_of_call("display")).to eq([])
    end
  end
end


135
3
задан 2 февраля 2018 в 07:02 Источник Поделиться
Комментарии
1 ответ

Я не знаю Ruby, поэтому я не могу говорить об этом. У меня есть несколько идей на минусы, что у тебя в обратной связи:


  1. Без использования Перечислимого методов, например,. #map

Многие языки поддерживают некоторый тип функции сопоставления; дана Iterable и функции, применить функцию к каждому элементу в массиве. Это особенно распространено в функциональных языках программирования, но это достаточно полезная функция, что многие другие языки заимствовали и его. В вашем случае, у вас есть много из них:

File.open(file).each do |line|
command_reader.defer_to_method(line)
end

Это действительно просто (я предполагаю, я не знаю Руби)

iterable.each do |itemOfIterable|
function(itemOfIterable)
end

Руби #map функция, кажется, поддерживают это.

File.open(file).map { |line| command_reader.defer_to_method(line) }

Или нет, это лучше-это другой вопрос; они, кажется, предпочитают его. Как и со всеми предложениями, вы должны рассмотреть, если то, что ты делаешь имеет смысл из-за эффективности, чистоты, идиоматические-Несс, насколько она соответствует принципам стиля коде и т. д.


  1. Использование RuntimeError для проверки и сообщения об ошибках

Вы могли бы более описательные ошибки, чем RuntimeError (Я предполагаю, что; опять же, не руби). Кроме этого, однако, если нет ничего, что вашу программу можно сделать, чтобы исправить саму ошибку он либо должен просить человека о помощи или бросить его так нежно, как это может. В этом случае я не думаю, что есть хороший способ, чтобы обратиться за помощью, поэтому вывод полезной ошибке и завершение представляется целесообразным.


  1. Доступ к переменные экземпляра вне объекта

Я с ними на этом. Вы определили API для вашего класса, а затем вы игнорируете его. Хотя вы хотите сохранить ваши испытания уникальной, очевидно, некоторые вещи (например, строительство объекта) трудно игнорировать. В этом случае следует сделать предположение, что вы уже проверяли create_table функции и что он работает правильно, а затем использовать его без страха во всех остальных случаях.


  1. Не использовать существующие библиотеки имитации

Я ничего не знаю о Ruby, поэтому я не знаю, какими они будут. Это, как говорится, часто имеет смысл включить инъекции зависимостей (посмотреть) из того, что ваш класс зависит от того, но вы не обязательно хотите проверить (например, файл ввода-вывода). Издеваясь над ними позволяет создать липовые версии под вашим контролем, так что вы можете проверить, что ваш код функций как и ожидалось, пока другой библиотека делает то, что он говорит, что делает. Ты разве не должен быть ответственным за тестирование чужого кода. Это не значит, что вы никогда не были, но по умолчанию следует предположить, что они работают, как описано.


  1. #clear тест не является эффективным тест

Не уверен, что они означают здесь, к сожалению. Я предполагаю, что они хотят больше тестов.


  1. Все в одном файле

Я не знаю, что Руби/принципы стиля этой компании. В общем, мне категорически не нравится в Java подход "все свои и файл класса"; это делает это тяжелее, чтобы группа вещей, логически не нажав вокруг, чтобы найти, где реализовано что-то. Пока все имеет смысл быть вместе, и ваш код все еще логично организовано, я в порядке с все в одном файле


  1. Конвенций код Руби не последовало.

Ничего не знаю об этом; в Python есть Пеп-8, который является де-факто руководство по стилю. Руби, возможно, что-то подобное


Некоторые из вещей, которые они указали, являются безусловно допустимыми; несколько других, я не знаю, если я согласен с. В общем, это подчеркивает важность объясняя, почему вы делаете вещи, как вы делаете (например, можно сгруппировать все в одном файле) и оправдывая их. Это даст вам больше очков, и, возможно, открыть дискуссию с интервьюером, а не просто делать что-то, если они не согласны с вами.

1
ответ дан 5 февраля 2018 в 06:02 Источник Поделиться