python - 如何在这个可嵌套的 For 循环中实现 Robot Framework 风格的变量?

标签 python loops robotframework

我在 Robot Framework 中看到过很多“可嵌套”的 For 循环,主要是在内部创建一个带有 For 循环的关键字,然后在另一个 For 循环中调用该关键字。我使用 Python 2.7.13 创建了一个可嵌套的 For 循环,但因为它主要使用 Run Keywords 语法,所以我无法使用 Robot Framework 风格的语法创建变量(例如 ${variable_name}= My Keyword) .作为记录,这是 BuiltIn Robot Framework 库下的 Run Keywords,它使用以下语法:

Run Keywords    Keyword1    arg11   arg12   AND     Keyword2    arg21   arg22

等价地,可以这样写:

Run Keywords    Keyword1    arg11   arg12
...     AND     Keyword2    arg21   arg22

它通常不支持在其中创建变量。但是,我使用 Run Keywords 作为可嵌套 For 循环的一部分。这是该关键字的 Python 代码。

from robot.libraries.BuiltIn import BuiltIn

class Loops(object):
    def __init__(self):
        self.selenium_lib = BuiltIn().get_library_instance('ExtendedSelenium2Library')
        self.internal_variables = {}

    def for_loop(self, loop_type, start, end, index_var, *keywords):
        #   Format the keywords
        keywords = self._format_loop(*keywords)

        #   Clean out the internal variables from previous iterations
        self.internal_variables = {}

        #   This is the actual looping part
        for loop_iteration in range(int(start), int(end)):
            keyword_set = self._index_var_swap(loop_iteration, index_var, *keywords)
            #   If it's a one-keyword list with no arguments, then I can use the fastest possible keyword to run it
            if len(keyword_set) == 1:
                BuiltIn().run_keyword(keyword_set)
            #   If it's a one-keyword list with arguments, then I can use a faster keyword to run it
            elif 'AND' not in keyword_set:
                BuiltIn().run_keyword(*keyword_set)
            #   If it's a multiple-keyword list, then I have to use Run Keywords
            else:
                BuiltIn().run_keywords(*keyword_set)

    def _format_loop(self, *keywords):
        keywords = list(keywords)   # I need to format the keywords as a list.
        changed = False             # Whether or not I changed anything in the previous iteration.
        index = 0                   # The item index I'm at in the list of keywords
        del_list = []               # The list of items I need to delete
        swap_list = []              # The list of items i need to swap to AND for the use of Run Keywords
        #   For each argument
        for x in keywords:
            #   Format it to a string
            x = str(x)
            #   If the keyword in question happens to be one of the 'Assign Internal Variable' keywords, then I need
            #       to run it now, not later.
            #   By splitting it up, I add a little complexity to the code but speed up execution when you're just
            #       assigning a scalar variable as opposed to having to search through the next few items just to find
            #       what I know is just going to be the next one.
            #   So, if it's the simple assignment...
            if x.lower() == 'assign internal variable':
                #   ...run the Assign Internal Variable keyword with the two inputs
                BuiltIn().run_keyword(x, *keywords[int(index)+1:int(index)+3])
            #   If it's the more complicated variable...
            elif x.lower() == 'assign internal variable to keyword':
                #   ...initialize variables...
                deliminator_search = 0
                k_check = x
                #   ...search the next few keywords for a deliminator...
                while k_check != '\\' and k_check != '\\\\':
                    deliminator_search = deliminator_search + 1
                    k_check = keywords[int(index)+deliminator_search]
                #   ...and run the Assign Internal Variable to Keyword keyword with the found keyword
                BuiltIn().run_keyword(x, *keywords[int(index)+1:int(index)+2+deliminator_search])

            #   If the previous element was not changed...
            if not changed:
                #   If the current item is not the last one on the list...
                if x != len(keywords) - 1:
                    #   If the current item is a deliminator...
                    if x == '\\':
                        #   If the next item is a deliminator, delete this item and set changed to True
                        if keywords[int(index) + 1] == '\\':
                            del_list.append(index)
                            changed = True
                        #   If the next item is not a deliminator...
                        else:
                            #   If this isn't the first deliminator on the list, swap it to an 'AND'
                            if index != 0:
                                swap_list.append(index)
                                changed = True
                            #   If this deliminator is in position index=0, just delete it
                            else:
                                del_list.append(index)
                                changed = True
                    #   If the current element is not a deliminator, then I don't need to touch anything.
                #   If the current element is the last one, then I don't need to touch anything
            #   If the previous element was changed, then I don't need to "change" this one...
            elif changed:
                changed = False
                #   ...but if it's a deliminator then I do need to set it up for the inner for loop it means.
                if keywords[index] == '\\':
                    keywords[index] = '\\\\'
            index = index + 1   # Advance the index

        # These actually do the swapping and deleting
        for thing in swap_list:
            keywords[thing] = 'AND'
        del_list.reverse()
        for item in del_list:
            del keywords[item]

        # I also need to activate my variables for this set of keywords to run.
        keywords = self._activate_variables(*keywords)

        return keywords

    @staticmethod
    def _index_var_swap(loop_iteration, index_var, *keywords):
        #   Format the keywords as a list for iteration
        keywords = list(keywords)
        index = 0
        #   For every line in keywords
        for line in keywords:
            #   Replace all instances of the index_var in the string with the loop iteration as a string
            keywords[index] = str(line).replace(str(index_var), str(loop_iteration))
            index = index + 1
        return keywords

    def assign_internal_variable(self, variable_name, assignment):
        # This keyword works like any other keyword so that it can be activated by BuiltIn.run_keywords
        # The syntax for an internal variable is '{{varName}}' where varName can be anything
        self.internal_variables[variable_name] = assignment

    def assign_internal_variable_to_keyword(self, variable_name, keyword, *assignment):
        # This keyword works like any other keyword so that it can be activated by BuiltIn.run_keywords
        # The syntax for an internal variable is '{{varName}}' where varName can be anything
        self.internal_variables[variable_name] = BuiltIn.run_keyword(keyword, *assignment)

    def _activate_variables(self, *keywords):
        #   Initialize variables
        keywords = list(keywords)   # Cast keywords as a List
        index = 0                   # The index of the keyword I'm looking at

        #   For each keyword
        for keyword in keywords:
            keyword = str(keyword)  # Cast keyword as a String
            assignment = False      # Whether or not the found variable name is in a variable assignment
            for key in self.internal_variables.keys():
                key = str(key)      # Cast key as a String
                #   If I can find the key in the keyword and it's not an assignment...
                if keyword.find(key) > -1 and not assignment:
                    #   ...replace the text of the key in the keyword.
                    keywords[index] = keyword.replace(str(key), str(self.internal_variables[key]))
                #   If the keyword I'm looking at is an assignment...
                if keyword.lower() == 'assign internal variable'\
                        and keyword.lower() != 'assign internal variable to keyword':
                    #   ...then my next keyword is going to definitely be a known variable, so I don't want to touch it.
                    assignment = True
                #   If the keyword I'm looking at is not an assignment...
                else:
                    #   ...set assignment to False just in case the previous one happened to be an assignment.
                    assignment = False
            index = index + 1   # Advance the index
        return keywords     # Return the list of keywords to be used in the format loop

