鹤啸九天 自律更自由,平凡不平庸 Less is More

代码规范及测试

2019-07-01
鹤啸九天
阅读量

Notes(温馨提示):

  1. ★ 首次阅读建议浏览:导航指南
  2. 右上角工具条搜索文章,右下角二维码关注微信公众号(鹤啸九天),底栏分享、赞赏、评论
  3. ★ 转载请注明文章来源,知识点积累起来不容易,水滴石穿,绳锯木断,谢谢理解
  4. ★ 如有疑问,邮件讨论,欢迎贡献优质资料


总结

  • 待定

编码规范

C++编码规范

【2022-4-3】现代C++ 整洁代码最佳实践

  1. KISS(Keep It Simple,Stupid)保持简单和直接原则:代码的实现、设计在满足需求的前提下越简单越好。大道至简,把简单的事情复杂化就是没事找事,把复杂的事情简单化才是一种能力。
  2. YAGNI(You Are NOT Gonna Need It)不需要原则:不要进行过度的设计。不要写现在用不上,将来或许可能也许用得上的一些代码,将来的事情谁也说不清楚。
  3. DRY(Do not Repeat Yourself)避免重复原则:尽量在项目中减少重复的代码行、重复的方法和模块。任何时候尽量避免Ctrl+C, Ctrl+V,因为重复意味着用中。可以想象,当修改一段代码时,也必须相应地修改另一段重复的代码,功能一样如果出了bug也一样。事实是,往往会漏改或忘记那段重复的代码。
  4. Information Hiding 信息隐藏原则:只提供必要的接口,并通过接口对外交互。通过接口一定程度上限制了变化范围,接口通常在一段时间内是相对稳定的,其实现可能是频繁变化的。
  5. 高内聚松耦合:
  6. 小心优化原则:建议没有明确的性能要求,就避免优化。不成熟的优化是编程中绝大部分问题的根源。

Python编码规范

pep8与google

【2017-11-23】python编码规范,目前有google和pep8两种,pylint默认pep8,Google python编码规范如何用pylint规范代码风格

pylint

  • 安装
    • pylint工具安装:sudo pip install pylint –ingore-installed
  • 使用:pylint test.py
  • 结果:MESSAGE_TYPE 有如下几种:
    • (C) 惯例。违反了编码风格标准
    • (R) 重构。写得非常糟糕的代码。
    • (W) 警告。某些 Python 特定的问题。
    • (E) 错误。很可能是代码中的错误。
    • (F) 致命错误。阻止 Pylint 进一步运行的错误。
  • 问题
    • No config file found, using default configuration——解决:生成pylint配置文件,touch ~/.pylintrc
    • C: 19, 0: Trailing newlines (trailing-newlines)——删除多余的空行
    • C: 17, 4: Invalid constant name “new” (invalid-name)——全局变量名字全部大写
    • C: 9, 0: Exactly one space required after comma——多参数时,逗号后加空格
    • C: 13, 4: Invalid variable name “s” (invalid-name)——局部变量命名过短,3个字符以上(避免单字母,除了计数器+迭代器;避免双下划线开头并结尾;避免包/模块名中的连字符-),_开头表示protected,__开头表示private
    • C: 10, 0: Old-style class defined. (old-style-class)——类定义中没有加object。,类名大写字母开头(pascal风格,如CapWord,模块、函数名小写字母与_,如lower_with_under.py)
    • R: 10, 0: Too few public methods (1/2) (too-few-public-methods)——缺少修改类属性值的方法
    • C: 25, 0: Trailing whitespace (trailing-whitespace)——行尾有空格

代码示例

#!/usr/bin/env python
# coding:utf8
"""
    test sample. google编码规范:(URL不受80字符限制)
    https://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/
    2020-7-16
    wangqiwen004@ke.com
"""
 
#(1)、以单下划线开头,表示这是一个保护成员,只有类对象和子类对象自己能访问到这些变量。以单下划线开头的变量和函数被默认当作是内部函数,使用from module improt *时不会被获取,但是使用import module可以获取
#(2)、以单下划线结尾仅仅是为了区别该名称与关键词
#(3)、双下划线开头,表示为私有成员,只允许类本身访问,子类也不行。在文本上被替换为_class__method
#(4)、双下划线开头,双下划线结尾。一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突。是一些 Python 的“魔术”对象,表示这是一个特殊成员,例如:定义类的时候,若是添加__init__方法,那么在创建类的实例的时候,实例会自动调用这个方法,一般用来对实例的属性进行初使化,Python不建议将自己命名的方法写为这种形式。
 
#import的包一定要使用;import包分成3部分,依次排序:
# ①系统包
# ②第三方包
# ③自定义包。
# 每部分按照字母顺序排序,一次不能导入多个包

import sys
 
class MyClass(object):
    """class测试: 类名满足Pascal风格"""
    public_name = '-public-' # public
    _myname = '-protected' # protected,不能通过import *导入,其它正常
    __private_name = '-private-' # private,实例、子类不能使用
  
    def __init__(self, name="wang"): # 特殊函数方法
        self._myname = name
        print '我的名字是%s'%(self._myname)
  
    def say(self):
        """打招呼"""
        print '你好,我是%s,%s,%s'%(self._myname, self.public_name, self.__private_name)
        return 'yes'
  
    def modify(self, name="-"):
        """更改属性值"""
        self._myname = name
  
def my_fun(value=0, delta=9):
    """
        外部函数:名字_连接。多参数时,逗号后面加一个空格
    """
    res = value + delta
    return res
  
def main():
    """main function"""
    #main里的都是全局变量,需要大写
    value = 3
    new = my_fun(value)
    v_result = MyClass("wqw")
    #不能访问protected、private变量.W._myname, W.__private_name
    #超过80字符时,可以用\换行,注:(),[]时可省略\
    print >> sys.stdout, 'hello,related values are listed as : %s , %s,I am \
        %s,%s ...'%(value, new, v_result.say(), v_result.public_name)
    print >> sys.stdout, 'hello,related values are listed as : %s , %s,I am %s,%s ...'%(value, new, v_result.say(), v_result.public_name) # pylint: disable=line-too-long
    #参考:怎么关闭某类检测:How do I disable a Pylint warning?
    #https://stackoverflow.com/questions/4341746/how-do-i-disable-a-pylint-warning
  
if __name__ == '__main__':
    A = 3 # 此处为全局变量,一律大写
    main()

# */* vim: set expandtab ts=4 sw=4 sts=4 tw=400: */

测试

(1)压力测试

  • wrk(请求)
  • htop(cpu和内存占用)
  • dstat(硬盘读写)
  • tcpdump(网络包)
  • iostat(io读写)
  • netstat(网络连接) (2)代码测试
  • unittest(单元测试)
  • selenium(浏览器测试)
  • mock/stub (3)黑盒测试

(4)功能测试

业界经验

【2022-2-17】脉脉 各业务线qps:一般来说qps>5k的都算高

  • 腾讯微信热门服务,全年峰值qps也才3w;qps过万就不错了,用户产品日常qps 500-1000,上千的已经到顶了,遇上运营活动偶尔过万,注意是偶尔
  • 国民级app(抖音)推荐接口的qps几十万,抖音电商10w,国内百万级别qps的只有微信、抖音之类,需要加codis缓存服务
  • 淘宝搜索多年前,双十一前几天的qps峰值是27w-40w
  • 韵达C端系统,每天110w请求,高峰期上午9点,下午14点,峰值qps 不到 200
  • 全世界只有google搜索/fb的feed流的qps能达到百万级别

