RobotFramework之Collections

weixin_33918357發表於2017-04-30

背景

繼續學習RobotFramework,這次看的是Collections的原始碼。

Collections庫是RobotFramework用來處理列表和字典的庫,官方文件是這麼介紹的

A test library providing keywords for handling lists and dictionaries.

Append To List

示例程式碼

*** Settings ***
Library  Collections

*** Test Cases ***
TestCase001
    ${l1}       create list     a       b
      append to list  ${l1}     1       2
      log   ${l1}

執行結果:

KEYWORD BuiltIn . Log ${l1}
Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170422 19:11:52.624 / 20170422 19:11:52.624 / 00:00:00.000
19:11:52.624    INFO    [u'a', u'b', u'1', u'2']

原始碼

    def append_to_list(self, list_, *values):
        for value in values:
            list_.append(value)

Combine List

示例程式碼

*** Settings ***
Library  Collections

*** Test Cases ***
TestCase001
    ${l1}       create list     a       b
    ${l2}       create list     1       2
    ${l}        combine lists  ${l1}        ${l2}
    log  ${l}

執行結果

KEYWORD ${l} = Collections . Combine Lists ${l1}, ${l2}
Documentation:  
Combines the given lists together and returns the result.
Start / End / Elapsed:  20170422 19:15:52.086 / 20170422 19:15:52.087 / 00:00:00.001
19:15:52.087    INFO    ${l} = [u'a', u'b', u'1', u'2']

原始碼

    def combine_lists(self, *lists):
        ret = []
        for item in lists:
            ret.extend(item)
        return ret

說明

原始碼中的extend方法和append方法是不一樣的,append方法是追加一個元素,extend方法是追加一個列表的內容,如果用append方法,則結果會變成如下情況:

l1 = [1,2,3]
l2 = [4,5,6]
l1.append(l2)  # ==>[1,2,3,[4,5,6]]
l1.extend(l2)  # ==>[1,2,3,,4,5,6]

所以合併列表的時候使用extend而不是append

Count Values In List

計算某一個值在列表中重複的次數

示例

*** Settings ***
Library  Collections

*** Test Cases ***
TestCase001
    ${l1}       create list  1      2       3       4       3
    ${count}      count values in list          ${l1}     3
    log  ${count}

執行結果

KEYWORD ${int} = Collections . Count Values In List ${l1}, 3
Documentation:  
Returns the number of occurrences of the given value in list.
Start / End / Elapsed:  20170422 19:30:33.807 / 20170422 19:30:33.807 / 00:00:00.000
19:30:33.807    INFO    ${int} = 2

原始碼

    def count_values_in_list(self, list_, value, start=0, end=None):
        return self.get_slice_from_list(list_, start, end).count(value)
        
    def get_slice_from_list(self, list_, start=0, end=None):
        start = self._index_to_int(start, True)
        if end is not None:
            end = self._index_to_int(end)
        return list_[start:end]
        
    def _index_to_int(self, index, empty_to_zero=False):
        if empty_to_zero and not index:
            return 0
        try:
            return int(index)
        except ValueError:
            raise ValueError("Cannot convert index '%s' to an integer." % index)

說明

這裡方法是預設從第一位開始計算,如果有需要,可以輸入一個區間,比如指定起始位置和結束位置,比如如下程式碼

*** Settings ***
Library  Collections

*** Test Cases ***
TestCase001
    ${l1}       create list  1      2       3       4       3       3
    ${count}      count values in list          ${l1}     3     3       5
    log  ${count}

執行結果就是1

Dictionaries Should Be Equal

示例程式碼

*** Settings ***
Library  Collections

*** Test Cases ***
TestCase001
    ${dict1}     create dictionary      a=1     b=2
     ${dict2}   create dictionary       a=1     b=3
    dictionaries should be equal  ${dict1}      ${dict2}

執行結果

KEYWORD Collections . Dictionaries Should Be Equal ${dict1}, ${dict2}
Documentation:  
Fails if the given dictionaries are not equal.
Start / End / Elapsed:  20170422 19:49:36.865 / 20170422 19:49:36.866 / 00:00:00.001
19:49:36.866    FAIL    Following keys have different values:
Key b: 2 != 3

