大道至简

学必求其心得,业必贵于专精。

0%

Python+UnitTest接口自动化测试(从框架到报告)

知识必备:Python

Api 自动化测试框架

个人源码:https://github.com/wupeng-paynewinn/ApiTest_Unittest
整体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- ApiTest
* Common
+ A.py
+ BaseTest.py
+ Cache.py
+ Decorator.py
+ General.py
+ Third.py
+ __init__.py
* Config
+ config.ini
+ product-config.ini
+ test-config.ini
* Report
+ __init__.py
+ TestReport.html
* TestCase
+ test_01_02_login.py
+ ...
* TestDate
+ 01_02_login.json
+ ...
* readme.md
* requirements.txt
* run_all_case.py

一、Config 模块

1
2
3
|-- config.ini
|-- product-config.ini
|-- test-config.ini

在 config.ini 中的 [ENVIRONMENT] 块中定义环境、发件人、收件人、邮件主题,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[ENVIRONMENT]
environment=test

[SENDER]
smtpserver = mail.qq.com
user = xxx@qq.com
password = xxxxxx
sender = xxx@qq.com

[RECEIVER]
;receiver = xx1@xx.com, xx2@xx.com
receiver = xx@xx.com

[MSG]
subject = xx自动定时测试报告(生产api)

将会读取 test-config.ini 文件中的配置,由这种方式将配置文件与环境剥离开。

1
2
3
4
5
[HOST]
cn_https=https://xx.com.cn
cn_http=http://xx.com.cn
en_https=https://
en_http=http://

[HOST] 块中定义host,可以在 case.json 文件中使用,如:

1
2
3
4
5
6
7
8
9
10
11
{
"request": {
"host": "{{cn_https}}",
"api": "/info/api/v3/auth/post/login",
"method": "POST",
"header": {
"Content-Type": "application/x-www-form-urlencoded"
}
},
....
}

使用 {{}} 包裹,将会从config中解析。否则,当成字符串处理

二、case 定义形式

测试用例,以 json 文件的形式定义。主要包括一个 request 块,和多个 case 块。基本的形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"request": {
"host": "{{cn_https}}",
"api": "/info/api/v3/auth/post/login",
"method": "POST",
"header": {
"Content-Type": "application/x-www-form-urlencoded"
}
},
"case_01": {
"query":"test=1&test2=2",
"data": [
{
"key": "useragent",
"value": "android",
"type": "text"
}
],
"assert": [
{
"type": "eq",
"rules": "status",
"expect": 0,
"msg": "这里是报错显示的msg"
}
],
"set_result": [
{
"key": "cache_key",
"value": "payload.id"
}
]
}
}

request 对象中包含:

  • host:在config 中定义
  • api:接口地址,与host组成完整url
  • method:请求方式,支持POST与GET
  • header:对象,可填写多个键值对

case_01 为用例名称,一个json文件中可以含有多个用例。同一个文件下所有的用例,请求的内容都相同。

  • query: 字符串,按照”field1=value1&field2=value2”规则,解析结果将于data合并

  • data: 数组,按照method内容,post则放在body中发送,get则放在url中。数组中可放入多个对象。

    • key: 必填,参数名称。
    • value:必填,请求内容。
    • type:必填,类型:text/array/json/file
    • file_name: 非必填,type 为 file 时需要参数。文件名称。
    • file_type: 非必填,type 为 file 时需要参数。文件类型。例子:text/plain
    • from:非必填,数据来源。当数据来源于cache时,填cache。目前只支持cache
  • assert:数组,定义请求结果断言。可以含有多个对象,定义不同的断言,

    • type:断言类型,eq/not_eq等
    • rules:取值规则,返回数据中的结构 例如 payload.group_id
    • expect: 预期值,对比用
  • set_result:非必填 数组,需要保存在cache中的数据

    • key: 名称,注意唯一性
    • result_field:返回数据的结构 例如:payload.group_id

三、定义单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- coding: utf-8 -*-
import unittest
from Common.BaseTest import BaseTest

# 定义测试类
class TestLogin(BaseTest):
# 定义测试用例文件地址
json_file = "/TestData"

# 定义单个测试用例
def test_01_login(self):
# 定义需要执行的case名称
self.run_case("case_01")

def test_02_login(self):
self.run_case("case_02")
# 保存全部返回结果
self.set_result("286592399")

def test_03_create_group(self):
# 在用例中定义测试用例文件地址
self.set_json_file("/TestData")
# 定义单个参数
self.set_single_extra_body("code_list", "1112,1113")
self.set_single_extra_body("code", "1200")
self.run_case("case_01")


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

四、Cache 模块