测试种类

【2021-11-18】一文带你了解测试类型

按照测试阶段可以将软件测试分为单元测试、冒烟测试、集成测试、系统测试与验收测试。这种分类方式与软件开发过程相契合,是为了检验软件开发各个阶段是否符合要求。

  • (1) 单元测试:单元测试是软件开发的第一步测试,目的是为了验证软件单元是否符合软件需求与设计。单元测试大多是开发人员进行的自测。
  • (2) 冒烟测试:冒烟测试最初是从电路板测试得来的,当电路板做好以后,首先会加电测试,如果电路板没有冒烟再进行其他测试,否则就必须重新设计后再次测试。后来这种测试理念被引入到软件测试中。在软件测试中,冒烟测试是指软件构建版本建立后,对系统的基本功能进行简单的测试,这种测试重点验证的是程序的主要功能,而不会对具体功能进行深入测试。如果测试未通过,需要返回给开发人员进行修正;如果测试通过则再进行其他测试。因此,冒烟测试是对新构建版本软件进行的最基本测试。
  • (3) 集成测试:集成测试是冒烟测试之后进行的测试,它是将已经测试过的软件单元组合在一起测试它们之间的接口,用于验证软件是否满足设计需求。
  • (4) 系统测试:系统测试是将经过测试的软件在实际环境中运行,并与其他系统的成分(如数据库、硬件和操作人员等)组合在一起进行的测试。
  • (5) 验收测试:验收测试主要是对软件产品说明进行验证,逐行逐字地按照说明书的描述对软件产品进行测试,确保其符合客户的各项要求。

