自動測試LeetCode用例方法


自動合並測試LeetCode解題方法

在leetcode.com上答題,Run Code或者Sumbmit通常要Spending一會,如果提交一次就Accepted那還好,如果反復Wrong Answer,很耽誤時間。為了調高效率和減少挫折(來回提交,一直Wrong Answer倍受打擊),我采取在本地Jupyter notebook上coding,測試通過后再提交的方式。本篇主要介紹自動測試的方法。

1. LeetCode模式

打開leetcode一道題后,左側有一個Description選項卡,該選項卡面板內有題目、命題描述、還列舉了多個Example,每個Example都有一個Input和一個Output,有的還會有一個Explanation;右側是coding區,coding區自動生成 class Solution模板。

class Solution: def method(self,**kwargs):

例如,第1071號題Decription區的Example內容如下

1071號題coding區初始化模板如下

2. Jupyter notebook編寫代碼

將LeetCode中coding模板內容復制到jupyter notebook中,編寫解題代碼。假如1071號題的代碼如下。

class Solution:
    def gcdOfStrings(self, str1: str, str2: str) -> str:
        if len(str1) < len(str2):
            tmp, str2, str1 = str2, str1, tmp
        len_str2, len_str1 = len(str2), len(str1)
        
        for i in range(1,len_str2+1):
            d2,m2 = divmod(len(str2),i)
            if m2==0 and str2==str2[:d2]*i:
                d1, m1 = divmod(len(str1),d2)
                if m1==0 and str1==str2[:d2]*d1:
                    return str2[:d2]
        return ""

3. 手動逐一測試

手動逐一測試,就是編寫好method內容后,實例化Solution,然后復制Decription中每個Example中的Input,傳入實例的方法,執行后觀察結果是否與Example中Output一致。

# 實列化類
s = Solution()
# 測試Example中的用例1
# Input: str1 = "ABCABC", str2 = "ABC"
# Output: "ABC"

str1 = "ABCABC"
str2 = "ABC"
s.gcdOfStrings(str1,str2)

'ABC'
# 測試Example中的用例2
# Input: str1 = "ABABAB", str2 = "ABAB"
# Output: "AB"

str1 = "ABABAB"
str2 = "ABAB"
s.gcdOfStrings(str1,str2)
'AB'
# 測試Example中的用例3
# Input: str1 = "LEET", str2 = "CODE"
# Output: ""

str1 = "LEET"
str2 = "CODE"
s.gcdOfStrings(str1,str2)
''
# 測試Example中的用例4
# Input: str1 = "ABCDEF", str2 = "ABC"
# Output: ""

str1 = "ABCDEF"
str2 = "ABC"
s.gcdOfStrings(str1,str2)
''

4. 自動合並測試

4.1 復制測試用例

從LeetCode問題頁面復制所有Example內容,賦值給str_Examples。這里用3對雙引號,內容可以原封不動。Example描述有規律,而且每個題的Example描述都是同樣的模式,所以我們可以定義一個通用方法來獲取測試用例。

str_Examples ="""Example 1:

Input: str1 = "ABCABC", str2 = "ABC"
Output: "ABC"
Example 2:

Input: str1 = "ABABAB", str2 = "ABAB"
Output: "AB"
Example 3:

Input: str1 = "LEET", str2 = "CODE"
Output: ""
Example 4:

Input: str1 = "ABCDEF", str2 = "ABC"
Output: ""
"""

4.2 定義獲取用例方法

  • 目標是把Input、Output提取出來,並成對組織好。
  • 通過觀察,可采用正則化split、字符串split、replace、strip等方法提取Input、Output內容
  • 輸入參數和輸出存入list,每個用例可能有多個參數,所以用例參數用dict裝載,方便調用測試方法時用**kwargs傳入。