为了方便用例间上下文联动,引入cache模块。可以将接口返回的结果保存在cache中,也可以从cache中取出所需要的内容放入到请求体中。

但需要注意的是,cache模块受用例执行顺序的影响,必须保证当前取值的用例在存值用例的后面执行。

使用:

  • 配合 json 文件:

    • 存值,在 {case_name}.set_result 块中定义需要保存的数据

      例如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      {
      "set_result": [
      {
      "key": "286592399_token",
      "result_field": "payload.token"
      },
      {
      "key": "286592399_all",
      "result_field": "*"
      }
      ]
      }
    • 取值,在 {case_name}.data 块中定义需要取值的数据

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "data": [
    {
    "key": "token",
    "value": "{{286592399_token}}",
    "type": "text"
    }
    ]
    }

    data 中使用 {{}} 将会从cache中取值

  • 配合 test 类:

    • 存值:

      使用 set_result 方法保存全部数据。

      1
      2
      self.run_case("case_02")
      self.set_result("286592399")

      这两个方法会将 case_02 的所有返回结果保存在286592399 键中。set_result 方法 必须在 run_case 之后执行。

      也可以写成:

      1
      self.run_case("case_02").set_result("286592399")
    • 取值:

      使用 get_result 方法从cache中取值。

      1
      self.get_cache(key="286592399",r="")

      其中,r 为取值规则。如果为空,则将 286592399 键中所有数据取出,如果需要取出指定结构下的数据,只需要给 r 设定规则,例如:

      1
      self.get_cache(key="286592399",r="payload.token")

五、给case请求体增加参数

  • 增加 header

    • set_extra_header

      1
      2
      self.set_extra_header({"test_header":"header","test_data2":"header"})

    • set_single_extra_header

      1
      self.set_single_extra_header(key="test_header",value="header")
  • 增加 body(post):

    ​ 将在body体中传输

    • set_extra_body

      1
      self.set_extra_body({"test_data":"data","test_data1":"data"})
    • set_single_extra_body

      1
      self.set_single_body(key="test_data",value="1")
  • 增加 params (get):

    ​ 将在url中传输

六、执行顺序问题

​ 在 run_all_case.py 文件中定义测试用例执行:

1
2
3
4
5
6
7
suite = unittest.TestSuite()
suite.addTest(TestVanishLogin('test_1_login_01'))
suite.addTest(TestVanishLogin('test_1_login_02'))
suite.addTest(TestVanishLogin('test_2_create_01'))
runner = unittest.TextTestRunner(verbosity=2)

runner.run(suite)

使用 addTest 手动增加测试用例,设置执行顺序。在执行用例很多的情况下,比较麻烦,也可以使用:

1
2
3
4
discover = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
#runner.run(discover)
runner = HTMLTestRunner(stream=fp, title=u'测试报告', description=u'用例执行情况:')
runner.run(discover)

使用自动 discover 功能,自动加载所有测试用例,但其加载执行的顺序是按照文件名和方法名排序的。

需要优先执行的文件可以将文件名命名为: test_1_1_1_vanish_account_login.py

文件内需要优先执行的用例可以将方法名命名为: test_1_1_account_login_01()

用名称中的数字用来控制用例执行顺序。

运行用例、取最新测试报告并发送邮件

需要导入python的HTMLTestRunner第三方模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# -*- coding: utf-8 -*-
import unittest
from HTMLTestRunner import HTMLTestRunner
import time
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
# import logging
# from tool import output_log
import urllib3
urllib3.disable_warnings()

run_os = 'linux'


# 2.定义:取最新测试报告
def new_file(test_dir):
# 列举test_dir目录下的所有文件,结果以列表形式返回。
lists = os.listdir(test_dir)
# sort按key的关键字进行排序,lambda的入参fn为lists列表的元素,获取文件的最后修改时间
# 最后对lists元素,按文件修改时间大小从小到大排序。
if run_os == 'linux':
lists.sort(key=lambda fn: os.path.getmtime(test_dir + '/' + fn)) # linux
else:
lists.sort(key=lambda fn: os.path.getmtime(test_dir + '\\' + fn)) # windows
# 获取最新文件的绝对路径
file_path = os.path.join(test_dir, lists[-1])
return file_path


