如何在此可嵌套For循环中实现Robot Framework样式变量?

时间:2017-07-24 22:04:26

标签: python loops robotframework

我在Robot Framework中看到了很多“可嵌套”For循环,主要是创建一个带有For循环的关键字,然后在另一个For循环中调用该关键字。我使用Python 2.7.13创建了一个可嵌套的For循环,但由于它主要使用运行关键字语法,因此我无法使用Robot Framework样式语法创建变量(例如${variable_name}= My Keyword)。对于记录,这是在BuiltIn Robot Framework库下运行关键字,它使用以下语法:

Run Keywords    Keyword1    arg11   arg12   AND     Keyword2    arg21   arg22

同样地,它可以这样写:

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

它通常不支持在其中创建变量。但是,我使用Run关键字作为可嵌套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的新关键字及其合作伙伴将内部变量分配给关键字。但是,这会根据我的喜好稍微改变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循环是两个完全不同的对象。一些关键字被编码,以便他们可以使用其他关键字(如“运行关键字”),但For Loops不会以这种方式编码。如果有人能找到一种方法来改变我的For循环的语法,那么最终结果将更加直观地用于刚刚来自Robot Framework的人。

为了清楚起见,如果从示例中不清楚,我可以使用来自Test Case和For Loop外部的Robot Framework变量。我问的是在For Loop本身创建它们。

1 个答案:

答案 0 :(得分:1)

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

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

为了对此解决方案添加一些说明,我不需要变量采用特定格式。如果您希望更准确地指定变量是什么而没有歧义,它可以是,但它不是必需的。我宁愿在这种程序中留下更多选择。