原始碼

    def dictionaries_should_be_equal(self, dict1, dict2, msg=None, values=True):
        keys = self._keys_should_be_equal(dict1, dict2, msg, values)
        self._key_values_should_be_equal(keys, dict1, dict2, msg, values)
        
    def _keys_should_be_equal(self, dict1, dict2, msg, values):
        keys1 = self.get_dictionary_keys(dict1)
        keys2 = self.get_dictionary_keys(dict2)
        miss1 = [unic(k) for k in keys2 if k not in dict1]
        miss2 = [unic(k) for k in keys1 if k not in dict2]
        error = []
        if miss1:
            error += ['Following keys missing from first dictionary: %s'
                      % ', '.join(miss1)]
        if miss2:
            error += ['Following keys missing from second dictionary: %s'
                      % ', '.join(miss2)]
        _verify_condition(not error, '\n'.join(error), msg, values)
        return keys1
        
    def _key_values_should_be_equal(self, keys, dict1, dict2, msg, values):
        diffs = list(self._yield_dict_diffs(keys, dict1, dict2))
        default = 'Following keys have different values:\n' + '\n'.join(diffs)
        _verify_condition(not diffs, default, msg, values)
        
    def _yield_dict_diffs(self, keys, dict1, dict2):
        for key in keys:
            try:
                assert_equal(dict1[key], dict2[key], msg='Key %s' % (key,))
            except AssertionError as err:
                yield unic(err)

說明

本來Python中比較兩個字典是否相等只要用==就行了,這裡為了能夠精確的報錯,遍歷了所有的key和value來做比較。

Dictionary Should Contain Item

示例程式碼

*** Settings ***
Library  Collections

*** Test Cases ***
TestCase001
    ${dict1}     create dictionary      a=1     b=2
    dictionary should contain item  ${dict1}        a       1

結果

KEYWORD Collections . Dictionary Should Contain Item ${dict1}, a, 1
Documentation:  
An item of key``/``value must be found in a `dictionary`.
Start / End / Elapsed:  20170422 19:58:37.086 / 20170422 19:58:37.087 / 00:00:00.001

原始碼

    def dictionary_should_contain_item(self, dictionary, key, value, msg=None):
        self.dictionary_should_contain_key(dictionary, key, msg)
        actual, expected = unic(dictionary[key]), unic(value)
        default = "Value of dictionary key '%s' does not match: %s != %s" % (key, actual, expected)
        _verify_condition(actual == expected, default, msg)
        
    def dictionary_should_contain_key(self, dictionary, key, msg=None):
        default = "Dictionary does not contain key '%s'." % key
        _verify_condition(key in dictionary, default, msg)

說明

判斷一個k-v是否在一個字典裡,同樣是通過遍歷原有字典的方式來處理,只不過這裡傳值必須是按照示例程式碼中那樣傳遞,不能傳入a=1或者一個字典。

Dictionary Should Contain Sub Dictionary

示例

*** Settings ***
Library  Collections

*** Test Cases ***
TestCase001
    ${dict1}     create dictionary      a=1     b=2
    ${dict2}        create dictionary  a=1
    Dictionary Should Contain Sub Dictionary    ${dict1}        ${dict2}

結果

KEYWORD Collections . Dictionary Should Contain Sub Dictionary ${dict1}, ${dict2}
Documentation:  
Fails unless all items in dict2 are found from dict1.
Start / End / Elapsed:  20170422 20:09:41.864 / 20170422 20:09:41.865 / 00:00:00.001

原始碼

    def dictionary_should_contain_sub_dictionary(self, dict1, dict2, msg=None,
                                                 values=True):
        keys = self.get_dictionary_keys(dict2)
        diffs = [unic(k) for k in keys if k not in dict1]
        default = "Following keys missing from first dictionary: %s" \
                  % ', '.join(diffs)
        _verify_condition(not diffs, default, msg, values)
        self._key_values_should_be_equal(keys, dict1, dict2, msg, values)

說明

上一個方法只能傳入單一的k-v,使用的侷限性比較大,這個方法就可以支援傳入一個字典

Get Dictionary Items

示例

*** Settings ***
Library  Collections

*** Test Cases ***
TestCase001
    ${dict1}     create dictionary      a=1     b=2
    ${dict2}        create dictionary  a=1
    ${dict3}        Get Dictionary Items    ${dict1}

執行結果

KEYWORD ${dict3} = Collections . Get Dictionary Items ${dict1}
Documentation:  
Returns items of the given dictionary.
Start / End / Elapsed:  20170424 00:39:49.766 / 20170424 00:39:49.766 / 00:00:00.000
00:39:49.766    INFO    ${dict3} = [u'a', u'1', u'b', u'2']