按照测试技术分类,按照使用的测试技术可以将软件测试分为黑盒测试与白盒测试。

  • (1)黑盒测试
    • 黑盒测试就是把软件(程序)当作一个有输入与输出的黑匣子,它把程序当作一个输入域到输出域的映射,只要输入的数据能输出预期的结果即可,不必关心程序内部是怎么样实现的,下图所示。
    • ![1605237901884黑盒测试01.jpg](http://www.itheima.com/images/newslistPIC/1605237901884%E9%BB%91%E7%9B%92%E6%B5%8B%E8%AF%9501.jpg)
  • (2)白盒测试
    • 白盒测试又叫透明盒测试,它是指测试人员了解软件程序的逻辑结构、路径与运行过程,在测试时,按照程序的执行路径得出结果。白盒测试就是把软件(程序)当作一个透明的盒子,测试人员清楚地知道从输入到输出的每一步过程,如下图所示。
    • ![1605237923772白盒测试.jpg](http://www.itheima.com/images/newslistPIC/1605237923771%E7%99%BD%E7%9B%92%E6%B5%8B%E8%AF%95.jpg)

相对于黑盒测试来说,白盒测试对测试人员的要求会更高一点,他要求测试人员具有一定的编程能力,而且要熟悉各种脚本语言。但是在软件公司里,黑盒测试与白盒测试并不是界限分明的,在测试一款软件时往往是黑盒测试与白盒测试相结合对软件进行完整全面的测试。

按照软件质量特性分类,按照软件质量特性可以将软件测试分为功能测试与性能测试。

  • (1)功能测试
    • 功能测试就是测试软件的功能是否满足客户的需求,包括准确性、易用性、适合性、互操作性等。
  • (2)性能测试
    • 性能测试就是测试软件的性能是否满足客户的需求,性能测试包括负载测试、压力测试、兼容性测试、可移植性测试和健壮性测试。

按照自动化程度分类,按照自动化程度可以将软件测试分为手工测试与自动化测试。

  • (1)手工测试
    • 手工测试是测试人员一条一条地执行代码完成测试工作。手工测试比较耗时费力,而且测试人员如果是在疲惫状态下,则很难保证测试的效果。
  • (2)自动化测试
    • 自动化测试是借助脚本、自动化测试工具等完成相应的测试工作,它也需要人工的参与,但是它可以将要执行的测试代码或流程写成脚本,执行脚本完成整个测试工作。

按照测试类型分类,软件测试类型有多种,包括界面类测试、功能测试、性能测试、安全性测试、文档测试等,其中功能测试与性能测试前面已经介绍,下面主要介绍其他几种测试。

  • (1)界面类测试
    • 界面类测试是验证软件界面是否符合客户需求,包括界面布局是否美观、按钮是否齐全等。
  • (2)安全性测试
    • 安全性测试是测试软件在没有授权的内部或外部用户的攻击或恶意破坏时如何进行处理,是否能保证软件与数据的安全。
  • (3)文档测试
    • 文档测试以需求分析、软件设计、用户手册、安装手册为主,主要验证文档说明与实际软件之间是否存在差异。

其他分类:还有一些软件测试无法具体归到哪一类,但在测试行业中也会经常进行这些测试,如α测试、β测试、回归测试等,具体介绍如下。

  • (1) α测试
    • α测试是指对软件最初版本进行测试。软件最初版本一般不对外发布,在上线之前,由开发人员和测试人员或者用户协助进行测试。测试人员记录使用过程中出现的错误与问题整个测试过程是可控的。
  • (2) β测试
    • β测试是指对上线之后的软件版本进行测试,此时软件已上线发布,但发布的版本中可能会存在较轻微的Bug,由用户在使用过程中发现错误与问题并进行记录,然后反馈给开发人员进行修复。
  • (3)回归测试
    • 当测试人员发现缺陷以后,会将缺陷提交给开发人员,开发人员对程序进行修改,修改之后,测试人员会对修改后的程序重新进行测试,确认原有的缺陷已经消除并且没有引入新的缺陷,这个重新测试的过程就叫作回归测试。回归测试是软件测试工作中非常重要的一部分,软件开发的各个阶段都会进行多次回归测试。
  • (4)随机测试
    • 随机测试是没有测试用例、检查列表、脚本或指令的测试,它主要是根据测试人员的经验对软件进行功能和性能抽查。随机测试是根据测试用例说明书执行测试用例的重要补充手段,是保证测试覆盖完整性的有效方式和过程。

指标概念

概念做下了解:

  1. 响应时间 (RT)
    • 响应时间是指系统对请求作出响应的时间。直观上看,这个指标与人对软件性能的主观感受是非常一致的,因为它完整地记录了整个计算机系统处理请求的时间。由于一个系统通常会提供许多功能,而不同功能的处理逻辑也千差万别,因而不同功能的响应时间也不尽相同,甚至同一功能在不同输入数据的情况下响应时间也不相同。所以,在讨论一个系统的响应时间时,人们通常是指该系统所有功能的平均时间或者所有功能的最大响应时间。当然,往往也需要对每个或每组功能讨论其平均响应时间和最大响应时间。
    • 对于单机的没有并发操作的应用系统而言,人们普遍认为响应时间是一个合理且准确的性能指标。需要指出的是,响应时间的绝对值并不能直接反映软件的性能的高低,软件性能的高低实际上取决于用户对该响应时间的接受程度。对于一个游戏软件来说,响应时间小于100毫秒应该是不错的,响应时间在1秒左右可能属于勉强可以接受,如果响应时间达到3秒就完全难以接受了。而对于编译系统来说,完整编译一个较大规模软件的源代码可能需要几十分钟甚至更长时间,但这些响应时间对于用户来说都是可以接受的。
  2. 吞吐量 (Throughput)
    • TPS 每秒完成的请求数(Request per Second)
    • 吞吐量是指系统在单位时间内处理请求的数量。对于无并发的应用系统而言,吞吐量与响应时间成严格的反比关系,实际上此时吞吐量就是响应时间的倒数。前面已经说过,对于单用户的系统,响应时间(或者系统响应时间和应用延迟时间)可以很好地度量系统的性能,但对于并发系统,通常需要用吞吐量作为性能指标。
    • 对于一个多用户的系统,如果只有一个用户使用时系统的平均响应时间是t,当有你n个用户使用时,每个用户看到的响应时间通常并不是n×t,而往往比n×t小很多(当然,在某些特殊情况下也可能比n×t大,甚至大很多)。这是因为处理每个请求需要用到很多资源,由于每个请求的处理过程中有许多不走难以并发执行,这导致在具体的一个时间点,所占资源往往并不多。也就是说在处理单个请求时,在每个时间点都可能有许多资源被闲置,当处理多个请求时,如果资源配置合理,每个用户看到的平均响应时间并不随用户数的增加而线性增加。实际上,不同系统的平均响应时间随用户数增加而增长的速度也不大相同,这也是采用吞吐量来度量并发系统的性能的主要原因。一般而言,吞吐量是一个比较通用的指标,两个具有不同用户数和用户使用模式的系统,如果其最大吞吐量基本一致,则可以判断两个系统的处理能力基本一致。
  3. 并发用户数
    • 并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量。与吞吐量相比,并发用户数是一个更直观但也更笼统的性能指标。实际上,并发用户数是一个非常不准确的指标,因为用户不同的使用模式会导致不同用户在单位时间发出不同数量的请求。一网站系统为例,假设用户只有注册后才能使用,但注册用户并不是每时每刻都在使用该网站,因此具体一个时刻只有部分注册用户同时在线,在线用户就在浏览网站时会花很多时间阅读网站上的信息,因而具体一个时刻只有部分在线用户同时向系统发出请求。这样,对于网站系统我们会有三个关于用户数的统计数字:注册用户数、在线用户数和同时发请求用户数。由于注册用户可能长时间不登陆网站,使用注册用户数作为性能指标会造成很大的误差。而在线用户数和同事发请求用户数都可以作为性能指标。相比而言,以在线用户作为性能指标更直观些,而以同时发请求用户数作为性能指标更准确些。
  4. QPS 每秒查询率 (Query Per Second)
    • 每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。 (类似于TPS,只是应用于特定场景的吞吐量)

压力测试

什么是压力测试

压力测试就是被测试的系统,在一定的访问压力下,看程序运行是否稳定/服务器运行是否稳定(资源占用情况)。压力测试涵盖性能测试,负载测试,并发测试等等,这些测试点常常交织耦合在一起。

压测方法

【2021-11-16】线上压测方法论

成熟的互联网研发体系下,一般会有测试环境和生产环境(线上环境)之分。

  • 测试环境下进行功能测试、异常测试等
  • 压力测试一般在线上环境进行。 这是为什么呢?
  • 从压力测试的目的来看,进行压力测试往往是为了评估线上系统的容量,在正式上线对外前根据压测情况进行系统性能优化、性能bug修复、机器扩容,已使线上系统承压能力达到预期。
  • 直接在线上环境做压测,能更加准确地得到结果,毕竟测试环境的机器数量、机器性能、网络拓扑、机房部署、链路结构(测试环境可能存在链路的某些模块是mock出来的,不真实)等和线上环境是有差别的。

但直接在线上环境做压测,会有什么风险呢?

  1. 向线上数据库写入大量脏数据,如果一次压测涉及多个表的联动写入,那么脏数据的清理将是非常繁琐的事,而且脏数据的清理操作也很可能造成线上事故。如果不清理脏数据,将对后续数据核对产生干扰,大量的数据存在也会造成数据库读变慢,存储压力变大等问题。
  2. 压力过大,造成线上系统瘫痪

业界常用的线上压测的方案是进行影子链路改造,即将真实流量和压测流量区分开,压测流量经过影子链路流入影子表。具体步骤如下:

  • step1: 创建影子表。一般直接复制真实表的表结构,根据需求构造一些数据写入影子表。
  • step2:链路改造。修改代码,引入流量识别逻辑。一般对于ToC产品,在压测时选择userId为识别标识,在压测时构造一批压测userId(带有特定识,例如最后一位为特定字母等)。代码识别是压测userId,转入影子链路。
  • step3: 压测执行。在压测执行时特别注意需要缓慢加压,防止压力过大压垮线上系统。压测前需理清压测链路涉及的各个系统,压测时做好系统监控察(观察机器资源情况(CPU、内存)、系统报错日志是否激增)。
  • step4: 压测报告编写。压测完成后需产出压测报告。

压力测试与性能测试

压力测试和性能测试的区别:二者区别在于他们不同的测试目的

  • 软件压力测试是为了发现系统能支持的最大负载,前提是要求系统性能处在可以接受的范围内,比如经常规定的页面3秒钟内响应;
    • 所以一句话概括就是:在性能可以接受的前提下,测试系统可以支持的最大负载。
  • 软件性能测试是为了检查系统的反映,运行速度等性能指标,他的前提是要求在一定负载下,如检查一个网站在100人同时在线的情况下的性能指标,每个用户是否都还可以正常的完成操作等。
    • 概括就是:在不同负载下(负载一定)时,通过一些系统参数(如反应时间等)检查系统的运行情况。比如某个网站的性能差,严格上应该说在N人同时在线情况下,这个站点性能很差)。

总之,就像一个方程式:

综合性能 = 压力数 * 性能指数

综合性能是固定的:

  • 软件压力测试是为了得到性能指数最小时候(可以接受的最小指数)最大的压力数
  • 软件性能测试是为了得到压力数确定下的性能指数。

总结:

  • 在项目上线之前,都需要做压力测试,目的是看下网站能抗住多少的压力,能承担多少并发,如果不做压力测试,一旦出现大访问量时,网站会挂掉,所以压力测试真的很重要,都应该重视起来!
  • 压力测试总共需要几个步骤?思路总结篇

压测工具

业内常用的压测工具包括 JMeter、Gatling、Locust、k6、Tsung、阿里云 PTS 等。这些工具无一例外,都需要将压测业务的 API,编排为一个压测脚本。重点在确认压测的 API,不要有遗漏,且 API 编排的顺序要符合用户的操作逻辑。

常见压力测试工具有: ab, wrk

ab

安装

  • yum -y install httpd-tools

命令选项

  • -n 请求数
  • -c 并发数
  • -s 等待响应的时间默认 30 秒
  • -H 自定义请求头参数,可多次使用该选项
  • -T 设置 Content-Type 请求头信息
  • -k 启用 HTTP KeepAlive 功能,即在一个 HTTP 会话中执行多个请求。默认时,不启用 KeepAlive 功能。

ab 示例

# 1. 基本实例
ab http://192.168.31.92/

# 2. 100 并发,5000 请求
ab -c 100 -n 5000 http://192.168.31.92/

# 3. 设置请求头信息 User-Agent
ab -c 500 -n 10000 \
-H "User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" http://192.168.31.92/

# 4. GET 传参
ab "http://192.168.31.91/api/test/?username=chris&password=123456"

# 5. POST 传参
echo "username="chris"&password="123456"" >> post.data
ab -p post.data -T "application/x-www-form-urlencoded" http://192.168.31.91/api/test/

wrk

性能测试工具 wrk 使用教程

wrk 是一种现代 HTTP 基准测试工具,当在多核 CPU 上运行时,能够产生大量负载, 它结合了多线程设计和可扩展的事件通知系统如 epoll 和 kqueue; 支持大多数类 Unix,不支持 Windows。基于 wrk 优化版的 wrk2, 其提供的额外参数 -R,用以指定每秒多少个请求限制

优势:

  • 轻量级性能测试工具;
  • 安装简单(相对 Apache ab 来说);
  • 学习曲线基本为零,几分钟就能学会咋用了;
  • 基于系统自带的高性能 I/O 机制,如 epoll, kqueue, 利用异步的事件驱动框架,通过很少的线程就可以压出很大的并发量;

劣势

  • wrk 目前仅支持单机压测,后续也不太可能支持多机器对目标机压测,因为它本身的定位,并不是用来取代 JMeter, LoadRunner 等专业的测试工具,wrk 提供的功能,对我们后端开发人员来说,应付日常接口性能验证还是比较友好的。

钩子函数

  • (1)启动阶段: 每个线程执行一次
    • function setup(thread)
    • function init(args) args 用于获取命令行中传入的参数
  • (2)运行阶段: 每次请求调用 1 次
    • function delay() 每次请求之前延迟多少毫秒
    • function request() 生成请求
    • function response(status, headers, body) 解析响应
  • (3)结束阶段: 整个压测过程运行一次
    • function done(summary, latency, requests) 可从参数给定的对象中,获取压测结果;

安装及命令选项

安装

git clone https://github.com/wg/wrk.git
cd wrk && make
cp wrk /usr/sbin/wrk && chmod +x /usr/sbin/wrk

命令选项

  • 使用方法: wrk <选项> <被测HTTP服务的URL> Options:
  • -c, –connections < N > 跟服务器建立并保持的TCP连接数量
  • -d, –duration < T > 压测持续时间
  • -t, –threads < N > 使用多少个线程进行压测
  • -s, –script < S > 指定Lua脚本路径
  • -H, –header < H > 为每一个HTTP请求添加HTTP头
  • –latency 在压测结束后,打印延迟统计信息
  • –timeout < T > 超时时间
  • -v, –version 打印正在使用的wrk的详细版本信息

< N > 代表数字参数,支持国际单位 (1k, 1M, 1G) < T > 代表时间参数,支持时间单位 (2s, 2m, 2h)

wrk 示例

# 1. 基本示例
wrk -t2 -c30 -d30s --latency http://192.168.31.92/
# 对 www.baidu.com 发起压力测试,线程数为 12,模拟 400 个并发请求,持续 30 秒。
wrk -t12 -c400 -d30s http://www.baidu.com
# -------- 压测报告 -------
# Running 30s test @ http://192.168.31.92/
#   2 threads and 30 connections
#   Thread Stats   Avg      Stdev     Max   +/- Stdev
#     Latency     1.59ms    1.16ms  47.58ms   91.91%    // 线程延迟状态
#     Req/Sec     9.23k   846.07    12.38k    68.83%    // 单个线程每秒请求数平均9.23k, 最大12.38k
#   Latency Distribution   // 所有请求的延迟分布, 75%的请求延迟小于1.77毫秒
#      50%    1.38ms
#      75%    1.77ms
#      90%    2.39ms
#      99%    5.95ms
#   551551 requests in 30.05s, 2.02GB read  // 2个线程, 30s内55万次请求
# Requests/sec:  18355.61     // 2个线程每次请求1.8万次
# Transfer/sec:     68.95MB

# 2. POST|GET 传参请求测试

wrk -t2 -c10 --latency --script=params.data http://192.168.31.91/api/test/

请求参数 params.data
cat > post.data <<EOF
wrk.method = "POST"
wrk.body = "username=chris&password=123456"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
EOF

# 3. 修改请求头信息 User-Agent 测试
wrk -t2 -c10 -d10s --latency \
-H "User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" \
http://192.168.31.92/

# 4. 基于 lua 脚本定制测试报告
wrk -t2 -c10 -d10s -s setup.lua --latency http://192.168.31.92/

【2021-11-17】Jenkins + Jmeter,线上用kePTs

怎么做服务器压测?

(1)收费:

  • 阿里云性能测试PTS: PTS无需安装软件;脚本场景监控简单化,省时、省力;分布式并发压测,施压能力无上限;快速大规模集群扩容、支持几十万用户及百万级TPS性能压测;可模拟海量用户的真实业务场景,全方位验证业务站点的性能、容量和稳定性。
  • 腾讯云压测大师:WeTest 压测大师(Load Master,LM)是简单易用的自动化性能测试平台,为用户提供测试框架及压测环境、创建虚拟机器人模拟产品多用户并发场景,支持 http 或https 协议,包括 Web/H5 网站、移动应用、API 、游戏等主流压测场景,适用于产品发布前及运营中的服务器压力测试及性能优化。

(2)免费:

  • http_load:程序非常小,解压后也不到100K,http_load以并行复用的方式运行,用以测试web服务器的吞吐量与负载。但是它不同于大多数压力测试工具,它可以以一个单一的进程运行,一般不会把客户机搞死。还可以测试HTTPS类的网站请求。
    • 下载地址:http_load-12mar2006.tar.gz
    • 使用方法:http_load -p 并发访问进程数 -s 访问时间 需要访问的URL文件
  • webbench:webbench是Linux下的一个网站压力测试工具,最多可以模拟3万个并发连接去测试网站的负载能力。
    • 下载地址:webbench-1.5.tar.gz,这个程序更小,解压后不到50K
    • 用法:webbench -c 并发数 -t 运行测试时间 URL
  • apache bench(主要是用来测试apache的): ab是apache自带的一款功能强大的测试工具。
    • 安装了apache一般就自带了。
    • webbench -c 1000 -t 130 https://iil.ink/m6c5a
  • Siege:一款开源的压力测试工具,可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行。
    • Siege官方:http://www.joedog.org/
    • Siege下载:siege-latest.tar.gz
    • Siege使用: siege -c 100 -r 10 -f site.url
  • ⑤ JMiter压测软件:一款通过不断提高对系统压力,用于测试系统性能,如负载,功能,性能,回归等;的有简洁明了的图形界面软件。参考地址
  • ⑥ LoadRunner
    • LoadRunner是惠普旗下一款自动负载测试工具,它能预测系统行为,优化性能。LoadRunner强调的是整个企业的系统,它通过模拟实际用户的操作行为和实行实时性能监测,来帮助更快的确认和查找问题。此外,LoadRunner 能支持最宽范的协议和技术,量身定做地提供解决方案。

单元测试

单元测试是开发人员编写的、用于检测在特定条件下目标代码正确性的代码。

  • 软件开发天生就具有复杂性,没人敢打包票说自己写的代码没有问题,或者不经测试就能保证代码正确运行,因此,要保证程序的正确性就必须要对代码进行严格测试。

不写单元测试的理由

  • 单元测试太花时间
  • 测试不是研发的工作:测试确实不是开发人员的工作,但单元测试确实是开发人员的工作
  • 代码都编译通过了,还测什么:代码编译通过只能说你写的代码符合语法要求,并不代表能保证正确性
  • 代码原来就没有单元测试,并且难以测试

单元测试优点

  • 便于后期重构。单元测试可以为代码的重构提供保障,只要重构代码之后单元测试全部运行通过,那么在很大程度上表示这次重构没有引入新的BUG,当然这是建立在完整、有效的单元测试覆盖率的基础上。
  • 优化设计。编写单元测试将使用户从调用者的角度观察、思考,特别是使用TDD驱动开发的开发方式,会让使用者把程序设计成易于调用和可测试,并且解除软件中的耦合。
  • 文档记录。单元测试就是一种无价的文档,它是展示函数或类如何使用的最佳文档,这份文档是可编译、可运行的、并且它保持最新,永远与代码同步。
  • 具有回归性。自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地地快速运行测试,而不是将代码部署到设备之后,然后再手动地覆盖各种执行路径,这样的行为效率低下,浪费时间。

良好的接口设计、正确性、可回归、可测试、完善的调用文档、高内聚、低耦合

  • 一个完整的测试脚本(用例)一般包含以下几个步骤:
    • 环境准备或检查
    • 执行业务操作
    • 断言结果
    • 清理环境
  • 而测试框架一般还要完成用例加载,批量执行,异常控制,结果输出等功能。基础的测试框架一般只提供执行控制方面的功能。

  • 【2020-10-15】单元测试
  • ①单元测试,系统测试,集成测试,百度要求必须有单测代码,一般是正式代码的1.2倍,单测不过不能提测
  • ②python单测有好多种方法,除了pytest,还有nosetest,unitest,后面两种除了mock,还有别的定制方法setup和teardown,分别指执行命令前后的操作
  • ③执行单测后一般有自动生成的测试报告,通过率,覆盖率(条件覆盖,分支覆盖)
  • ④断言assert仅适用于单测和非正式代码,线上模块避免使用,因为触发异常时整个进程会挂掉
  • ⑤python类分新式类和旧式类,区别在于继承方式,多继承时出现重名函数时,一个深度遍历,一个广度遍历

单元测试工具

单测框架

  • unittest: Python自带,最基础的单元测试框架
  • nose: 基于unittest开发,易用性好,有许多插件
  • pytest: 同样基于unittest开发,易用性好,信息更详细,插件众多
  • robot framework:一款基于Python语言的关键字驱动测试框架,有界面,功能完善,自带报告及log清晰美观

总结(Python测试框架对比—-unittest, pytest, nose, robot framework对比

  • unittest比较基础,二次开发方便,适合高手使用;pytest/nose更加方便快捷,效率更高,适合小白及追求效率的公司;robot framework由于有界面及美观的报告,易用性更好,灵活性及可定制性略差。

各测试工具横向对比

  • 如果不想/不允许安装第三方库,那么 unittest 是最好也是唯一的选择。反之,pytest无疑是最佳选择,众多 Python 开源项目(如大名鼎鼎的 requests)都是使用 pytest 作为单元测试框架。甚至,连 nose2 在官方文档上都建议大家使用 pytest

unitest

【2021-11-23】Python 单元测试 - unittest

unittest 是 python内置的用于测试代码的模块,无需安装, 使用简单方便。

  • unittest中最核心的部分是:TestFixture、TestCase、TestSuite、TestRunner

unittest case的运行流程:

  • 写好一个完整的TestCase
  • 多个TestCase 由TestLoder被加载到TestSuite里面, TestSuite也可以嵌套TestSuite
  • 由TextTestRunner来执行TestSuite,测试的结果保存在TextTestResult中
  • TestFixture指的是环境准备和恢复
import unittest

# 继承 unittest.TestCase 来创建一个测试用例
class TestStringMethods(unittest.TestCase):

    # 以 test 开头的方法,测试框架将把它作为独立的测试去执行。
    # 每个用例都采用 unittest 内置的断言方法来判断被测对象的行为是否符合预期

    def test_upper(self):
        # 是否等于预期值
        self.assertEqual('foo'.upper(), 'FOO')
        # 检查字符串是否包含在其他字符串中
        self.assertEqual('foo', 'this is foo my fool', '字符串确认包含')
        self.assertNotIn('foo', 'this is foo my fool', '字符串不包含')

    def test_isupper(self):
        # 是否符合条件
        self.assertTrue('FOO'.isupper()) # 真
        self.assertFalse('Foo'.isupper()) # 假

    def test_split(self):
        s = 'hello world'
        # 是否等于预期值
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        # 是否抛出特定异常
        with self.assertRaises(TypeError):
            s.split(2)

if __name__ == '__main__':
    unittest.main()

断言

UnitTest中的断言使用代码示例

断言语法 解释 python最低版本
assertEqual(a, b) 判断a==b -
assertNotEqual(a, b) 判断a!=b -
assertTrue(x) bool(x) is True -
assertFalse(x) bool(x) is False -
assertIs(a, b) a is b 3.1
assertIsNot(a, b) a is not b 3.1
assertIsNone(x) x is None 3.1
assertIsNotNone(x) x is not None 3.1
assertIn(a, b) a in b 3.1
assertNotIn(a, b) a not in b 3.1
assertIsInstance(a, b) isinstance(a, b) 3.1
assertNotIsInstance(a, b) not isinstance(a, b) 3.1
     
assertAlmostEqual(a, b) round(a-b, 7) == 0  
assertNotAlmostEqual(a, b) round(a-b, 7) != 0  
assertGreater(a, b) a > b  
assertGreaterEqual(a, b) a >= b  
assertLess(a, b) a < b  
assertLessEqual(a, b) a <= b  
assertRegexpMatches(s, re) regex.search(s)  
assertNotRegexpMatches(s, re) not regex.search(s)  
assertItemsEqual(a, b) sorted(a) == sorted(b) and works with unhashable objs  
assertDictContainsSubset(a, b) all the key/value pairs in a exist in b  
assertMultiLineEqual(a, b) strings  
assertSequenceEqual(a, b) sequences  
assertListEqual(a, b) lists  
assertTupleEqual(a, b) tuples  
assertSetEqual(a, b) sets or frozensets  
assertDictEqual(a, b) dicts  
assertMultiLineEqual(a, b) strings  
assertSequenceEqual(a, b) sequences  
assertListEqual(a, b) lists  
assertTupleEqual(a, b) tuples  
assertSetEqual(a, b) sets or frozensets 2.7assertDictEqual(a, b) dicts  

核心功能

nittest中最核心的部分是:TestFixture、TestCase、TestSuite、TestRunner

测试夹具(Fixtures)

  • 测试夹具也就是测试前置(setUp)和清理(tearDown)方法。
  • 测试前置方法 setUp() 用来做一些准备工作,比如建立数据库连接。它会在用例执行前被测试框架自动调用。
  • 测试清理方法 tearDown() 用来做一些清理工作,比如断开数据库连接。它会在用例执行完成(包括失败的情况)后被测试框架自动调用。
  • 测试前置和清理方法可以有不同的执行级别。
    • 方法:setUp、tearDown,每个测试方法执行时分别执行一次
    • :setUpClass、tearDownClass,只执行一次
    • 模块: setUpModule()、tearDownModule(),单个测试模块中只执行一次前置方法,再执行该模块中所有测试类的所有测试,最后执行一次清理方法

(1)Test Fixture用于测试环境的准备和恢复还原, 一般用到下面几个函数。

  • setUp():准备环境,执行每个测试用例的前置条件
  • tearDown():环境还原,执行每个测试用例的后置条件
  • setUpClass():必须使用@classmethod装饰器,所有case执行的前置条件,只运行一次
  • tearDownClass():必须使用@classmethod装饰器,所有case运行完后只运行一次

(2)Test Case

  • 参数verbosity可以控制错误报告的详细程度:默认为1。0表示不输出每一个用例的执行结果;2 表示详细的执行报告结果。
  • Verbosity=1 情况下成功是 .,失败是 F,出错是 E,跳过是 S
  • 测试的执行跟方法的顺序没有关系, 默认按字母顺序
  • 每个测试方法均以 test 开头
  • Verbosity=2情况下会打印测试的注释

(3)Test Suite

  • 一般通过 addTest() 或者 addTests() 向 suite 中添加。case的执行顺序与添加到Suite中的顺序是一致的

跳过测试或预计失败

unittest 支持直接跳过或按条件跳过测试,也支持预计测试失败:

  • 通过 skip 装饰器或 SkipTest 直接跳过测试
  • 通过 skipIf 或 skipUnless 按条件跳过或不跳过测试
  • 通过 expectedFailure 预计测试失败

@unittest.skip()装饰器跳过某个case

  • skip():无条件跳过
    • @unittest.skip(“i don’t want to run this case. “)
  • skipIf(condition,reason):如果condition为true,则 skip
    • @unittest.skipIf(condition,reason)
  • skipUnless(condition,reason):如果condition为False,则skip
    • @unittest.skipUnless(condition,reason)
class MyTestCase(unittest.TestCase):

    @unittest.skip("直接跳过")
    def test_nothing(self):
        self.fail("shouldn't happen")

    @unittest.skipIf(mylib.__version__ < (1, 3),
                     "满足条件跳过")
    def test_format(self):
        # Tests that work for only a certain version of the library.
        pass

    @unittest.skipUnless(sys.platform.startswith("win"), "满足条件不跳过")
    def test_windows_support(self):
        # windows specific testing code
        pass

    def test_maybe_skipped(self):
        if not external_resource_available():
            self.skipTest("跳过")
        # test code that depends on the external resource
        pass

    @unittest.expectedFailure
    def test_fail(self):
        self.assertEqual(1, 0, "这个目前是失败的")

子测试

  • 在一个测试方法中传入不同的参数来测试同一段逻辑,但它将被视作一个测试,但是如果使用了子测试,就能被视作 N(即为参数的个数)个测试。
class NumbersTest(unittest.TestCase):

    def test_even(self):
        """
        Test that numbers between 0 and 5 are all even.
        """
        for i in range(0, 6):
            # 定义子测试, 即使单个子测试执行失败,也不会影响后续子测试的执行
            with self.subTest(i=i):
                self.assertEqual(i % 2, 0)

(4)Test Loader

  • TestLoadder用来加载TestCase到TestSuite中。

loadTestsFrom*()方法从各个地方寻找testcase,创建实例,然后addTestSuite,再返回一个TestSuite实例

defaultTestLoader() 与 TestLoader()功能差不多,复用原有实例

  • unittest.TestLoader().loadTestsFromTestCase(testCaseClass)
  • unittest.TestLoader().loadTestsFromModule(module)
  • unittest.TestLoader().loadTestsFromName(name,module=None)
  • unittest.TestLoader().loadTestsFromNames(names,module=None)
  • unittest.TestLoader().discover()

用例发现和执行

unittest 支持用例自动(递归)发现:

  • 默认发现当前目录下所有符合 test*.py 测试用例
    • 使用 python -m unittest 或 python -m unittest discover
  • 通过 -s 参数指定要自动发现的目录, -p 参数指定用例文件的名称模式
    • python -m unittest discover -s project_directory -p “test_*.py”
  • 通过位置参数指定自动发现的目录和用例文件的名称模式
    • python -m unittest discover project_directory “test_*.py”

unittest 支持执行指定用例:

  • 指定测试模块
    • python -m unittest test_module1 test_module2
  • 指定测试
    • python -m unittest test_module.TestClass
  • 指定测试方法
    • python -m unittest test_module.TestClass.test_method
  • 指定测试文件路径(仅 Python 3)
    • python -m unittest tests/test_something.py

示例:常规函数测试

代码测试脚本demo.py内容:

#!/usr/bin/python
# -*- coding: utf-8 -*-

def add(a, b):
    return a+b

def minus(a, b):
    return a-b

单测编写步骤:

  1. 先导入unittest模块;
  2. 创建一个类(任意名)继承unittest.TestCase;
  3. 编写需要测试的代码对函数进行各方面的测试。

单测脚本:test_demo_class.py

# !/usr/bin/env python
# -*- coding:utf8 -*- 

# **************************************************************************
# * Copyright (c) 2021 ke.com, Inc. All Rights Reserved
# **************************************************************************
# * @function 单元测试(业务线功能说明,如:FAQ服务端)
# * @author wangqiwen004@test.com
# * @date 2021/11/24 17:35
# **************************************************************************

import unittest
# 导入待测试函数
from demo import add, minus

# def setUpModule():
#    pass

# def tearDownModule():
#    pass

class TestDemo(unittest.TestCase):
    """Test mathfuc.py"""

    @classmethod
    def setUpClass(cls):
        # 所有测试用例公用的初始化
        print ("this setupclass() method only called once.\n")

    @classmethod
    def tearDownClass(cls):
        # 所有测试用例公用的销毁
        print ("this teardownclass() method only called once too.\n")

    def setUp(self):
        # 
        print ("do something before test : prepare environment.\n")

    def tearDown(self):
        print ("do something after test : clean up.\n")

    def test_add(self):
        """Test method add(a, b)"""
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))

    def test_minus(self):
        """Test method minus(a, b)"""
        self.assertEqual(1, minus(3, 2))
        self.assertNotEqual(1, minus(3, 2))

    @unittest.skip("do't run as not ready")
    def test_minus_with_skip(self):
        """Test method minus(a, b)"""
        self.assertEqual(1, minus(3, 2))
        self.assertNotEqual(1, minus(3, 2))

