一、项目架构
1.1 项目介绍
IHRM人力资源管理系统,包含组织管理,招聘管理等等模块。
技术栈
前端:以node.js为核心的vue.js前端技术生态架构
后端:SpringBoot+SpringCloud+SpringMVC+SpringData (Spring全家桶)
MySQL + Redis + RabbitMQ
1.2 项目架构
1.3 接口文档
二、接口文档解析
三、编写测试用例
3.1 登录接口测试用例
登录接口测试用例为单场景用例测试,因此数据应当考虑:正常数据、异常数据(类型错误、长度不足、长度过长、含有特殊符号)、参数异常(多参、少参、无参)
3.2 员工管理测试用例
注意:员工管理测试是业务流程测试,因此只需要将整个操作完成一遍,检查接口返回数据是否为期望值。
四、编写代码
4.1 测试项目结构
测试项目包含:
- api模块:封装api接口,包括employee.py、login.py,其中还包括app.py(包含host、header、logging)、utils.py(封装断言)
- scripts模块:测试业务场景代码(testLogin、testEmployee、testLoginParam、testLoginsuc)
- data:放测试数据
- files:放用例以及接口文档等文件
- log:日志
- reports:测试报告
- tools:工具包:HtmlRunnerTest.py
- run_suite.py:执行测试套件,生成测试报告
4.2 log日志文件封装
import logging
import os
# app.py 初始化日志函数
# 获取当前项目路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def get_log():
# 创建日志对象
logger = logging.getLogger()
# 设置日志等级
logger.setLevel(logging.INFO)
#创建handler
now = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
file_name = BASE_DIR + now + '/.log'
sh = logging.StreamHandler(filename = file_name,encording = 'utf-8')
fh = logging.FileHandler(filename = file_name,encording = 'utf-8')
#创建日志格式
fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - % (message)s'
formatter = logging.Formatter(fmt)
#将日志格式添加至handler
fh.setFormatter(formatter)
fh.setFormatter(formatter)
#将handler添加至日志对象
logger.addHandler(fh)
logger.addHandler(sh)
4.3 添加项目信息
import logging
import os
# app.py 初始化日志函数
# 获取当前项目路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def get_log():
# 创建日志对象
logger = logging.getLogger()
# 设置日志等级
logger.setLevel(logging.INFO)
#创建handler
now = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
file_name = BASE_DIR + now + '/.log'
sh = logging.StreamHandler(filename = file_name,encording = 'utf-8')
fh = logging.FileHandler(filename = file_name,encording = 'utf-8')
#创建日志格式
fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - % (message)s'
formatter = logging.Formatter(fmt)
#将日志格式添加至handler
fh.setFormatter(formatter)
fh.setFormatter(formatter)
#将handler添加至日志对象
logger.addHandler(fh)
logger.addHandler(sh)
[HOST]
HOST = "http://ihrm2-test.itheima.net/api/sys/"
[TOKEN]
TOKEN = None
[HEADERS]
HEADERS = {"Content-type":"Aplication/json",
"Authorization":TOKEN
}
添加请求信息、以及员工管理所需要的token。
4.4 添加api._init_.py
该模块下整体首先执行
import logging
import app
app.get_logger()
logging.info("TEST日志器能不能正常工作")
4.5 编写登录案例
4.5.1 登录接口
在api.login.py下封装接口信息以及登录函数
import requests
class LogingAPI:
# 初始化登录
def __init__(self):
url = app.HOST + "login"
# 登录函数
def login(self,data):
# 如果要调用同一个类中的函数值,则使用self.变量名
responce = requests.post(url = self.url,json = data)
if __name__ == "__main__":
LoginAPI().login({"mobile": "13800000002", "password": "123456"})
print(responce)
运行结果:
4.5.2 登录用例
在scripts下建立testLogin.py
import logging
import unittest
import app
from login import LoginAPI
from utils import assert_common
class TestLogin:
# 前置处理
@classmethod
def setUpclass(cls)->None:
cls.login_api = LoginAPI()
@classmethod
def tearDownclass(cls)->None:
pass
# 测试用例
# 登陆成功
def test_01_loginSuc(self):
responce = self.login_api.login({"mobile": "13800000002", "password": "123456"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(True,jsondata.get("success"))
self.assertEqual(10000,jsondata.get("code"))
self.assertEqual("操作成功!",jsondata.get("message"))
# 提取token
token = "Bearer " + jsondata.get("data")
app.TOKEN = token
print(token)
print(app.TOKEN)
# 手机号为空
def test_02_empmobile(self):
responce = self.loging_api.login({"mobile": "", "password": "123456"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
# 密码为空
def test_03_emppwd(self):
responce = self.login_api.login({"mobile": "13800000002", "password": ""})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
# 手机号大于11位
def test_04_moremob(self):
responce = self.login_api.login({"mobile": "138000000021", "password": "123456"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
# 手机号小于11位
def test_05_lessmob(self):
responce = self.login_api.login({"mobile": "1380000000", "password": "123456"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
# 手机号含有字母
def test_06_mobcontainsletter(self):
responce = self.login_api.login({"mobile": "1380000000m2", "password": "123456"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
# 手机号含有空格
def test_07_mobcontainsspace(self):
responce = self.login_api.login({"mobile": "138000000 2", "password": "123456"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
# 手机号未注册
def test_08_unregister(self):
responce = self.login_api.login({"mobile": "13812321236", "password": "123456"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
# 密码错误
def test_09_errorpwd(self):
responce = self.login_api.login({"mobile": "1380000002", "password": "12345"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
# 多参
def test_10_moreparam(self):
responce = self.login_api.login({"mobile": "1380000002", "password": "123456",""xx":"aa"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(True,jsondata.get("success"))
self.assertEqual(10000,jsondata.get("code"))
self.assertEqual("操作成功",jsondata.get("message"))
# 少参-登录
def test_11_lessmobpram(self):
responce = self.login_api.login({"password": "123456"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
# 少参-密码
def test_12_lesspwdpram(self):
responce = self.login_api.login({"mobile": "1380000002"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
# 无参
def test_13_nopram(self):
responce = self.login_api.login({})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(False,jsondata.get("success"))
self.assertEqual(20001,jsondata.get("code"))
self.assertEqual("用户名或密码错误",jsondata.get("message"))
4.6 封装断言
在app模块下的utils.py中进行封装
def assert_common(self,responce,http_code,success,code,message):
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.asserEqual(success,jsondata.get("success")
self.asserEqual(code,jsondata.get("code")
self.asserEqual(message,jsondata.get("message")
对TestLogin进行优化
import logging
import unittest
import app
from login import LoginAPI
from utils import assert_common
class TestLogin:
# 前置处理
@classmethod
def setUpclass(cls)->None:
cls.login_api = LoginAPI()
@classmethod
def tearDownclass(cls)->None:
pass
# 测试用例
# 登陆成功
def test_01_loginSuc(self):
responce = self.login_api.login({"mobile": "13800000002", "password": "123456"})
jsondata = responce.json()
assert_common(responce,200,True,1000,"操作成功!")
# 提取token
token = "Bearer " + jsondata.get("data")
app.TOKEN = token
print(token)
print(app.TOKEN)
# 手机号为空
def test_02_empmobile(self):
responce = self.loging_api.login({"mobile": "", "password": "123456"})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
# 密码为空
def test_03_emppwd(self):
responce = self.login_api.login({"mobile": "13800000002", "password": ""})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
# 手机号大于11位
def test_04_moremob(self):
responce = self.login_api.login({"mobile": "138000000021", "password": "123456"})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
# 手机号小于11位
def test_05_lessmob(self):
responce = self.login_api.login({"mobile": "1380000000", "password": "123456"})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
# 手机号含有字母
def test_06_mobcontainsletter(self):
responce = self.login_api.login({"mobile": "1380000000m2", "password": "123456"})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
# 手机号含有空格
def test_07_mobcontainsspace(self):
responce = self.login_api.login({"mobile": "138000000 2", "password": "123456"})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
# 手机号未注册
def test_08_unregister(self):
responce = self.login_api.login({"mobile": "13812321236", "password": "123456"})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
# 密码错误
def test_09_errorpwd(self):
responce = self.login_api.login({"mobile": "1380000002", "password": "12345"})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
# 多参
def test_10_moreparam(self):
responce = self.login_api.login({"mobile": "1380000002", "password": "123456",""xx":"aa"})
jsondata = responce.json()
assert_common(responce,200,True,1000,"操作成功!")
# 少参-登录
def test_11_lessmobpram(self):
responce = self.login_api.login({"password": "123456"})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
# 少参-密码
def test_12_lesspwdpram(self):
responce = self.login_api.login({"mobile": "1380000002"})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
# 无参
def test_13_nopram(self):
responce = self.login_api.login({})
jsondata = responce.json()
assert_common(self,response,200,False,20001,"用户名或密码错误")
4.7 参数化TestLoginPara
4.7.1 参数文件
文件位json格式放在data目录下:其中包含登录数据以及断言数据
[
{
"desc": "登陆成功",
"login_data": {
"mobile": "13800000002",
"password": "123456"
},
"http_code": 200,
"success":true,
"code":10000,
"message":"操作成功!"
},
{
"desc": "用户名为空",
"login_data": {
"mobile": "",
"password": "123456"
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
},
{
"desc": "密码为空",
"login_data": {
"mobile": "13800000002",
"password": ""
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
},
{
"desc": "用户名大于11位",
"login_data": {
"mobile": "138000000021",
"password": "123456"
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
},
{
"desc": "用户名小于11位",
"login_data": {
"mobile": "1380000000",
"password": "123456"
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
},
{
"desc": "用户名非数字",
"login_data": {
"mobile": "138000000m2",
"password": "123456"
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
},
{
"desc": "用户名含空格",
"login_data": {
"mobile": "13800000 002",
"password": "123456"
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
},
{
"desc": "用户名未注册",
"login_data": {
"mobile": "13812321236",
"password": "123456"
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
},
{
"desc": "密码错误",
"login_data": {
"mobile": "13800000002",
"password": "12345"
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
},
{
"desc": "多参",
"login_data": {
"mobile": "13800000002",
"password": "123456",
"xx": "aa"
},
"http_code": 200,
"success":true,
"code":10000,
"message":"操作成功!",
"data":null
},
{
"desc": "少参--用户名",
"login_data": {
"password": "123456"
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
},
{
"desc": "少参--密码",
"login_data": {
"mobile": "13800000002"
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
},
{
"desc": "无参",
"login_data": {
},
"http_code": 200,
"success":false,
"code":20001,
"message":"用户名或密码错误",
"data":null
}
]
4.7.3 TestLoginPara
import json
import unittest
from scripts.testLoginsuc import LoginAPI
from parameterized import parameterized
from utils import assert_common
def build_data(self):
file_path = '../data/login_data.json'
data = []
with open(file_path,encording = "utf-8") as f:
json_data = json.load(f)
for i in json_data:
login_data = json_data.get("login_data")
http_code = json_data.get("http_code")
success = json_data.get("success")
code = json_data.get("code")
messsage = json_data.get("message")
data.append((login_data,http_code,success,code,message))
return data
class TestLoginParam:
@classmethod
def setUpclass(cls)->None:
cls.login_api = LoginAPI()
@classmethod
def tearDownclass(cls)->None:
pass
@parameterized.expand(build_data)
def test_01_login(self,login_data,http_code,success,code,message)):
responce = login_api.login(login_data)
print(responce.json())
assert_commoc(login_data,http_code,success,code,message)
运行结果为:
4.8 员工管理用例
4.8.1 TestLoginsue.py
员工管理必须在登陆后获得TOKEN之后才能进行操作,必须先封装登录成功步骤,封装在scripts目录下,执行员工管理测试用例前首先执行该测试。
import logging
import unittest
import app
from login import LoginAPI
from utils import assert_common
class TestLogin:
# 前置处理
@classmethod
def setUpclass(cls)->None:
cls.login_api = LoginAPI()
@classmethod
def tearDownclass(cls)->None:
pass
# 测试用例
# 登陆成功
def test_01_loginSuc(self):
responce = self.login_api.login({"mobile": "13800000002", "password": "123456"})
jsondata = responce.json()
self.assertEqual(200,responce.status_code)
self.assertEqual(True,jsondata.get("success"))
self.assertEqual(10000,jsondata.get("code"))
self.assertEqual("操作成功!",jsondata.get("message"))
# 提取token
token = "Bearer " + jsondata.get("data")
app.TOKEN = token
print(token)
print(app.TOKEN)
4.8.2 员工管理接口
在api模块下封装员工管理接口于employee.py文件中
import requests
import app
class EmployeeAPI:
def __init__(self):
self.header = app.HEADER
self.add_url = app.HOST + 'user'
self.get_url = app.HOST + 'user/{}'
# 添加员工
def add_emp(self,username,password):
data = {
"username":username,
"password":password,
"timeOfEntry": "2019-12-02",
"formOfEmployment": 1,
"workNumber": "1234",
"departmentName": "测试",
"departmentId": "1210411411066695680",
"correctionTime": "2019-12-15T16:00:00.000Z"
}
responce = requests.post(url = self.add_url,json=data,header=self.header)
return responce
# 查看员工
def get_emp(self,id):
recponce = requests.get(url = self.get_url.format(id),header = self.header)
return = responce
# 修改员工
def update_emp(self,id,username):
data = {"username":username}
responce = requests.put(url = self.get_url.format(id),json = data,header = self.header)
# 添加员工
def del_emp(self,id):
responce = requests.delete(url = self.get_url.format(id),header = self.haeder)
return responce
4.8.3 TestEmployee
在scripts模块下写员工管理测试用例
class TestEmployee:
@classmethod
def setUpclass(cls)->None:
cls.emlpoyee_api = EmployeeAPI()
@classmethod
def tearDown(cls)->None:
pass
id = None
def test_01_add_emp(self):
responce = self.employee_api.add_emp("熊猫44", "11116111111")
jsondata = responce.json()
assert_common(responce,200,True,10000,"操作成功!")
global id
id = jsondata.get("data").get("id") #声明为全局变量
print(id)
print(jsondata)
def test_02_get_emp(self):
responce = self.employee_api.get_emp(id = id )
jsondata = responce.json()
print(jsondata)
assert_common(responce,200,True,10000,"操作成功!")
def test_03_update_emp(self):
responce = self.employee_api.update_emp(id = id,username = "bb")
jsondata = responce.json()
assert_common(responce,200,True,10000,"操作成功!")
print(jsondata)
def test_04_delete_emp(self):
responce = self.employee_api.del_emp(id = id)
jsondata = responce.json()
print(jsondata)
assert_common(responce,200,True,10000,"操作成功!")
if __name__ == "__main__":
unittest.main()
4.9 run_suite
执行套件
4.9.1 HtmlTestRunner.py
先将该文件放入tools模块下
4.9.2 run_suite.py
将该文件放入根目录下,目的将所有的测试用例组装成测试套件并且将其加载执行。
import unittest
from scripts.testLogin import TestLogin
from scripts.testLoginparam import TestLoginParam
from scripts.testLoginsuc import TestLogin02
from scrpits.testEmplyee import TestEmployee
# 创建测试套件suite
suite = unittest.TestSuite()
# 添加测试用例
suite.addTest(unittest.makeSuite(TestLogin))
suite.addTest(unittest.makeSuite(TestLoginparam))
suite.addTest(unittest.makeSuite(TestLogin02))
suite.addTest(unittest.makeSuite(TestEmployee))
# 执行测试套件
report = "./report/report.html"
with open(report,"wb") as f:
runner = HTMLTestRunner(f,title = "API report")
runner.run(suite)
5 测试报告
6 注意事项
6.1 api
将模块api设置成test sources root,在导包的时候不会出现路径错误找不到文件的情况,但是注意在其他目录下导包的时候不用加api.
6.2 用例执行顺序
在run_suite.py中添加用例的顺序决定执行的顺序,每一个用例的名称的asccii码值决定执行顺序