原始碼

    def get_dictionary_items(self, dictionary):
        ret = []
        for key in self.get_dictionary_keys(dictionary):
            ret.extend((key, dictionary[key]))
        return ret

說明

這個方法就是把字典裡面的成員以列表的形式返回出來,具體應用場景還不是很確定

Get Dictionary Values

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${dict}     create dictionary   a=1     b=2
    ${rst}      get from dictionary     ${dict}     a
    log   ${rst}

執行結果

Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170428 19:45:59.199 / 20170428 19:45:59.200 / 00:00:00.001
19:45:59.200    INFO    1

原始碼

def get_from_dictionary(self, dictionary, key):
        try:
            return dictionary[key]
        except KeyError:
            raise RuntimeError("Dictionary does not contain key '%s'." % key)

說明

原始碼其實非常簡單,傳入一個字典和一個key,然後就返回這個key對應的值

Get From List

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${lst}      create list  1      2       3
    ${new_lst}  get from list  ${lst}       0
    log     ${new_lst}

執行結果

KEYWORD BuiltIn . Log ${new_lst}
Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170428 19:50:42.533 / 20170428 19:50:42.533 / 00:00:00.000
19:50:42.533    INFO    1

原始碼

def get_from_list(self, list_, index):
        try:
            return list_[self._index_to_int(index)]
        except IndexError:
            self._index_error(list_, index)

說明

執行方法就是獲取列表和索引,然後返回列表的索引,值得一提的是,不論你傳入的值是str型別還是int型別,都會被python程式碼轉成int型別,所以原始碼中${new_lst} get from list ${lst} 0這樣寫,執行結果也是一樣的。附上轉換的原始碼。

def _index_to_int(self, index, empty_to_zero=False):
        if empty_to_zero and not index:
            return 0
        try:
            return int(index)
        except ValueError:
            raise ValueError("Cannot convert index '%s' to an integer." % index)

Get Index From List

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${lst}      create list  1      2       3       4       5       4
    ${rst}      get index from list  ${lst}       3
    log     ${rst}

執行結果

KEYWORD BuiltIn . Log ${rst}
Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170428 19:59:05.532 / 20170428 19:59:05.532 / 00:00:00.000
19:59:05.532    INFO    2

原始碼

def get_index_from_list(self, list_, value, start=0, end=None):
        if start == '':
            start = 0
        list_ = self.get_slice_from_list(list_, start, end)
        try:
            return int(start) + list_.index(value)
        except ValueError:
            return -1

說明
原始碼中的self.get_slice_from_list方法是一個切片方法,在Get Index From List中還可以傳入兩個引數,start表示列表的開始索引,end表示列表結束的索引,如果沒有給,預設就是整個列表來取索引結果。

Get Match Count

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${count}        get match count     aaabbcc      a
    log     ${count}

執行結果

KEYWORD BuiltIn . Log ${count}
Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170428 20:08:08.699 / 20170428 20:08:08.700 / 00:00:00.001
20:08:08.700    INFO    3

原始碼

def get_match_count(self, list, pattern, case_insensitive=False,
                        whitespace_insensitive=False):
        return len(self.get_matches(list, pattern, case_insensitive,
                                    whitespace_insensitive))
                                    
def _get_matches_in_iterable(iterable, pattern, case_insensitive=False,
                             whitespace_insensitive=False):
    if not is_string(pattern):
        raise TypeError("Pattern must be string, got '%s'." % type_name(pattern))
    regexp = False
    if pattern.startswith('regexp='):
        pattern = pattern[7:]
        regexp = True
    elif pattern.startswith('glob='):
        pattern = pattern[5:]
    matcher = Matcher(pattern,
                      caseless=is_truthy(case_insensitive),
                      spaceless=is_truthy(whitespace_insensitive),
                      regexp=regexp)
    return [string for string in iterable
            if is_string(string) and matcher.match(string)]

說明

這個方法是返回一個字元在字串中重複的次數。

原始碼中的第二個方法才是真正的方法,Get Match Count方法是非常強大的,可以做普通匹配,也可以做正則匹配,首先,傳入的必須是字串,否則報錯,然後判定傳入的條件是否帶有正則,如果傳入的條件是regexp=開頭的,表示要使用正則匹配,否則就是普通查詢。

第三個參數列示是否忽略大小寫,第四個參數列示是否忽略空格。

Get Matchs

此方法與上面一個方法呼叫的是一樣的底層方法,請參考上面的內容。

Get Slice From List

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${lst}      create list  1  2   3   4   5   6   7   8
    ${slice_list}   get slice from list  ${lst}     3   6
    log  ${slice_list}