if __name__ == '__main__':
    # verbosity=*:默认是1;设为0,则不输出每一个用例的执行结果;2-输出详细的执行结果
    # ----- 运行所有用例 ------
    unittest.main(verbosity=1) 
    # ------ 运行指定用例 ------
    suite = unittest.TestSuite() # 实例化用例集合
    suite.addTest(TestDemo("test_add")) #把testsub1测试用例添加到用例集
    suite.addTest(TestDemo("test_minus"))
    runner = unittest.TextTestRunner() #实例化运行类
    runner.run(suite) #运行用例集,会根据添加的用例顺序进行用例的执行
    import os
    # ----- 通过路径自动识别测试用例 -------
    test_dir = os.path.join(os.path.dirname(os.path.realpath(__file__))) # 获取当前工作目录
    # 用discover的方法去覆盖所有的测试用例
    #  test_dir 要执行的测试用例目录
    #  test*表示匹配以test开头的所有.py文件
    #  top_level_dir=None 测试模块的顶级目录,没有的话就写None
    discover = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py", top_level_dir=None)
    runner = unittest.TextTestRunner() # 实例化runner类
    runner.run(discover) # 执行路径中匹配的所有test*.py用例

test_demo_module.py文件

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import HTMLReport
import unittest
import test_demo_class
from test_demo_class import TestDemo