如您所见,我的解决方法是创建一个名为 Assign Internal Variable 的新关键字及其伙伴 Assign Internal Variable to Keyword。然而,这改变了 Robot Framework 循环的语法,这对我来说有点太明显了,并且在某种程度上限制了内部变量与外部变量完全分开。 Robot Framework 测试用例中此关键字的作用示例如下:

*** Variables ***
${gold_squadron} =  Gold
${red_squadron} =   Red

*** Test Cases ***
Test For Loop
    For Loop    IN RANGE    0   1   INDEX0
    ...     \\  For Loop    IN RANGE    1   6   INDEX1
    ...     \\  \\  Assign Internal Variable    {{standing_by}}     Standing By Red Leader
    ...     \\  \\  Run Keyword If      INDEX1 == 1     Log to Console  ${red_squadron} Leader Standing By
    ...     \\  \\  Run Keyword Unless  INDEX1 == 1     Log to Console  ${red_squadron} INDEX1 {{standing_by}}
    ...     \\  For Loop    IN RANGE    1   6   INDEX2
    ...     \\  \\  Assign Internal Variable    {{standing_by_2}}   Standing By Gold Leader
    ...     \\  \\  Run Keyword If      INDEX2 == 1     Log to Console  ${gold_squadron} Leader Standing By
    ...     \\  \\  Run Keyword Unless  INDEX2 == 1     Log to Console  ${gold_squadron} INDEX2 {{standing_by_2}}