執行結果

KEYWORD BuiltIn . Log ${slice_list}
Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170428 20:20:00.780 / 20170428 20:20:00.780 / 00:00:00.000
20:20:00.780    INFO    [u'4', u'5', u'6']

原始碼

def get_slice_from_list(self, list_, start=0, end=None):
        start = self._index_to_int(start, True)
        if end is not None:
            end = self._index_to_int(end)
        return list_[start:end]

說明

原始碼還是比較容易看懂的,如果開始和結束不傳值,那麼就是返回原來的列表,否則就返回切片的列表。

Insert into list

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${lst}      create list  1  2   3   4   5   6   7   8
    insert into list    ${lst}      0       a
    log  ${lst}

執行結果

KEYWORD BuiltIn . Log ${lst}
Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170428 20:27:02.736 / 20170428 20:27:02.737 / 00:00:00.001
20:27:02.737    INFO    [u'a', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8']

原始碼

def insert_into_list(self, list_, index, value):
        list_.insert(self._index_to_int(index), value)

說明

呼叫的就是Python中列表的insert方法。如果傳入的索引大於列表的長度,那麼值會預設插入到列表的最後一位,如果傳入的是負數,比如傳入-1,那麼就會在列表的倒數第二位加入該元素。

根據實現的Python程式碼可以知道,傳入的索引是int或者str型別都可以,實現的時候會強制轉為int型別。

關於插入索引大於列表長度的問題,可以參考以下程式碼

Python 2.7.10 (default, Feb  6 2017, 23:53:20)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [1,2,3,4,5]
>>> a
[1, 2, 3, 4, 5]
>>> a.insert(9, 'a')
>>> a
[1, 2, 3, 4, 5, 'a']

Keep In Dictionary

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${dict}     create dictionary  a=1  b=2     c=3     d=4
    keep in dictionary  ${dict}     a   b
    log  ${dict}

執行結果

KEYWORD BuiltIn . Log ${dict}
Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170428 20:47:47.528 / 20170428 20:47:47.529 / 00:00:00.001
20:47:47.529    INFO    {u'a': u'1', u'b': u'2'}

原始碼

def keep_in_dictionary(self, dictionary, *keys):
        remove_keys = [k for k in dictionary if k not in keys]
        self.remove_from_dictionary(dictionary, *remove_keys)
        
def remove_from_dictionary(self, dictionary, *keys):
        for key in keys:
            if key in dictionary:
                value = dictionary.pop(key)
                logger.info("Removed item with key '%s' and value '%s'." % (key, value))
            else:
                logger.info("Key '%s' not found." % key)   

說明

該方法是用來保留字典裡指定的key,原始碼也比較好理解,不過此處的註釋有問題,

Keeps the given keys in the dictionary and removes all other.
If the given key cannot be found from the dictionary, it is ignored.

第二句是說明如果傳入的key如果不在這個字典裡,那麼這個操作就會被忽略。但是實際上我們可以看到程式碼執行時並不是這樣的,第一個方法會把字典中與傳入的keys不匹配的key全部拿出來,呼叫第二個方法來全部從字典裡移除,也就是說傳入的key如果在字典裡不存在,那麼原字典就會變為一個空字典。

實際上的執行結果就是這樣

KEYWORD ${dict} = BuiltIn . Create Dictionary a=1, b=2, c=3, d=4
00:00:00.001KEYWORD Collections . Keep In Dictionary ${dict}, e
Documentation:  
Keeps the given keys in the dictionary and removes all other.
Start / End / Elapsed:  20170428 20:52:04.051 / 20170428 20:52:04.052 / 00:00:00.001
20:52:04.052    INFO    Removed item with key 'a' and value '1'.    
20:52:04.052    INFO    Removed item with key 'b' and value '2'.    
20:52:04.052    INFO    Removed item with key 'c' and value '3'.    
20:52:04.052    INFO    Removed item with key 'd' and value '4'.    
00:00:00.000KEYWORD BuiltIn . Log ${dict}
Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170428 20:52:04.052 / 20170428 20:52:04.052 / 00:00:00.000
20:52:04.052    INFO    {}

List Should Contain Sub List

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${lst1}     create list  1      2       3       4
    ${lst2}     create list  2      3
    list should contain sub list  ${lst1}       ${lst2}

執行結果

KEYWORD Collections . List Should Contain Sub List ${lst1}, ${lst2}
Documentation:  
Fails if not all of the elements in list2 are found in list1.

原始碼

def list_should_contain_sub_list(self, list1, list2, msg=None, values=True):
        diffs = ', '.join(unic(item) for item in list2 if item not in list1)
        default = 'Following values were not found from first list: ' + diffs
        _verify_condition(not diffs, default, msg, values)

List Should Not Contain Duplicates

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${lst1}     create list  1      2       3       4       2
    list should not contain duplicates      ${lst1}     2

執行結果

KEYWORD Collections . List Should Not Contain Duplicates ${lst1}, 2
Documentation:  
Fails if any element in the list is found from it more than once.
Start / End / Elapsed:  20170428 21:08:59.719 / 20170428 21:08:59.720 / 00:00:00.001
21:08:59.719    INFO    '2' found 2 times.  
21:08:59.720    FAIL    2

原始碼

def list_should_not_contain_duplicates(self, list_, msg=None):
        if not isinstance(list_, list):
            list_ = list(list_)
        dupes = []
        for item in list_:
            if item not in dupes:
                count = list_.count(item)
                if count > 1:
                    logger.info("'%s' found %d times." % (item, count))
                    dupes.append(item)
        if dupes:
            raise AssertionError(msg or
                                 '%s found multiple times.' % seq2str(dupes))

說明

該方法用於斷言某個元素在列表中只會出現一次,如果出現多次則報錯。

Pop From Dictionary

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${dict}     create dictionary  a=1      b=2     c=3
    ${newdict}      pop from dictionary     ${dict}     a
    log   ${newdict}
    log     ${dict}

執行結果

KEYWORD BuiltIn . Log ${dict}
Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170428 21:21:05.523 / 20170428 21:21:05.523 / 00:00:00.000
21:21:05.523    INFO    {u'b': u'2', u'c': u'3'}

原始碼

def pop_from_dictionary(self, dictionary, key, default=NOT_SET):
        if default is NOT_SET:
            self.dictionary_should_contain_key(dictionary, key)
            return dictionary.pop(key)
        return dictionary.pop(key, default)

說明

這裡的方法有一個可選引數default,是用來處理不存在的鍵值,如果pop的鍵值在字典裡不存在,那麼框架是會報錯處理的,但是如果給了一個default,那麼傳入的鍵值不存在時,會返回default的值。

Remove Duplicates

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${lst}      create list  1      2       3       4       1       2
    ${rst}      remove duplicates       ${lst}

執行結果

KEYWORD ${rst} = Collections . Remove Duplicates ${lst}
Documentation:  
Returns a list without duplicates based on the given list.
Start / End / Elapsed:  20170428 21:52:22.522 / 20170428 21:52:22.522 / 00:00:00.000
21:52:22.522    INFO    2 duplicates removed.   
21:52:22.522    INFO    ${rst} = [u'1', u'2', u'3', u'4']

原始碼

def remove_duplicates(self, list_):
        ret = []
        for item in list_:
            if item not in ret:
                ret.append(item)
        removed = len(list_) - len(ret)
        logger.info('%d duplicate%s removed.' % (removed, plural_or_not(removed)))
        return ret

說明

該方法是一個除重方法,可以吧列表中重複的元素移除。

Sort List

示例程式碼

*** Settings ***
Library     Collections

*** Test Cases ***
test1
    ${lst}      create list  1      2       3       4       1       2       and     dib
    sort list   ${lst}
    log  ${lst}

執行結果

KEYWORD BuiltIn . Log ${lst}
Documentation:  
Logs the given message with the given level.
Start / End / Elapsed:  20170428 22:13:57.288 / 20170428 22:13:57.288 / 00:00:00.000
22:13:57.288    INFO    [u'1', u'1', u'2', u'2', u'3', u'4', u'and', u'dib']

原始碼

def sort_list(self, list_):
        list_.sort()

說明

該方法呼叫了列表的sort方法,需要注意的是,如果原始列表還需要使用的話,最好先保留一份,否則原始資料就會被破壞。

Note that the given list is changed and nothing is returned. UseCopy List first, if you need to keep also the original order.

總結

Collections庫是一個非常簡單的庫,不過裡面包含了大部分的字典和列表的操作,本文僅僅覆蓋了一些關鍵字,還有一些重複的,或者一眼就能看出來怎麼用的關鍵字我就沒有寫了。通過這些示例程式碼,基本上能夠了解Robot Framework框架的使用方法,也能夠對Robot Framework框架的編寫方式和編寫思想有了一定的認識。

相關文章