if __name__ == '__main__':
    paras = sys.argv[1:]
    args = paras[0]
    report = paras[1]

    suite = unittest.TestSuite()
    if args == 'test': # 逐个测试
        tests = [TestDemo("test_minus"), TestDemo("test_add"), TestDemo("test_minus_with_skip")]
        suite.addTests(tests)
    elif args == 'tests': # 批量测试
        suite.addTest(TestDemo("test_minus"))
        suite.addTest(TestDemo("test_add"))
        suite.addTest(TestDemo("test_minus_with_skip"))
    elif args == 'class': # 测试类
        suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestDemo))
    elif args == 'module': # 测试函数
        suite.addTests(unittest.TestLoader().loadTestsFromModule(test_demo_class))
    elif args == 'mix':
        suite.addTests(unittest.TestLoader().loadTestsFromName('test_demo_class.TestDemo.test_minus'))
    elif args == 'mixs':
        suite.addTests(unittest.TestLoader().loadTestsFromNames(['test_demo_class.TestDemo.test_minus', 'test_demo_class.TestDemo', 'test_demo_class']))
    elif args == 'discover': # 自动识别目录里的单测文件
        suite.addTests(unittest.TestLoader().discover('.', 'test_*.py', top_level_dir=None))

    if report == 'terminal': # 终端测试
        runner = unittest.TextTestRunner(verbosity=1)
        runner.run(suite)
    elif report == 'txt': # 读文件
        with open('ut_log.txt', 'a') as fp:
            runner = unittest.TextTestRunner(stream=fp, verbosity=1)
            runner.run(suite)
    elif report == 'html': # 生成测试报告
        runner = HTMLReport.TestRunner(report_file_name='test',
                               output_path='report',
                               title='测试报告',
                               description='测试描述',
                               sequential_execution=True
                               )
        runner.run(suite)