假设您已将 Loops.py python 文件正确导入为库,该循环将按预期工作。然而,我正在寻找的语法大致如下:

*** Variables ***
${gold_squadron} =  Gold
${red_squadron} =   Red

*** Test Cases ***
Test For Loop
    For Loop    IN RANGE    0   1   INDEX0
    ...     \\  For Loop    IN RANGE    1   6   INDEX1
    ...     \\  \\  {standing_by}=      Standing By Red Leader
    ...     \\  \\  Run Keyword If      INDEX1 == 1     Log to Console  ${red_squadron} Leader Standing By
    ...     \\  \\  Run Keyword Unless  INDEX1 == 1     Log to Console  ${red_squadron} INDEX1 {{standing_by}}
    ...     \\  For Loop    IN RANGE    1   6   INDEX2
    ...     \\  \\  {standing_by_2}=    Standing By Gold Leader
    ...     \\  \\  Run Keyword If      INDEX2 == 1     Log to Console  ${gold_squadron} Leader Standing By
    ...     \\  \\  Run Keyword Unless  INDEX2 == 1     Log to Console  ${gold_squadron} INDEX2 {{standing_by_2}}

对于那些不想撕毁 Robot Framework 基本代码的人(不推荐,这很痛苦),For Loops 通常不能嵌套在 Robot Framework 中的原因是因为在基本级别,关键字和 For Loops是两个完全不同的对象。一些关键字被编码以便它们可以使用其他关键字(如“运行关键字”),但 For 循环不是这样编码的。如果有人能想出一种方法来更改我的 For 循环的语法,那将使最终结果更直观地用于刚接触 Robot Framework 的人。

需要说明的是,如果从示例中看不清楚,我可以使用来自测试用例和 For 循环外部的 Robot Framework 变量。我问的是在 For 循环本身中创建它们。

最佳答案

我终于明白了。这是更正后的代码。

from robot.libraries.BuiltIn import BuiltIn


