#ruby #variables #instance-variables #site-prism
#ruby #переменные #экземпляр-переменные #сайт-prism
Вопрос:
Я использую SitePrism для создания некоторых тестов POM. Один из моих классов page выглядит следующим образом:
class HomePage < SitePrism::Page
set_url '/index.html'
element :red_colour_cell, "div[id='colour-cell-red']"
element :green_colour_cell, "div[id='colour-cell-green']"
element :blue_colour_cell, "div[id='colour-cell-blue']"
def click_colour_cell(colour)
case colour
when 'red'
has_red_colour_cell?
red_colour_cell.click
when 'green'
has_green_colour_cell?
green_colour_cell.click
when 'blue'
has_blue_colour_cell?
blue_colour_cell.click
end
end
end
Метод click_colour_cell()
получает свое строковое значение, переданное с шага тестирования Capybara, который вызывает этот метод.
Если мне понадобится создать дополнительные подобные методы в будущем, это может стать довольно утомительным и громоздким из-за такого количества переключений регистра для определения потока кода.
Есть ли какой-нибудь способ, которым я могу создать переменную, которой динамически присваивается строковое значение другой переменной? Например, я хотел бы сделать что-то для click_colour_cell()
, похожее на следующее:
def click_colour_cell(colour)
has_@colour_colour_cell?
@colour_colour_cell.click
end
где @colour
представляет значение переданного значения, colour
и будет интерпретироваться Ruby:
def click_colour_cell('blue')
has_blue_colour_cell?
blue_colour_cell.click
end
Разве не для этого используются переменные экземпляра? Я попробовал вышеупомянутое предложение в качестве решения, но получаю неоднозначную ошибку:
syntax error, unexpected end, expecting ':'
end
^~~ (SyntaxError)
Если мне нужно использовать переменную экземпляра, то я не уверен, что использую ее правильно. если мне нужно использовать что-то еще, пожалуйста, посоветуйте.
Ответ №1:
Переменные экземпляра используются для определения свойств объекта.
Вместо этого вы можете достичь этого с помощью метода отправки и интерполяции строк.
Попробуйте следующее:
def click_colour_cell(colour)
send("has_#{colour}_colour_cell?")
send("#{colour}_colour_cell").click
end
Об отправке:
send
определен ли метод в Object
классе (родительский класс для всех классов).
Как говорится в документации, он вызывает метод, идентифицируемый заданной строкой или символом. Вы также можете передавать аргументы методам, которые пытаетесь вызвать.
В приведенном ниже фрагменте send выполнит поиск метода с именем testing
и вызовет его.
class SendTest
def testing
puts 'Hey there!'
end
end
obj = SendTest.new
obj.send("testing")
obj.send(:testing)
ВЫВОД
Hey there!
Hey there!
В вашем случае рассмотрим аргумент, переданный для colour
является blue
,
"has_#{colour}_colour_cell?"
вернет строку "has_blue_colour_cell?"
, а send будет динамически вызывать метод с именем has_blue_colour_cell?
. То же самое относится и к методу blue_colour_cell
Комментарии:
1. Я могу подтвердить, что это действительно работает. Но как? Я не знаком с
send
методом, и предоставленная документация описывает другую цель дляsend
того, для чего я могу его использовать. Мне действительно интересно больше понять, как это на самом деле работает.2. Спасибо за объяснение. Я нашел это решение наиболее полезным для моей ситуации на данный момент. Однако я хотел бы ограничить свое использование
send()
в будущем и рассмотреть другие решения, если они будут предложены.
Ответ №2:
Прямой ответ на ваш вопрос
Вы можете динамически получать / устанавливать переменные экземпляра с помощью:
instance_variable_get("@build_string_as_you_see_fit")
instance_variable_set("@build_string_as_you_see_fit", value_for_ivar)
Но…
Предупреждение!
Я думаю, что динамическое создание переменных здесь и / или использование таких вещей, как имена методов построения строк в send
, являются плохой идеей, которая сильно затруднит будущую ремонтопригодность.
Подумайте об этом так: каждый раз, когда вы видите имена методов, подобные этому:
click_blue_button
click_red_button
click_green_button
это то же самое, что делать:
add_one_to(1) // instead of 1 1, i.e. 1. (1)
add_two_to(1) // instead of 1 2, i.e. 1. (2)
add_three_to(1) // instead of 1 3, i.e. i. (3)
Вместо того, чтобы передавать значимый аргумент в метод, вы в конечном итоге жестко кодируете значения в имени метода! Продолжайте в том же духе, и в конечном итоге всей вашей кодовой базе придется иметь дело со «значениями», которые были жестко запрограммированы в именах методов.
Лучший способ
Вот что вы должны сделать вместо этого:
class HomePage < SitePrism::Page
set_url '/index.html'
elements :color_cells, "div[id^='colour-cell-']"
def click_cell(color)
cell = color_cells.find_by(id: "colour-cell-#{color}") # just an example, I don't know how to do element queries in site-prism
cell.click
end
end
Или, если вы должны иметь их как отдельные элементы:
class HomePage < SitePrism::Page
set_url '/index.html'
COLORS = %i[red green blue]
COLORS.each do |color|
element :"#{color}_colour_cell", "div[id='colour-cell-#{color}']"
end
def cell(color:) # every other usage should call this method instead
@cells ||= COLORS.index_with do |color|
send("#{color}_colour_cell") # do the dynamic `send` in only ONE place
end
@cells.fetch(color)
end
end
home_page.cell(color: :red).click
Комментарии:
1. Я думаю, что это может быть более подходящим подходом в ситуации, когда у меня есть несколько элементов, которые следуют шаблону, или один элемент, который может получать динамически именованный CSS-селектор в качестве своего идентификатора. У меня уже была такая ситуация. Я согласен, что в целях удобства сопровождения это может быть более эффективным (и элегантным) в будущем. Я попробую это и сообщу о своих результатах.
2. Единственная проблема с этим заключается в том, что это уменьшает значение того, для чего предназначен SitePrism. Хотя это может помочь в обслуживании методов, становится все труднее поддерживать элементы, если в шаблон вводятся новые или изменяются существующие.
3. Как текущий сопровождающий siteprism, я бы сказал, что это разумный путь. Это не идеально, и это не тот способ, которым я бы точно это кодировал. Но это хорошее начало. Если вы обнаружите, что у вас есть 5/10/15 цветов, и вы хотите сгенерировать для них большое количество элементов, я бы сохранил их независимо от кода (например, в файле yml), а затем извлек их (вместо использования константы). Таким образом, вы сохраняете бит, который менее релевантен коду (сколько у вас цветов), вдали от бита, который релевантен коду (элементы и помощники для каждого из них).