示例:web服务测试

待测试代码:demo1_login.py

# !/usr/bin/env python
# -*- coding:utf8 -*- 

# **************************************************************************
# * Copyright (c) 2021 ke.com, Inc. All Rights Reserved
# **************************************************************************
# * @function 单元测试(业务线功能说明,如:FAQ服务端)
# * @author wangqiwen004@test.com
# * @date 2021/11/24 17:35
# **************************************************************************

from flasgger import Swagger
from flask import Flask, request, redirect

app = Flask(__name__)
Swagger(app)

@app.route('/login', methods=['POST'])
def login():
    """
        用户登录
    """
    username = request.form.get('username')
    password = request.form.get('password')
 
    # 判断参数是否为空
    if not all([username, password]):
        result = {
            "errcode": -2,
            "errmsg": "params error"
        }
        return jsonify(result)
 
    # a = 1 / 0
    # 如果账号密码正确
    # 判断账号密码是否正确
    if username == 'itheima' and password == 'python':
        result = {
            "errcode": 0,
            "errmsg": "success"
        }
        return jsonify(result)
    else:
        result = {
            "errcode": -1,
            "errmsg": "wrong username or password"
        }
        return jsonify(result)

单元测试代码:

# !/usr/bin/env python
# -*- coding:utf8 -*- 