class Loops(object):

    def __init__(self):
        self.selenium_lib = BuiltIn().get_library_instance('ExtendedSelenium2Library')
        self.internal_variables = {}

    def for_loop(self, loop_type, start, end, index_var, *keywords):
        #   Format the keywords
    keywords = self._format_loop(*keywords)

        #   Clean out the internal variables from previous iterations
        self.internal_variables = {}

        #   This is the actual looping part
        for loop_iteration in range(int(start), int(end)):
            keyword_set = self._index_var_swap(loop_iteration, index_var, *keywords)
            #   If it's a one-keyword list with no arguments, then I can use the fastest possible keyword to run it
            if len(keyword_set) == 1:
                BuiltIn().run_keyword(keyword_set)
            #   If it's a one-keyword list with arguments, then I can use a faster keyword to run it
            elif 'AND' not in keyword_set:
                BuiltIn().run_keyword(*keyword_set)
            #   If it's a multiple-keyword list, then I have to use Run Keywords
            else:
                BuiltIn().run_keywords(*keyword_set)

    def _format_loop(self, *keywords):
        keywords = list(keywords)   # I need to format the keywords as a list.
        changed = False             # Whether or not I changed anything in the previous iteration.
        index = 0                   # The item index I'm at in the list of keywords
        del_list = []               # The list of items I need to delete
        swap_list = []              # The list of items i need to swap to AND for the use of Run Keywords

        def _new_variable():
            #   Default to a variable declaration of 'name='
            t = 1
            #   If my variable declaration is 'name ='
            if x[-2:] == ' =':
                #   Reflect that in the value of t
                t = 2

            #   Count the number of cells until the end of the line
            length = self._deliminator_search(index, x, *keywords)

            if length == 3 and not BuiltIn().run_keyword_and_return_status("Keyword Should Exist", keywords[index + 1]):
                #   If I'm assigning a value to my variable
                self._assign_internal_variable(x[:-t], str(keywords[index + 1]))

            elif length == 3:
                #   If I'm assigning the result of a keyword without any arguments
                self._assign_internal_variable_to_keyword(keywords[index][:-t], str(keywords[index + 1]))
            else:
                #   If I'm assigning the result of a keyword with arguments
                self._assign_internal_variable_to_keyword(keywords[index][:-t], keywords[index + 1],
                                                          keywords[index + 2:index + (length - 1)])

            #   Add the variable declaration code to the delete list.
            del_list.extend(range(index - 1, index + length))

        #   For each argument
        for x in keywords:
            #   Format it to a string
            x = str(x)
            #   Assign new variables
            if x[-1:] == '=':
                _new_variable()

            #   If the previous element was not changed...
            if not changed:
                #   If the current item is not the last one on the list...
                if x != len(keywords) - 1:
                    #   If the current item is a deliminator...
                    if x == '\\':
                        #   If the next item is a deliminator, delete this item and set changed to True
                        if keywords[int(index) + 1] == '\\':
                            del_list.append(index)
                            changed = True
                        #   If the next item is not a deliminator...
                        else:
                            #   If this isn't the first deliminator on the list, swap it to an 'AND'
                            if index != 0:
                                swap_list.append(index)
                                changed = True
                            #   If this deliminator is in position index=0, just delete it
                            else:
                                del_list.append(index)
                                changed = True
                    #   If the current element is not a deliminator, then I don't need to touch anything.
                #   If the current element is the last one, then I don't need to touch anything
            #   If the previous element was changed, then I don't need to "change" this one...
            elif changed:
                changed = False
                #   ...but if it's a deliminator then I do need to set it up for the inner for loop it means.
                if keywords[index] == '\\':
                    keywords[index] = keywords[index]*2
            index = index + 1   # Advance the index

        # These actually do the swapping and deleting
        for thing in swap_list:
            keywords[thing] = 'AND'
        del_list.reverse()
        for item in del_list:
            del keywords[item]

        # I also need to activate my variables for this set of keywords to run.
        keywords = self._activate_variables(*keywords)

        return keywords

    @staticmethod
    def _index_var_swap(loop_iteration, index_var, *keywords):
        #   Format the keywords as a list for iteration
        keywords = list(keywords)
        index = 0
        #   For every line in keywords
        for line in keywords:
            #   Replace all instances of the index_var in the string with the loop iteration as a string
            keywords[index] = str(line).replace(str(index_var), str(loop_iteration))
            index = index + 1
        return keywords

    def _assign_internal_variable(self, variable_name, assignment):
        # This keyword works like any other keyword so that it can be activated by BuiltIn.run_keywords
        self.internal_variables[variable_name] = assignment

    def _assign_internal_variable_to_keyword(self, variable_name, keyword, *arguments):
        # Uses assign_internal_variable to simplify code.
        # BuiltIn().log_to_console(BuiltIn().run_keyword(keyword, *arguments))
        self._assign_internal_variable(variable_name, BuiltIn().run_keyword(keyword, *arguments))
        # BuiltIn().log_to_console(self.internal_variables[variable_name])

    def _activate_variables(self, *keywords):
        #   Initialize variables
        keywords = list(keywords)   # Cast keywords as a List
        index = 0                   # The index of the keyword I'm looking at

        #   For each keyword
        for keyword in keywords:
            keyword = str(keyword)  # Cast keyword as a String
            assignment = False      # Whether or not the found variable name is in a variable assignment
            for key in self.internal_variables.keys():
                key = str(key)      # Cast key as a String
                #   If I can find the key in the keyword and it's not an assignment...
                if keyword.find(key) > -1 and not assignment:
                    #   ...replace the text of the key in the keyword.
                    keywords[index] = keyword.replace(str(key), str(self.internal_variables[key]))
                #   If the keyword I'm looking at is an assignment...
                if keyword.lower() == 'assign internal variable'\
                        and keyword.lower() != 'assign internal variable to keyword':
                    #   ...then my next keyword is going to definitely be a known variable, so I don't want to touch it.
                    assignment = True
                #   If the keyword I'm looking at is not an assignment...
                else:
                    #   ...set assignment to False just in case the previous one happened to be an assignment.
                    assignment = False
            index = index + 1   # Advance the index
            #   NOTE: Replaces the EXACT text, even if it's in another keyword or variable, so be very careful
        return keywords     # Return the list of keywords to be used in the format loop

    @staticmethod
    def _deliminator_search(start, keyword, *keywords):
        index = 0
        while keyword != '\\' and keyword != '\\\\':
            keyword = keywords[int(start) + index]
            index = index + 1
        return index