# 3.定义:发送邮件,发送最新测试报告html
def send_email(newfile):
try:
# 打开文件
f = open(newfile, 'rb')
# 读取文件内容
mail_body = f.read()
# 关闭文件
f.close()
# 发送邮箱服务器
import configparser
if run_os == 'linux':
config_file = os.path.abspath('.') + '/Config/config.ini' # linux 运行全部接口
else:
config_file = os.path.abspath('.') + '\\Config\\config.ini' # windows 运行全部接口
# print "config_file :" + str(config_file)
config = configparser.ConfigParser()
config.read_file(open(config_file, encoding='UTF-8'))
user = config.get("SENDER", "user")
password = config.get("SENDER", "password")
smtpserver = config.get("SENDER", "smtpserver")
sender = config.get("SENDER", "sender")
receiver = config.get("RECEIVER", "receiver")
subject = config.get("MSG", "subject")
# 发送邮件主题
# subject = '自动定时测试报告(生产api)'+now
msg = MIMEMultipart('mixed')
msg_html1 = MIMEText(mail_body, 'html', 'utf-8')
msg.attach(msg_html1)
msg_html = MIMEText(mail_body, 'html', 'utf-8')
msg_html["Content-Disposition"] = 'attachment; filename="TestReport.html"'
msg.attach(msg_html)
msg['From'] = sender
# 多个收件人
msg['To'] = receiver
msg['Subject'] = Header(subject, 'utf-8')
# 连接发送邮件
# 2.7版本不需要往SMTP()里面加实参
smtp = smtplib.SMTP_SSL(smtpserver)
smtp.connect(smtpserver, 465)
#smtp.ehlo()
# smtp.starttls()
smtp.login(user, password)
smtp.sendmail(sender, receiver, msg.as_string())
smtp.quit()
except Exception as e:
print(e)


# 4.查找报告中是否有failtest(ft),find找不到则返回-1
def parse_html(new_report):
from bs4 import BeautifulSoup
with open(new_report, 'r', encoding='utf-8') as wb_data:
soup = BeautifulSoup(wb_data, 'html.parser')
return str(str(soup).find('id="ft'))


if __name__ == '__main__':
try:
# 1.执行测试用例,生成最新的测试用例
now = time.strftime('%Y-%m-%d_%H_%M_%S')

test_dir = os.path.abspath('./TestCase')
print(test_dir)
discover = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
# 测试报告的路径
if run_os == 'linux':
test_report_dir = os.path.abspath('.') + '/Report' # linux
filename = test_report_dir + '/' + now + '_result.html'
else:
test_report_dir = os.path.abspath('.') + '\\Report' # windows
filename = test_report_dir + '\\' + now + '_result.html'
fp = open(filename, 'wb')
runner = HTMLTestRunner(stream=fp, title=u'测试报告', description=u'用例执行情况:')
runner.run(discover)
fp.close()
# 2.取最新测试报告
new_report = new_file(test_report_dir)
print("测试报告路径:%s" % new_report)

# 3.如果有运行失败的case,发送邮件,发送最新测试报告html
if parse_html(new_report) != "-1":
send_email(new_report)
print("Email sent successfully...")
elif parse_html(new_report) == "-1":
print("All passed, no need to send email...")

except Exception as e:
print(e)

Unittest、Github、Jenkins持续化集成

一、Jenkins服务搭建

1、linux搭建jenkins

1
2
3
4
5
war部署:
1.下载安装包jenkins.war;
2.在安装包根路径下,运行命令 java -jar jenkins.war --httpPort=8080
3.打开浏览器进入链接 http://ip地址:8080
4.填写初始密码,激活系统
1
2
#默认密码:
cat /var/lib/jenkins/secrets/initialAdminPassword
1
2
3
4
5
6
7
8
9
docker部署:
下载镜像:docker pull jenkins/jenkins
查看本地镜像:docker images
镜像实例化:
mkdir -p /var/jenkins_node
chmod -R 777 /var/jenkins_node
docker run -d -uroot -p 80:8080 --name jenkins -v /var/jenkins_node:/var/jenkins_home jenkins/jenkins
查看容器是否运行:docker ps
进入容器:docker exec -it -uroot jenkins /bin/bash

2、linux搭建Git环境

1
2
3
4
5
6
7
yum install git		#安装git
git version #验证是否安装
mkdir /xx/xx #创建目录
cd /xx/xx
git init #初始化仓库
git remote add origin +地址 #连接到git
git pull origin +分支 #拉取代码

二、构建项目

1、配置Git代码拉取

1
2
3
源码管理:选择项目源码地址,
选择git,输入仓库地址,
然后添加Credentials用户校验

2、设置定时任务

构建触发器选择:定时构造(下面是15分钟运行一次)
每行包含5个字段,依次为分钟、小时、日、月、星期几

1
H/15 * * * *

3、构建

1
2
3
cd /home/paynewinn/ApiTest_Chinamobile
git pull --rebase origin master
python3 /home/paynewinn/ApiTest_Chinamobile/run_all_case.py

完成后点击应用即可