# **************************************************************************
# * Copyright (c) 2021 ke.com, Inc. All Rights Reserved
# **************************************************************************
# * @function 单元测试(业务线功能说明,如:FAQ服务端)
# * @author wangqiwen004@test.com
# * @date 2021/11/24 17:35
# **************************************************************************

import json
import unittest
from demo1_login import app
 
class LoginTest(unittest.TestCase):
    """
        为登录逻辑编写测试案例
    """
 
    @classmethod
    def setUpClass(cls):
        # 所有测试用例公用的初始化
        print ("this setupclass() method only called once.\n")

    @classmethod
    def tearDownClass(cls):
        # 所有测试用例公用的销毁
        print ("this teardownclass() method only called once too.\n")
        
    def setUp(self):
        # 用例前置工作
        app.testing = True
        self.client = app.test_client()
        
    def tearDown(self):
        # 用例后续销毁
        print ("do something after test : clean up.\n")
 
    def test_empty_username_password(self):
        """测试用户名与密码为空的情况[当参数不全的话,返回errcode=-2]"""
        response = app.test_client().post('/login', data={})
        json_data = response.data
        json_dict = json.loads(json_data)
 
        self.assertIn('errcode', json_dict, '数据格式返回错误')
        self.assertEqual(json_dict['errcode'], -2, '状态码返回错误')
 
        # TODO 测试用户名为空的情况
 
        # TODO 测试密码为空的情况
 
    def test_error_username_password(self):
        """测试用户名和密码错误的情况[当登录名和密码错误的时候,返回 errcode = -1]"""
        response = app.test_client().post('/login', data={"username": "aaaaa", "password": "12343"})
        json_data = response.data
        json_dict = json.loads(json_data)
        self.assertIn('errcode', json_dict, '数据格式返回错误')
        self.assertEqual(json_dict['errcode'], -1, '状态码返回错误')
 
    def test03(self):
        """
            直接调用request包
        """
        url = 'https://api.apiopen.top/searchMusic'
        data = {
            "name":"芒种"
        }
        r = requests.post(url,data=data)
        a = '抖音'
        b = r.text
        try:
            self.assertIn(a,b,msg='\n抖音不存在芒种歌曲信息中')
        except Exception as msg:
            print('错误信息%s'%msg)
        print('第三个用例失败')
 
if __name__ == '__main__':
    unittest.main()

示例:数据库测试

单元测试代码:

#coding=utf-8
import unittest
from author_book import *
 
#自定义测试类,setUp方法和tearDown方法会分别在测试前后执行。以test_开头的函数就是具体的测试代码。
class DatabaseTestCase(unittest.TestCase):
    def setUp(self):
        app.config['TESTING'] = True
        app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@localhost/test0'
        self.app = app
        db.create_all()
 
    def tearDown(self):
        db.session.remove()
        db.drop_all()
 
    #测试代码
    def test_append_data(self):
        au = Author(name='xxxxx')
        bk = Book(info='python')
        db.session.add_all([au,bk])
        db.session.commit()
        author = Author.query.filter_by(name='itcast').first()
        book = Book.query.filter_by(info='python').first()
        #断言数据存在
        self.assertIsNotNone(author)
        self.assertIsNotNone(book)

测试报告

Testing Report

  • 终端报告: 如上terminal 分支
  • TXT报告: 如上txt 分支,当前目录会生成ut_log.txt文件
  • HTML 报告:如上html 分支,终端上打印运行信息同时会在当前目录生成report文件夹, 文件夹下有test.html和test.log文件 更多见Python 单元测试 - HTML report
    • 如果想要生成 HTML 格式的报告,那么就需要额外借助第三方库(如 HtmlTestRunner)来操作。

自动生成测试用例

【2021-12-10】对于n条测试用例,还需要写满篇的test_1 ,test_2么?