下面是测试它的代码:

*** Variables ***
${blue_squadron} =      Blue
${gold_squadron} =      Gold
${green_squadron} =     Green
${red_squadron} =       Red

*** Test Cases ***
Test For Loop
    For Loop    IN RANGE    0   1   INDEX0
    ...     \\  For Loop    IN RANGE    1   6   INDEX1
    ...     \\  \\  {standing_by}=      standing by
    ...     \\  \\  Run Keyword If      INDEX1 == 1     Log to Console  This is ${red_squadron} Leader standing by
    ...     \\  \\  Run Keyword Unless  INDEX1 == 1     Log to Console  ${red_squadron} INDEX1 {standing_by}
    ...     \\  For Loop    IN RANGE    1   6   INDEX2
    ...     \\  \\  standing_by_2 =     standing by
    ...     \\  \\  Run Keyword If      INDEX2 == 1     Log to Console  This is ${gold_squadron} Leader standing by
    ...     \\  \\  Run Keyword Unless  INDEX2 == 1     Log to Console  ${gold_squadron} INDEX2 standing_by_2
    ...     \\  For Loop    IN RANGE    1   6   INDEX3
    ...     \\  \\  standing_by_3=      Get Blue Squadron
    ...     \\  \\  Run Keyword If      INDEX3 == 1     Log to Console  This is ${blue_squadron} Leader standing by
    ...     \\  \\  Run Keyword Unless  INDEX3 == 1     Log to Console  ${blue_squadron} INDEX3 standing_by_3
    ...     \\  For Loop    IN RANGE    1   6   INDEX4
    ...     \\  \\  standing_by_4 =     Get Green Squadron   null input
    ...     \\  \\  Run Keyword If      INDEX4 == 1     Log to Console  This is ${green_squadron} Leader standing by
    ...     \\  \\  Run Keyword Unless  INDEX4 == 1     Log to Console  ${green_squadron} INDEX4 standing_by_4

*** Keywords ***
Get Blue Squadron
    [Return]    standing by

Get Green Squadron
    [Arguments]     ${null_input}
    [Return]        standing by

要对此解决方案进行一些说明,我不需要变量采用特定格式。它可以,如果你想进一步准确地指定你的变量是什么而没有歧义,但这不是必需的。我宁愿在此类程序中保留更多选项。

关于python - 如何在这个可嵌套的 For 循环中实现 Robot Framework 风格的变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45290821/

相关文章:

ruby 遍历数组并提取元素

css - Robot Framework - 是否所有图像都经过测试?

python - 将 CSV 从两行转换为带有时间戳的列

python - PyTorch 梯度对于 CUDA 和 CPU 具有不同的形状

python - 绘制多维特征的散点图

python - 提取列表中找到的子列表的索引

javascript - 从配置读取的多个 FOR var 循环问题

javascript - Canvas 仅在循环结束后重绘

xpath - 使用机器人框架查找属性为 'selected' 的选项标签

python - 使用 robotframework api 从 python 执行机器人关键字