这回就不直接贴代码而是讲讲接口测试框架中的一些技巧和suds模块
1.首先来讲讲suds,和request模块不同的是,suds模块不是只调用post和get这两种功能,而是根据接口的不同功能调用不同的模块。具体的可以使用suds.client.Client(接口url),一般来讲具体写成:
from suds.client import Client class Http_Driver(): def suds_driver(self,url,data,method): clients = Client(url) Method = eval("clients.service.{0}({1})".format(method,data)) result = {} result["retInfo"] = str(Method.retInfo) result["retCode"] = str(Method.retCode) return result
这是我这回重写框架中的一部分,将suds模块重新封装,方便调用不同模块,而不是死板的写成固定的某个功能然后if判断调用,既不美观又闲的代码臃肿。
2.然后再来讲讲sql语法的一些应用,在python代码中sql语句是以str形式存放的,自然也就能使用format模块来灵活运用
sql = 'SELECT * FROM sms_db_{}.t_mvcode_info_{} WHERE Fmobile_no = "{}"'.format(test_dict["sql_package"],test_dict["sql_sheet"],test_dict["moblie"])
这里的test_dict是我接口中返回的字典数据,根据返回的数据不同,模块的灵活性大大增强。
3.replace模块。在接口测试中,因为经常会出现只能使用一次的数据。为了方便测试,减少反复修改用例的麻烦,所以我们经常把测试数据中不能重复利用的某些数据参数化,然后再代码中进行替换。而在这个过程中使用的模块,就是replace模块
str.replace(old, new[, max]) # old -- 将被替换的子字符串。 # new -- 新字符串,用于替换old子字符串。 # max -- 可选字符串, 替换不超过 max 次 # 例如: f data.count("register_phone") >0: data = data.replace("register_phone", test_dict["moblie"])
使用ddt导入参数的同时,检测是否存在参数化的数据。如果有,则进行替换操作,将参数化的数据和调用创建数据模块创建的数据互换,以达到每次测试参数化数据的唯一性。
4.log模块。这次接口测试中,我发现log日志存在大量重复,基本上每调用1次log模块,打印出来的log日志就多出一条重复。经改正后代码如下
from Project_common.File_path import file_path from configparser import ConfigParser import logging import time class Data_Logger(): def __init__(self,name, path="Data_test.cfg", section="Log_data", Level_option="level", Format_option="FORMAT", encoding="UTF-8"): """ 默认初始化读取配置文件 :param file_path: :param encoding: """ self.name = name self.cf = ConfigParser() FilePath = file_path + "Project_config\" + path self.cf.read(FilePath, encoding) self.Level = self.cf.get(section = section, option = Level_option) self.fm = logging.Formatter(self.cf.get(section = section, option = Format_option)) def log_write(self): """ log文件创建及读取 :return: """ # 创建日志文件并设定收集信息级别 my_logger = logging.getLogger(self.name) my_logger.setLevel(self.Level) # 编辑日志格式并设定输出信息级别 ch = logging.StreamHandler() ch.setLevel(self.Level) ch.setFormatter(self.fm) # 指定输出到文本渠道 path = file_path + "Project_log\"+ time.strftime('%Y-%m-%d') + "test.log" fh = logging.FileHandler(path, encoding="UTF-8") fh.setLevel(self.Level) fh.setFormatter(self.fm) # 渠道 my_logger.addHandler(ch) my_logger.addHandler(fh) return my_logger
要点就是再使用的过程中,不要返回结果,而是返回对象,减少模块的调用。
4.faker模块。在创建测试数据时,经常需要创建假数据,使用faker模块可以实现这一点
# 常用方法: # # city_suffix():市,县 # # country():国家 # # country_code():国家编码 # # district():区 # # geo_coordinate():地理坐标 # # latitude():地理坐标(纬度) # # longitude():地理坐标(经度) # # lexify():替换所有问号(“?”)带有随机字母的事件。 # # numerify():三位随机数字 # # postcode():邮编 # # province():省份 # # street_address():街道地址 # # street_name():街道名 # # street_suffix():街、路 # # random_digit():0 # ~9 # 随机数 # # random_digit_not_null():1 # ~9 # 的随机数 # # random_element():随机字母 # # random_int():随机数字,默认0 # ~9999,可以通过设置min, max来设置 # # random_letter():随机字母 # # random_number():随机数字,参数digits设置生成的数字位数 # # color_name():随机颜色名 # # hex_color():随机HEX颜色 # # rgb_color():随机RGB颜色 # # safe_color_name():随机安全色名 # # safe_hex_color():随机安全HEX颜色 # # bs():随机公司服务名 # # company():随机公司名(长) # # company_prefix():随机公司名(短) # # company_suffix():公司性质 # # credit_card_expire():随机信用卡到期日 # # credit_card_full():生成完整信用卡信息 # # credit_card_number():信用卡号 # # credit_card_provider():信用卡类型 # # credit_card_security_code():信用卡安全码 # # currency_code():货币编码 # # am_pm():AM / PM # # century():随机世纪 # # date():随机日期 # # date_between():随机生成指定范围内日期,参数:start_date,end_date取值:具体日期或者today, -30 # d, -30 # y类似 # # date_between_dates():随机生成指定范围内日期,用法同上 # # date_object():随机生产从1970 - 1 - 1 # 到指定日期的随机日期。 # # date_this_month(): # # date_this_year(): # # date_time():随机生成指定时间(1970 # 年1月1日至今) # # date_time_ad():生成公元1年到现在的随机时间 # # date_time_between():用法同dates # # future_date():未来日期 # # future_datetime():未来时间 # # month():随机月份 # # month_name():随机月份(英文) # # past_date():随机生成已经过去的日期 # # past_datetime():随机生成已经过去的时间 # # time():随机24小时时间 # # timedelta():随机获取时间差 # # time_object():随机24小时时间,time对象 # # time_series():随机TimeSeries对象 # # timezone():随机时区 # # unix_time():随机Unix时间 # # year():随机年份 # # file_extension():随机文件扩展名 # # file_name():随机文件名(包含扩展名,不包含路径) # # file_path():随机文件路径(包含文件名,扩展名) # # mime_type():随机mime # Type # # ascii_company_email():随机ASCII公司邮箱名 # # ascii_email():随机ASCII邮箱 # # ascii_free_email(): # # ascii_safe_email(): # # company_email(): # # domain_name():生成域名 # # domain_word():域词(即,不包含后缀) # # email(): # # free_email(): # # free_email_domain(): # # f.safe_email():安全邮箱 # # f.image_url():随机URL地址 # # ipv4():随机IP4地址 # # ipv6():随机IP6地址 # # mac_address():随机MAC地址 # # tld():网址域名后缀(.com,.net.cn, 等等,不包括.) # # uri():随机URI地址 # # uri_extension():网址文件后缀 # # uri_page():网址文件(不包含后缀) # # uri_path():网址文件路径(不包含文件名) # # url():随机URL地址 # # user_name():随机用户名 # # isbn10():随机ISBN(10 # 位) # # isbn13():随机ISBN(13 # 位) # # job():随机职位 # # paragraph():随机生成一个段落 # # paragraphs():随机生成多个段落,通过参数nb来控制段落数,返回数组 # # sentence():随机生成一句话 # # sentences():随机生成多句话,与段落类似 # # text():随机生成一篇文章(不要幻想着人工智能了,至今没完全看懂一句话是什么意思) # # word():随机生成词语 # # words():随机生成多个词语,用法与段落,句子,类似 # # binary():随机生成二进制编码 # # boolean():True / False # # language_code():随机生成两位语言编码 # # locale():随机生成语言 / 国际 # 信息 # # md5():随机生成MD5 # # null_boolean():NULL / True / False # # password():随机生成密码, 可选参数:length:密码长度;special_chars:是否能使用特殊字符;digits:是否包含数字;upper_case:是否包含大写字母;lower_case:是否包含小写字母 # # sha1():随机SHA1 # # sha256():随机SHA256 # # uuid4():随机UUID # # first_name(): # # first_name_female():女性名 # # first_name_male():男性名 # # first_romanized_name():罗马名 # # last_name(): # # last_name_female():女姓 # # last_name_male():男姓 # # last_romanized_name(): # # name():随机生成全名 # # name_female():男性全名 # # name_male():女性全名 # # romanized_name():罗马名 # # msisdn():移动台国际用户识别码,即移动用户的ISDN号码 # # phone_number():随机生成手机号 # # phonenumber_prefix():随机生成手机号段 # # profile():随机生成档案信息 # # simple_profile():随机生成简单档案信息 # # 随机生成指定类型数据: # # pybool(): # # pydecimal(): # # pydict(): # # pyfloat():left_digits = 5 # 生成的整数位数, # # right_digits = 2 # 生成的小数位数, # # positive = True # 是否只有正数 # # pyint(): # # pyiterable() # # pylist() # # pyset() # # pystr() # # pystruct() # # pytuple() # # ssn():生成身份证号 # # chrome():随机生成Chrome的浏览器user_agent信息 # # firefox():随机生成FireFox的浏览器user_agent信息 # # internet_explorer():随机生成IE的浏览器user_agent信息 # # opera():随机生成Opera的浏览器user_agent信息 # # safari():随机生成Safari的浏览器user_agent信息 # # linux_platform_token():随机Linux信息 # # user_agent():随机user_agent信息
5.映射。测试接口中必然存在的登录的session或者token,因为登录和登录后的其他操作基本是处于两个用例中,在使用过程中如果不保存登陆后的token,就没法进行登陆后的操作了,这个时候就可以使用映射来处理,映射使用的则是hasattr()、getattr()、setattr()函数
1) hasattr(object, name)
判断object对象中是否存在name属性,当然对于python的对象而言,属性包含变量和方法;有则返回True,没有则返回False;需要注意的是name参数是string类型,所以不管是要判断变量还是方法,其名称都以字符串形式传参;getattr和setattr也同样;
>>> >>> class A(): name = 'python' def func(self): return 'A()类的方法func()' >>> >>> hasattr(A, 'name') True >>> >>> hasattr(A, 'age') False >>> >>> hasattr(A, 'func') True >>>
2)getattr(object, name[, default])
获取object对象的属性的值,如果存在则返回属性值,如果不存在分为两种情况,一种是没有default参数时,会直接报错;给定了default参数,若对象本身没有name属性,则会返回给定的default值;如果给定的属性name是对象的方法,则返回的是函数对象,需要调用函数对象来获得函数的返回值;调用的话就是函数对象后面加括号,如func之于func();
另外还需要注意,如果给定的方法func()是实例函数,则不能写getattr(A, 'func')(),因为fun()是实例函数的话,是不能用A类对象来调用的,应该写成getattr(A(), 'func')();实例函数和类函数的区别可以简单的理解一下,实例函数定义时,直接def func(self):,这样定义的函数只能是将类实例化后,用类的实例化对象来调用;而类函数定义时,需要用@classmethod来装饰,函数默认的参数一般是cls,类函数可以通过类对象来直接调用,而不需要对类进行实例化;
>>> >>> class A(): name = 'python' def func(self): return 'Hello world' >>> >>> getattr(A, 'name') 'python' >>> >>> getattr(A, 'age') # age变量不存在则报错 Traceback (most recent call last): File "<pyshell#464>", line 1, in <module> getattr(A, 'age') AttributeError: class A has no attribute 'age' >>> >>> getattr(A, 'age', 20) >>> >>> getattr(A, 'func') <unbound method A.func> >>> >>> getattr(A, 'func')() # func()函数不能被A类对象调用,所以报错 Traceback (most recent call last): File "<pyshell#470>", line 1, in <module> getattr(A, 'func')() TypeError: unbound method func() must be called with A instance as first argument (got nothing instead) >>> >>> getattr(A(), 'func')() 'Hello world' >>> >>> class A(object): name = 'python' @classmethod def func(cls): return 'the method of A object.' >>> >>> getattr(A, 'func')() 'the method of A object.' >>>
3) setattr(object, name, value)
给object对象的name属性赋值value,如果对象原本存在给定的属性name,则setattr会更改属性的值为给定的value;如果对象原本不存在属性name,setattr会在对象中创建属性,并赋值为给定的value;
>>> >>> class A(): name = 'python' def func(self): return 'Hello world' >>> >>> setattr(A, 'name', 'java') >>> getattr(A, 'name') 'java' >>> >>> setattr(A, 'age', 20) >>> getattr(A, 'age') >>>
扩展:一般先判断对象中是否存在某属性,如果存在则返回;如果不存在,则给对象增加属性并赋值;很简单的if-else判断
>>> >>> class A(): name = 'python' def func(self): return 'Hello world' >>> >>> if hasattr(A, 'age'): print getattr(A, 'age') else: setattr(A, 'age', 20) >>> >>> getattr(A, 'age') >>>
6.最后就是一些细节需要注意,
1)cfg文件中一般使用‘ ’而不是“ ”,如果是sql连接则不需要引号,连接中接口必须是int类型,而其他的host,user,password则是str类型
2)希望sql返回数据是字典类型则可以使用pymysql.cursors.DictCursor,
cursor = self.mysql.cursor(pymysql.cursors.DictCursor)
3)抛出的异常也可以作为返回值进行判断
except suds.WebFault as e: self.log.error("执行sendMcode用例第{}条报错,错误为{}".format(case_id,e)) error_result = str(e).split(":")[1] Excel_Edit().write_excel(test_function=error_result, case_id=case_id, sheet_name=sheet_name)
抛出的异常打印为
执行sendMcode用例第4条报错,错误为Server raised fault: '短信模板不存在'