nose

nose 是一个第三方单元测试框架,它完全兼容 unittest,并且号称是一个更好用的测试框架。

用例编写方式除了编写继承于 unittest.TestCase 的测试类外,还可以编写成没有继承的测试类。

  • 没有继承 unittest.TestCase,将不能使用其内置的各类 assertXXX 方法,进而导致用例出错时无法获得更加详细的上下文信息。
from nose.tools import raises

class TestStringMethods:

    def test_upper(self):
        # nose 也支持定义函数来作为测试
        assert 'foo'.upper() == 'FOO'

    def test_isupper(self):
        assert 'FOO'.isupper()
        assert not 'Foo'.isupper()

    @raises(TypeError)
    def test_split(self):
        s = 'hello world'
        assert s.split() == ['hello', 'world']
        # check that s.split fails when the separator is not a string
        s.split(2)

示例:MapReduce任务

test目录结构:

  • bin:存储单测代码
    • 文件名:test_appsearch.py
  • case:测试用例
    • test:输入数据
    • check:期望结果
#!/usr/bin/env python
#-*- coding:gbk -*-
 
from shunit import *
from nose.tools import *
import sys
 
sys.path.append("../../bin/hadoop_jobs")
job_name = 'appsearch'
source_dir = "../../bin/hadoop_jobs/%s"%(job_name) # 源代码目录
data_dir = "../case/%s"%(job_name)   # 测试用例目录
call_cmd("touch "+source_dir+"/__init__.py")
 
exec 'from %s import mapper'%(job_name)

#-----setUp 和 teardown为总控函数,函数的执行顺序为setup 单测函数1 ... 单测函数n  teardown---
 
def setUp():
    call_cmd("[ ! -e "+data_dir+"/test ]&&mkdir "+data_dir+"/test")
    pass    ####本函数在测试函数执行前进行一些总体的初始化的工作###

def tearDown():
    #print os.getcwd()
    call_cmd("rm *.pyc diff file*")
    # 创建test目录
    call_cmd("rm "+source_dir+"/__init__.py "+source_dir+"/*pyc")
    pass    ####本函数在测试函数执行后进行一些总体的清理的工作###
 
#--------------------------------------------------------------------------------------------
 
def test_mapper():
    # 准备环境变量
    #sys.argv=['','cookie','mergeOneDay'] # 用于区分数据源的匹配字符串
    input=open(data_dir+"/check/webmarket.txt",'r')
    output=open(data_dir+"/test/mapper1.txt",'w')
    # 调用主函数
    ret = mapper.main(input,output)
    output.flush();output.close();input.close()
    # 检查结果
    runDiffIsNotExist(data_dir+"/check/mapper1.txt",data_dir+"/test/mapper1.txt","diff")
 
def test_mapper_sort():
    cmd1="cat "+data_dir+"/test/mapper?.txt | sort > "+data_dir+"/test/mapper_sort.txt" 
    (ret, stdout, stderr)=call_cmd(cmd1)
    # 检查结果
    runDiffIsNotExist(data_dir+"/check/mapper_sort.txt",data_dir+"/test/mapper_sort.txt","diff")

pytest用法

Python世界中最火的第三方单元测试框架pytest

主要特性:

  • assert 断言失败时输出详细信息(再也不用去记忆 self.assert* 名称了)
  • 自动发现 测试模块和函数
  • 模块化夹具 用以管理各类测试资源
  • unittest 完全兼容,对 nose基本兼容
  • 非常丰富的插件体系,有超过 315 款第三方插件,社区繁荣

同 nose 一样,pytest 支持函数、测试类形式的测试用例。最大的不同点是,你可以尽情地使用 assert 语句进行断言,丝毫不用担心它会在 nose 或 unittest 中产生的缺失详细上下文信息的问题。

  • [2020-11-6] 经验总结, 提交代码前,执行单测,确保代码功能无误
  • 主目录下执行命令: pytest使用笔记(一)
    • pytest # 整体报告
    • pytest -q # 运行简单模式测试报告
    • pytest -x # 在第一个测试用例发生错误时就停止运行
    • pytest –maxfail=2 # 在第2个测试用例发生错误时就停止运行
    • pytest test_mod.py # 执行具体文件
    • pytest test # 运行单个文件的用例
    • pytest -k “TestClass” # 运行某些包含关键字的用例,如包含TestClass的用例
    • pytest test_class.py::TestClass # 运行某一文件内特定模块的用例
    • pytest -m slow # 运行用@ pytest.mark.slow装饰器修饰的用例

示例

import pytest

def test_upper():
    assert 'foo'.upper() == 'FOO1'

class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        with pytest.raises(TypeError):
            x + []

而当使用 pytest 去执行用例时,它会输出详细的(且是多种颜色)上下文信息. pytest 既输出了测试代码上下文,也输出了被测变量值的信息。相比于 nose 和 unittest,pytest 允许用户使用更简单的方式编写测试用例,又能得到一个更丰富和友好的测试结果。

unittest 和 nose 所支持的用例发现和执行能力,pytest 均支持。 pytest 支持用例自动(递归)发现:

  • 默认发现当前目录下所有符合 test_*.py 或 *_test.py 的测试用例文件中,以 test 开头的测试函数或以 Test 开头的测试类中的以 test 开头的测试方法
  • 使用 pytest 命令
  • 同 nose2 的理念一样,通过在配置文件中指定特定参数,可配置用例文件、类和函数的名称模式(模糊匹配)

pytest 也支持执行指定用例:

# 指定测试文件路径
pytest /path/to/test/file.py
# 指定测试类
pytest /path/to/test/file.py:TestCase
# 指定测试方法
pytest another.test::TestClass::test_method
# 指定测试函数
pytest /path/to/test/file.py:test_function

pytest 的测试夹具(Fixtures)和 unittest、nose、nose2的风格迥异,它不但能实现 setUp 和 tearDown这种测试前置和清理逻辑,还其他非常多强大的功能。

  • 声明和使用
  • 共享
  • 生效级别,pytest 的测试夹具同样支持各类生效级别,且更加丰富。通过在 pytest.fixture 中指定 scope 参数来设置:
    • function —— 函数级,即调用每个测试函数前,均会重新生成 fixture
    • class —— 类级,调用每个测试类前,均会重新生成 fixture
    • module —— 模块级,载入每个测试模块前,均会重新生成 fixture
    • package —— 包级,载入每个包前,均会重新生成 fixture
    • session —— 会话级,运行所有用例前,只生成一次 fixture

测试结果输出

pytest 的测试结果输出相比于 unittest 和 nose 来说更为丰富,其优势在于:

  • 高亮输出,通过或不通过会用不同的颜色进行区分
  • 更丰富的上下文信息,自动输出代码上下文和变量信息
  • 测试进度展示
  • 测试结果输出布局更加友好易读

robot framework

  • 待添加

结束


支付宝打赏 微信打赏

~ 海内存知已,天涯若比邻 ~

Share

Related Posts

上一篇 Python技能

标题:Python技能

摘要:Python开发相关技术知识点

标题:互联网人的职业发展之路-Future for the tech

摘要:陈皓的心得,技术人员如何规划未来,中年危机如何破解?值得深思,反复研读

Comments

--disqus--

    Content
    My Moment ( 微信公众号 )
    欢迎关注鹤啸九天