注意:因為輸入用例文檔用的""""",引號里內容還含有雙引號",split后會把里面的引號當做字符內容,導致結果出乎意料。

def get_test_cases(str_examples):
    import re
    params =re.split('Example.+?:',str_examples.replace('\n','').replace(' ',''))[1:]
    cases_arg, cases_ans = [], []
    for param in params:
        kawargs = {}
        args_,ans = param.split('Output:')
        args_ = args_.replace('Input:','').strip().split(',')
        cases_ans.append(ans)
        for arg in args_:
            key,val = arg.split("=")
            kawargs[key] = val
        cases_arg.append(kawargs)
    return zip(cases_arg, cases_ans)

4.3 定義自動測試方法

  • leetcode每個題生成的默認代碼類名都是Solution,但是函數名根據題目來取,因題而異。dir(Solution())能夠獲取Solution的方法名列表,前面部分是內建方法名,最后一個是自定義的解題方法(這里考慮只有一個自建函數)。用dir(Solution())[-1]獲取函數名,然后使用getattr獲取實例方法f。
  • 傳入參數為get_usecases返回的結果——zip打包的測試用例輸入和輸出,for迭代或取每一個測試用例的輸入參數kwargs(Example中的Input)和輸入answer(Example中的Output)。
  • 迭代測試用例,斷言 f(**kwargs) == answer 。如果每個斷言成功,返回'Accepted',否則try...except捕獲斷言失敗,返回'Wrong Answer'。
def auto_test(test_cases):
    f = getattr(Solution(), dir(Solution())[-1])
    try:
        for kwargs, answer in test_cases:
            print(kawargs,answer)
            assert f(**kwargs) == answer        
        return 'Accepted'
    except Exception as e:  
        print(e)
        return 'Wrong Answer'

4.4 what?! 神奇問題bug ?

  • 先調用get_test_cases獲取測試用例,然后將結果傳入auto_test進行測試,結果是'Wrong Answer';
  • 直接調用auto_test(get_test_cases()),結果也是'Wrong Answer'。
  • 然而調用get_test_cases獲取測試用例test_cases后,先迭代一遍test_cases,再將test_cases傳入auto_test,結果為'Accepted'。
test_cases = get_test_cases(str_Examples)
auto_test(test_cases)
name 'kawargs' is not defined

'Wrong Answer'
auto_test(get_test_cases(str_Examples))
name 'kawargs' is not defined

'Wrong Answer'
for input_args,out in test_cases:
    print(input_args, out)
auto_test(test_cases)    
{'str1': '"ABABAB"', 'str2': '"ABAB"'} "AB"
{'str1': '"LEET"', 'str2': '"CODE"'} ""
{'str1': '"ABCDEF"', 'str2': '"ABC"'} ""


'Accepted'

4.5 編程陷阱

陷阱1:"""doc""",三對雙引號中內容帶引號,沒去除引號被當做字符內容,導致Solution算法“失靈”。

doc = """ABC "efg" HIGK"""  # efg帶引號
doc.split()                 # 分割后'"efg"'而不是'efg',不注意還看不出來
['ABC', '"efg"', 'HIGK']

避坑辦法:.replace('"','')去除內容引號

def get_test_cases(str_examples):
    import re
    params =re.split('Example.+?:',str_examples.replace('\n','').replace(' ',''))[1:]
    cases_arg, cases_ans = [], []
    for param in params:
        kawargs = {}
        args_,ans = param.split('Output:')
        args_ = args_.replace('Input:','').strip().split(',')
        # ans.replace('"','')
        cases_ans.append(ans.replace('"','')) 
        for arg in args_:
            key,val = arg.split("=")
             # val.replace('"','')
            kawargs[key] = val.replace('"','')
        cases_arg.append(kawargs)
    return zip(cases_arg, cases_ans)

陷阱2:try...except不僅捕獲assert斷言成功失敗,在try中print(kawargs,answer),‘kawargs’寫錯也導致進入except塊。

避坑辦法:try...except Exception as e打印異常,debug錯誤

# 捕獲except異常並不等於assert斷言失敗
try:
    print(what)
    assert 1==1
except Exception as e:
    print(e)
name 'what' is not defined
def auto_test(test_cases):
    f = getattr(Solution(), dir(Solution())[-1])
    try:
        for kwargs, answer in test_cases:
            print(kwargs,answer)
            assert f(**kwargs) == answer        
        return 'Accepted'
    except Exception as e:  
        print(e)
        return 'Wrong Answer'

陷阱2:zip只能迭代一次,looping后被釋放,外部迭代一次后再傳入auto_test,不進入for迭代,造成外部迭代后返回正確'Accepted'的假象。

val =[1,2,3]
key = ['a','b','c']
ziped = zip(key,val)
print('----first iter zip---------')
for (key,val) in ziped:    
    print(key,val)
    
print('----second iter zip---------')
for (key,val) in ziped:
    
    print(key,val)
----first iter zip---------
a 1
b 2
c 3
----second iter zip---------

避坑辦法:頭一次掉這坑,刷新zip認知,記住它。

4.6 再次測試

test_cases = get_test_cases(str_Examples)
auto_test(test_cases)
{'str1': 'ABCABC', 'str2': 'ABC'} ABC
{'str1': 'ABABAB', 'str2': 'ABAB'} AB
{'str1': 'LEET', 'str2': 'CODE'} 
{'str1': 'ABCDEF', 'str2': 'ABC'} 

'Accepted'
auto_test(get_test_cases(str_Examples))
{'str1': 'ABCABC', 'str2': 'ABC'} ABC
{'str1': 'ABABAB', 'str2': 'ABAB'} AB
{'str1': 'LEET', 'str2': 'CODE'} 
{'str1': 'ABCDEF', 'str2': 'ABC'} 

'Accepted'

5. 總結

step1: 在Jupyter notebook中編寫LeetCode問題解答代碼
class Solution: def method(self,**kwargs):
step2: 復制LeetCode中Example內容復制給變量str_Examples
step3: 使用get_test_cases獲取測試用例
step4: 調用auto_test進行自動測試,返回'Accepted'所有測試用例通過,'Wrong Answer'表示存在沒能通過的用例。

LeetCode問題Description中所有Example測試通過,即Run Code結果為'Accepted',並不代表Submit后的結果一定會為'Accepted'。相反,Run Code結果為'Wrong Answer',那么Submit后結果一定是'Wrong Answer'。因為Description中的Example只是部分示例,是Submit后驗證的測試用例集合的子集。

def get_test_cases(str_examples):
    import re
    params =re.split('Example.+?:',str_examples.replace('\n','').replace(' ',''))[1:]
    cases_arg, cases_ans = [], []
    for param in params:
        kawargs = {}
        args_,ans = param.split('Output:')
        args_ = args_.replace('Input:','').strip().split(',')
        # ans.replace('"','')
        cases_ans.append(ans.replace('"','')) 
        for arg in args_:
            key,val = arg.split("=")
             # val.replace('"','')
            kawargs[key] = val.replace('"','')
        cases_arg.append(kawargs)
    return zip(cases_arg, cases_ans)

def auto_test(test_cases):
    f = getattr(Solution(), dir(Solution())[-1])
    try:
        for kwargs, answer in test_cases:
            print(kwargs,answer)
            assert f(**kwargs) == answer        
        return 'Accepted'
    except Exception as e:  
        print(e)
        return 'Wrong Answer'


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM