接口测试之代码项目实战

代码项目实战

文章目录

  • 代码项目实战
    • 面试题:代码实现接口测试和工具实现接口测试的区别?
    • 接口自动化框架代码实现顺序
    • 1,初始化项目
    • 2,初始化日志
    • 3,登陆模块接口测试
      • 1 封装IHRM登陆接口
      • 2 实现IHRM登陆接口测试用例
      • 3 优化断言
      • 4 实现剩余登陆测试用例代码
      • 5 使用测试套件批量运行登陆接口测试脚本生成测试报告
      • 6 使用Parameterized实现参数化
    • 4,员工模块接口测试
      • 1,新增login.py文件,编写登陆代码并保存全局变量
      • 2,封装添加员工接口
      • 3, 实现添加员工接口测试用例脚本
      • 4, 使用run_suite.py执行员工模块的相关测试用例
      • 5,封装查询员工接口的准备
      • 6,封装查询员工接口
      • 7,实现查询员工接口测试用例
      • 8,完成剩余修改和删除员工接口的封装
      • 9 完成修改和删除员工接口测试用例
      • 10 在run_suite.py中运行,实现员工的增删改查业务场景测试
      • 11 参数化员工管理模块
      • 12 封装数据库工具类
      • 13 在run_suite.py中执行,查看结果
      • 14 修改run_suite.py代码,把登陆的测试用例添加到测试套件中
      • 15 在run_suite.py中运行登陆和员工模块的测试用例,查看结果

面试题:代码实现接口测试和工具实现接口测试的区别?

优点:

工具:工具使用简单,学习成本低;

代码:灵活,逼格比较高(注意:也可以面试管说逼格比较高。还有面试时,可以夸讲面试官,说他问题问得好)

缺点:

工具:不灵活,

代码:学习成本比较高

什么时候选择工具,什么时候选择代码?

如果项目中,定制化需求比较少,测试人员编码水平不高时,可以选择工具,例如:postman、jmeter等

如果项目中,定制化需求比较多,测试人员编码水平都还可以时,可以选择代码,例如:python+requests+unittest

接口自动化框架代码实现顺序

  • 实现顺序:从下往上,从里往外
  • 设计顺序:从外到内,从上往下
  • 核心功能模块:api,script,data
  • 设计顺序:script -> api -> data
  • 实现顺序:data -> api -> script

1,初始化项目

创建项目目录结构

接口测试之代码项目实战_第1张图片

安装依赖包

  • requests
  • pymysql
  • parameterzied
  • HTMLTestRunner

requests,pymysql,parameterized在File->Setting->Project Intepreter->点击+号 进行安装

接口测试之代码项目实战_第2张图片

2,初始化日志

  • 1 编写日志的代码:写在app.py中

    • 日志的等级:5个等级(了解)

      低于日志等级的日志都不会打印

      • DEBUG
      • INFO
      • WARN
      • ERROR
      • CRITICAL
#app.py 初始化日志的函数代码
# 获取当前项目路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# 初始化日志函数
def init_logging():
    # 创建日志器
    logger = logging.getLogger()
    # 设置日志等级
    logger.setLevel(logging.INFO)
    # 设置处理器
    # 设置控制台处理器
    sh = logging.StreamHandler()
    # 设置文件处理器
    # TimedRotatingFileHandler 可以用来帮助我们切分日志:按照时间来设置日志
    filename = BASE_DIR + "/log/ihrm.log"
    fh = logging.handlers.TimedRotatingFileHandler(filename, when='M', interval=1, backupCount=7)
    # 设置格式化器
    fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
    formatter = logging.Formatter(fmt)
    # 将格式化器添加到处理器当中
    sh.setFormatter(formatter)
    fh.setFormatter(formatter)
    # 将处理器添加到日志器
    logger.addHandler(fh)
    logger.addHandler(sh)
  • 2 初始化日志
#api.__init__.py
import app
import logging

# 初始化日志
# 为什么要在api.__init__.py中初始化日志呢?
# 这是因为,我们后面进行接口测试时,都会调用封装的API接口,调用时,会自动运行__init__.py函数,初始化日志器,从而实现,自动初始化日志的功能
app.init_logging()

logging.info("TEST日志器能不能正常工作")

3,登陆模块接口测试

1 封装IHRM登陆接口

  • app.py中设置全局属性

接口测试之代码项目实战_第3张图片

#app.py部分代码
HOST = "http://182.92.81.159"
HEADERS = {"Content-Type":"application/json"}

2 实现IHRM登陆接口测试用例

  • 在script中新增test_login.py文件,用于实现登陆接口测试用例

    • 先实现第一个测试用例:登陆成功
    #scritp.test_login.py
    import unittest,logging
    from api.login_api import LoginApi
    
    class TestIHRMLogin(unittest.TestCase):
        def setUp(self) -> None:
            pass
    
        @classmethod
        def setUpClass(cls) -> None:
            # 初始化登陆类
            cls.login_api = LoginApi()
    
        def tearDown(self) -> None:
            pass
    
        @classmethod
        def tearDownClass(cls) -> None:
            pass
    
    
        def test01_login_success(self):
            # 调用封装的登陆接口
            response = self.login_api.login('13800000002','123456')
            # 接收返回的json数据
            jsonData = response.json()
            # 调试输出登陆接口返回的数据,日志输出只能用作为{}占位符
            logging.info("登陆成功接口返回的数据为: {}".format(jsonData))
    
            # 断言
            self.assertEqual(200, response.status_code) # 断言响应状态码
            self.assertEqual(True, jsonData.get("success")) # 断言success
            self.assertEqual(10000, jsonData.get("code")) # 断言code
            self.assertIn("操作成功", jsonData.get("message")) # 断言message
    
    
    

    3 优化断言

    分析登陆成功的代码,发现登陆成功中,断言有4行

    # 断言
    self.assertEqual(200, response.status_code) # 断言响应状态码
    self.assertEqual(True, jsonData.get("success")) # 断言success
    self.assertEqual(10000, jsonData.get("code")) # 断言code
    self.assertIn("操作成功", jsonData.get("message")) # 断言message
    

    这4行中可以考虑利用封装技术优化成一行

    封装:减少代码冗余,

    思路:1.我们的断言代码中,self,response,都是高度重复的代码,可以优化。

    2.剩余4个预期结果,都是会动态变化的,所以也可以优化

    优化方式:把self,response,4个断言数据作为参数传递给通用断言类处理。

    实现代码:通用断言工具类写在utils.py中

    # utils.py
    # 编写断言代码函数
    # 可能的问题:self,response为什么可以作为参数传递?
    # 答:这是因为self,response都是对象
    def assert_common(self, response, http_code, success, code, message):
        self.assertEqual(http_code, response.status_code)  # 断言响应状态码
        self.assertEqual(success, response.json().get("success"))  # 断言success
        self.assertEqual(code, response.json().get("code"))  # 断言code
        self.assertIn(message, response.json().get("message"))  # 断言message
    

    优化后再改写登陆成功案例,至此断言优化完毕

    # script.test_login.py
    import unittest,logging
    from api.login_api import LoginApi
    from utils import assert_common
    
    class TestIHRMLogin(unittest.TestCase):
        def setUp(self) -> None:
            pass
    
        @classmethod
        def setUpClass(cls) -> None:
            # 初始化登陆类
            cls.login_api = LoginApi()
    
        def tearDown(self) -> None:
            pass
    
        @classmethod
        def tearDownClass(cls) -> None:
            pass
    
    
        def test01_login_success(self):
            # 调用封装的登陆接口
            response = self.login_api.login('13800000002','123456')
            # 接收返回的json数据
            jsonData = response.json()
            # 调试输出登陆接口返回的数据,日志输出只能用作为{}占位符
            logging.info("登陆成功接口返回的数据为: {}".format(jsonData))
    
            # 断言
            # self.assertEqual(200, response.status_code) # 断言响应状态码
            # self.assertEqual(True, jsonData.get("success")) # 断言success
            # self.assertEqual(10000, jsonData.get("code")) # 断言code
            # self.assertIn("操作成功", jsonData.get("message")) # 断言message
    
            assert_common(self,
                          response,
                          200, True, 10000, "操作成功")
    

    4 实现剩余登陆测试用例代码

    import unittest,logging
    from api.login_api import LoginApi
    from utils import assert_common
    
    class TestIHRMLogin(unittest.TestCase):
        def setUp(self) -> None:
            pass
    
        @classmethod
        def setUpClass(cls) -> None:
            # 初始化登陆类
            cls.login_api = LoginApi()
    
        def tearDown(self) -> None:
            pass
    
        @classmethod
        def tearDownClass(cls) -> None:
            pass
    
    
        def test01_login_success(self):
            # 调用封装的登陆接口
            response = self.login_api.login('13800000002','123456')
            # 接收返回的json数据
            jsonData = response.json()
            # 调试输出登陆接口返回的数据,日志输出只能用作为{}占位符
            logging.info("登陆成功接口返回的数据为: {}".format(jsonData))
    
            # 断言
            # self.assertEqual(200, response.status_code) # 断言响应状态码
            # self.assertEqual(True, jsonData.get("success")) # 断言success
            # self.assertEqual(10000, jsonData.get("code")) # 断言code
            # self.assertIn("操作成功", jsonData.get("message")) # 断言message
    
            assert_common(self,
                          response,
                          200, True, 10000, "操作成功")
    
        def test02_username_is_not_exist(self):
            # 调用封装的登陆接口
            response = self.login_api.login('13900000002','123456')
            # 接收返回的json数据
            jsonData = response.json()
            # 输出
            logging.info("账号不存在时输出的数据为: {}".format(jsonData))
            # 断言
            assert_common(self, response, 200, False, 20001, "用户名或密码错误")
    
        def test03_password_is_error(self):
            # 调用封装的登陆接口
            response = self.login_api.login('13800000002','error')
            # 接收返回的json数据
            jsonData = response.json()
            # 输出
            logging.info("密码错误时输出的数据为: {}".format(jsonData))
            # 断言
            assert_common(self, response, 200, False, 20001, "用户名或密码错误")
    
        def test04_username_have_special_char(self):
            # 调用封装的登陆接口
            response = self.login_api.login('!@#$%^&*()_-+=','error')
            # 接收返回的json数据
            jsonData = response.json()
            # 输出
            logging.info("账号输入特殊字符时输出的数据为: {}".format(jsonData))
            # 断言
            assert_common(self, response, 200, False, 20001, "用户名或密码错误")
    
        def test05_username_is_empty(self):
            # 调用封装的登陆接口
            response = self.login_api.login('','error')
            # 接收返回的json数据
            jsonData = response.json()
            # 输出
            logging.info("账号为空输出的数据为: {}".format(jsonData))
            # 断言
            assert_common(self, response, 200, False, 20001, "用户名或密码错误")
    
        def test06_password_is_empty(self):
            # 调用封装的登陆接口
            response = self.login_api.login('13800000002','')
            # 接收返回的json数据
            jsonData = response.json()
            # 输出
            logging.info("密码为空输出的数据为: {}".format(jsonData))
            # 断言
            assert_common(self, response, 200, False, 20001, "用户名或密码错误")
    
        def test07_username_have_chinese(self):
            # 调用封装的登陆接口
            response = self.login_api.login('1380三毛0000002','123456')
            # 接收返回的json数据
            jsonData = response.json()
            # 输出
            logging.info("账号中有中文输出的数据为: {}".format(jsonData))
            # 断言
            assert_common(self, response, 200, False, 20001, "用户名或密码错误")
    
        def test08_username_middler_have_space(self):
            # 调用封装的登陆接口
            response = self.login_api.login('1380 0000002','123456')
            # 接收返回的json数据
            jsonData = response.json()
            # 输出
            logging.info("账号中有空格输出的数据为: {}".format(jsonData))
            # 断言
            assert_common(self, response, 200, False, 20001, "用户名或密码错误")
    
    

    运行结果:

接口测试之代码项目实战_第4张图片

5 使用测试套件批量运行登陆接口测试脚本生成测试报告

编写代码步骤:

  • 1 初始化测试套件

  • 2 将测试用例添加到测试套件

  • 3 使用HTMLTestRunner运行测试套件,生成测试报告

    • 设置报告的路径
    • 打开报告路径,初始化Runner
    • 使用Runner运行测试套件
# run_suite.py
import time
import unittest
import app
from script.test_login import TestIHRMLogin
from tools.HTMLTestRunner import HTMLTestRunner

# 1 初始化测试套件
suite = unittest.TestSuite()
# 2 将测试用例添加到测试套件
suite.addTest(unittest.makeSuite(TestIHRMLogin))
# 3 使用HTMLTestRunner执行测试套件,生成测试报告
report_path = app.BASE_DIR + "/report/ihrm{}.html".format(time.strftime('%Y%m%d %H%M%S'))
with open(report_path, mode='wb') as f:
    # 初始化HTMLTestRunner
    runner = HTMLTestRunner(f, verbosity=1, title="IHRM人力资源接口测试", description="V1.0.0")
    # 使用Runner运行测试套件
    runner.run(suite)

运行结果:

接口测试之代码项目实战_第5张图片

测试报告:

接口测试之代码项目实战_第6张图片

报告打开截图:

接口测试之代码项目实战_第7张图片

6 使用Parameterized实现参数化

  • 设计登陆接口测试和断言数据文件

在data中新建login_data.json文件,参考登陆的入参和断言数据设计数据如下:

接口测试之代码项目实战_第8张图片

[
  {
    "case_name": "登陆成功",
    "mobile": "13800000002",
    "password": "123456",
    "http_code": 200,
    "success": true,
    "code": 10000,
    "message": "操作成功"
  },
  {
    "case_name": "账号不存在",
    "mobile": "13900000002",
    "password": "123456",
    "http_code": 200,
    "success": false,
    "code": 20001,
    "message": "用户名或密码错误"
  },
  {
    "case_name": "密码错误",
    "mobile": "13800000002",
    "password": "error",
    "http_code": 200,
    "success": false,
    "code": 20001,
    "message": "用户名或密码错误"
  },
  {
    "case_name": "账号输入特殊字符",
    "mobile": "!@#$%^&*()_-+=",
    "password": "123456",
    "http_code": 200,
    "success": false,
    "code": 20001,
    "message": "用户名或密码错误"
  },
  {
    "case_name": "账号为空",
    "mobile": "",
    "password": "error",
    "http_code": 200,
    "success": false,
    "code": 20001,
    "message": "用户名或密码错误"
  },
  {
    "case_name": "密码为空",
    "mobile": "13800000002",
    "password": "",
    "http_code": 200,
    "success": false,
    "code": 20001,
    "message": "用户名或密码错误"
  },
  {
    "case_name": "账号有中文",
    "mobile": "13800咸鱼000002",
    "password": "123456",
    "http_code": 200,
    "success": false,
    "code": 20001,
    "message": "用户名或密码错误"
  },
  {
    "case_name": "账号中间有空格",
    "mobile": "138 00000002",
    "password": "123456",
    "http_code": 200,
    "success": false,
    "code": 20001,
    "message": "用户名或密码错误"
  }
]

  • 编写读取登陆数据文件的函数

在utils.py中继续编写读取文件的函数

函数名:read_login_data,代码:

# utils.py
import app
import json


# 编写断言代码函数
def assert_common(self, response, http_code, success, code, message):
    self.assertEqual(http_code, response.status_code)  # 断言响应状态码
    self.assertEqual(success, response.json().get("success"))  # 断言success
    self.assertEqual(code, response.json().get("code"))  # 断言code
    self.assertIn(message, response.json().get("message"))  # 断言message

def read_login_data():
    data_path = app.BASE_DIR + "/data/login_data.json"
    with open(data_path, mode='r',encoding='utf-8') as f:
        # 加载文件为json格式的数据
        jsonData = json.load(f)
        # 遍历文件取出其中数据并保存在列表中
        p_list = []
        for data in jsonData:
            mobile = data.get("mobile")
            password = data.get("password")
            http_code = data.get("http_code")
            success = data.get("success")
            code = data.get("code")
            message = data.get("message")
            p_list.append( (mobile,password,http_code,success,code,message) )
    print(p_list)
    return p_list


if __name__ == '__main__':
    # main函数的作用?
    # 防止调用这个模块或者类时,自动执行代码
    read_login_data()
  • 在测试脚本中使用parameterized调用读取数据文件的函数,实现参数化和数据驱动登陆接口测试用例测试

为了不与未参数化的登陆脚本冲突,单独新增一个参数化的py文件,文件名:test_login_para.py

代码:

# test_lgin_para.py
import unittest, logging
from api.login_api import LoginApi
from utils import assert_common, read_login_data
from parameterized.parameterized import parameterized

class TestIHRMLoginPara(unittest.TestCase):
    def setUp(self) -> None:
        pass

    @classmethod
    def setUpClass(cls) -> None:
        # 初始化登陆类
        cls.login_api = LoginApi()

    def tearDown(self) -> None:
        pass

    @classmethod
    def tearDownClass(cls) -> None:
        pass

    @parameterized.expand(read_login_data)
    def test_login(self, mobile,password,http_code,success,code,message):
        # 调用封装的登陆接口
        response = self.login_api.login(mobile, password)
        # 接收返回的json数据
        jsonData = response.json()
        # 调试输出登陆接口返回的数据,日志输出只能用作为{}占位符
        logging.info("登陆接口返回的数据为: {}".format(jsonData))

        assert_common(self, response, http_code, success, code, message)

至此登陆模块的自动化接口测试完美完成

使用到的技术:requests,unittest,parameterized,python

实现了:登陆的8个测试用例实现

4,员工模块接口测试

分析:由于员工模块依赖了登陆接口中返回的令牌,所以测试员工模块时,要先获取到登陆模块中返回的令牌,并添加到员工模块接口的请求头当中。

因此,我们首先第一个接口,应该实现登陆接口,并保存登陆接口的返回的令牌到全局变量中的HEADERS中,然后在后续调用员工模块的接口时引用设置的请求头

这里,我们有好几种实现方案:

第一种: 在员工的增删改查中先调用登陆接口,保存令牌,最后调用员工模块接口,如截图所示:

接口测试之代码项目实战_第9张图片

缺点:是代码集中在一个函数体内,显得冗长;并且,登陆接口在用例中调用,如果有其他测试场景时,也需要重复调用登陆接口。

第二种: 新增一个login.py文件,单独实现登陆接口,并保存令牌到全局变量,然后后续调用添加员工接口时引用即可。

缺点:1 需要对python的顺序执行和封装的概念要理解得更透彻。2 script一般是存放用例,这个登陆既不是测试用例,存放这里有些突兀。但是这个不影响执行,所以我们推荐这一种。3 需要控制代码的执行顺序。

控制代码执行顺序:可以在run_suite.py中通过测试用例的添加顺序控制执行顺序。

其他方式:代码的实现方式是多种多样的,我们这里通过这一种方式入门接口自动化测试框架,虽然对于小白来理解起来有一定难度,但是这也是进步的好机会,不能因为觉得难就放弃。

步骤:

1,新增login.py文件,编写登陆代码并保存全局变量

# script.login.py
import unittest, logging
from api.login_api import LoginApi
import app


class Login(unittest.TestCase):
    def setUp(self) -> None:
        pass

    @classmethod
    def setUpClass(cls) -> None:
        # 初始化登陆类
        cls.login_api = LoginApi()

    def tearDown(self) -> None:
        pass

    @classmethod
    def tearDownClass(cls) -> None:
        pass

    def test_login(self):
        # 调用封装的登陆接口
        response = self.login_api.login('13800000002', '123456')
        # 接收返回的json数据
        jsonData = response.json()
        # 调试输出登陆接口返回的数据,日志输出只能用作为{}占位符
        logging.info("登陆成功接口返回的数据为: {}".format(jsonData))

        # 获取令牌,并拼接成以Bearer 开头的令牌字符串
        token = jsonData.get("data")
        # 保存令牌到全局变量
        app.HEADERS['Authorization'] = "Bearer " + token
        logging.info("保存的令牌是:{}".format(app.HEADERS))

2,封装添加员工接口

import requests
import app


class EmpApi:
    def __init__(self):
        self.emp_url = app.HOST + "/api/sys/user"
        # 注意:如果我们调用员工管理模块的相关接口时,先执行login.py文件
        # 获取到的app.HEADERS 才会是{"Content-Type":"application","Authorization":"Bearer xxxx-xxx-xxxxx"}
        self.headers = app.HEADERS

    def add_emp(self, username, mobile):
        data = {
            "username": username,
            "mobile": mobile,
            "timeOfEntry": "2019-12-02",
            "formOfEmployment": 1,
            "workNumber": "1234",
            "departmentName": "测试",
            "departmentId": "1210411411066695680",
            "correctionTime": "2019-12-15T16:00:00.000Z"
        }
        # 发送添加员工接口请求
        response = requests.post(self.emp_url, json=data, headers=self.headers)
        # 返回添加员工接口的响应数据
        return response

3, 实现添加员工接口测试用例脚本

在script中新增test_emp.py文件

编写添加员工的测试用例,注意:由于我们不能在这里调试代码,只能通过run_suite.py来进行调试

import logging
import unittest
from api.emp_api import EmpApi
from utils import assert_common
import app


class TestIHRMEmp(unittest.TestCase):
    def setUp(self) -> None:
        pass

    @classmethod
    def setUpClass(cls) -> None:
        # 初始化员工类
        cls.emp_api = EmpApi()

    def tearDown(self) -> None:
        pass

    @classmethod
    def tearDownClass(cls) -> None:
        pass

    def test01_add_emp(self):
        # 调用添加员工接口
        response = self.emp_api.add_emp("熊猫222123", "18799994321")
        # 获取添加员工接口的json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("添加员工接口返回数据为:{}".format(jsonData))
        # 断言
        assert_common(self, response, 200, True, 10000, "操作成功")

4, 使用run_suite.py执行员工模块的相关测试用例

run_suite.py代码

导入部分增加了

from script.login import Login
from script.test_login import TestIHRMLogin

并且添加到测试用例中

suite.addTest(unittest.makeSuite(Login))
suite.addTest(unittest.makeSuite(TestIHRMEmp))

其他不变

# run_suite.py
import time
import unittest
import app
from script.login import Login
from script.test_emp import TestIHRMEmp
from script.test_login import TestIHRMLogin
from tools.HTMLTestRunner import HTMLTestRunner

# 1 初始化测试套件
suite = unittest.TestSuite()
# 2 将测试用例添加到测试套件
suite.addTest(unittest.makeSuite(Login))
suite.addTest(unittest.makeSuite(TestIHRMEmp))
suite.addTest(unittest.makeSuite(TestIHRMLogin))
# 3 使用HTMLTestRunner执行测试套件,生成测试报告
report_path = app.BASE_DIR + "/report/ihrm{}.html".format(time.strftime('%Y%m%d %H%M%S'))
with open(report_path, mode='wb') as f:
    # 初始化HTMLTestRunner
    runner = HTMLTestRunner(f, verbosity=1, title="IHRM人力资源接口测试", description="V1.0.0")
    # 使用Runner运行测试套件
    runner.run(suite)

执行结果:

在这里插入图片描述

5,封装查询员工接口的准备

分析:由于查询员工接口的URL中,需要使用添加员工接口返回的ID,所以,我们需要在添加员工的代码中增加保存员工ID的操作,并把ID保存在全局变量。但是员工ID必须提前定义,这里定义在app.py中,变量名为EMP_ID。

app.py中增加员工ID的全局变量。见下方代码的EMP_ID=""

# app.py
# 编写初始化日志的函数
import logging
from logging import handlers
import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
HOST = "http://182.92.81.159"
HEADERS = {"Content-Type":"application/json"}
EMP_ID = ""

def init_logging():
    # 创建日志器
    logger = logging.getLogger()
    # 设置日志等级
    logger.setLevel(logging.INFO)
    # 设置处理器
    # 设置控制台处理器
    sh = logging.StreamHandler()
    # 设置文件处理器
    # TimedRotatingFileHandler 可以用来帮助我们切分日志:按照时间来设置日志
    filename = BASE_DIR + "/log/ihrm.log"
    fh = logging.handlers.TimedRotatingFileHandler(filename, when='M', interval=1, backupCount=7)
    # 设置格式化器
    fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
    formatter = logging.Formatter(fmt)
    # 将格式化器添加到处理器当中
    sh.setFormatter(formatter)
    fh.setFormatter(formatter)
    # 将处理器添加到日志器
    logger.addHandler(fh)
    logger.addHandler(sh)

添加员工的代码更新为:

# script.test_emp
    def test01_add_emp(self):
        # 调用添加员工接口
        response = self.emp_api.add_emp(username, mobile)
        # 获取添加员工接口的json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("添加员工接口返回数据为:{}".format(jsonData))
        # 获取员工ID保存在app.EMP_ID中
        app.EMP_ID = jsonData.get("data").get("id")
        logging.info("员工ID: {}".format(app.EMP_ID))
        # 断言
        assert_common(self, response, 200, True, 10000, "操作成功")

6,封装查询员工接口

url: 拼接查询员工接口返回的app.EMP_ID,组成查询员工接口所需的URL

# 封装查询员工接口
    def query_emp(self):
        # 查询员工接口的URL结构是http://182.92.81.159/api/sys/user/12344343221
        # 所以拼接URL时需要加上“/”
        url = self.emp_url + "/" + app.EMP_ID
        # 返回查询的结果,headers是{"Content-Type":"application/json", "Authorization":"Bearer xxxx"}
        return requests.get(url, headers=self.headers)

7,实现查询员工接口测试用例

def test02_query_emp(self):
    # 调用查询员工接口
    response = self.emp_api.query_emp()
    # 获取查询员工接口的返回json数据
    jsonData = response.json()
    # 输出json数据
    logging.info("查询员工接口的返回数据为:{}".format(jsonData))
    # 断言
    assert_common(self, response, 200, True, 10000, "操作成功")

8,完成剩余修改和删除员工接口的封装

import requests
import app


class EmpApi:
    def __init__(self):
        self.emp_url = app.HOST + "/api/sys/user"
        # 从全局变量中获取app.HEADERS,但是需要注意的是,只有先运行会保存令牌的登陆接口后
        # app.HEADERS的值才会是:
        # {"Content-Type":"application/json","Authorizaition":"Bearer xxxx-xxx-xxx"}
        self.headers = app.HEADERS 

    def add_emp(self, username, mobile):
        data = {
            "username": username,
            "mobile": mobile,
            "timeOfEntry": "2019-12-02",
            "formOfEmployment": 1,
            "workNumber": "1234",
            "departmentName": "测试",
            "departmentId": "1210411411066695680",
            "correctionTime": "2019-12-15T16:00:00.000Z"
        }
        # 发送添加员工接口请求
        response = requests.post(self.emp_url, json=data, headers=self.headers)
        # 返回添加员工接口的响应数据
        return response

    # 封装查询员工接口
    def query_emp(self):
        # 查询员工接口的URL结构是http://182.92.81.159/api/sys/user/12344343221
        # 所以拼接URL时需要加上“/”
        url = self.emp_url + "/" + app.EMP_ID
        # 返回查询的结果,headers是{"Content-Type":"application/json", "Authorization":"Bearer xxxx"}
        return requests.get(url, headers=self.headers)

    # 封装修改员工接口
    def modify_emp(self, username):
        # 修改员工的URL应该和查询员工是一样的,所以拼接的时候也需要加上“/”
        # http://182.92.81.159/api/sys/user/12344343221
        url = self.emp_url + "/" + app.EMP_ID
        # 从外部接收要修改的username,拼接成json数据
        data = {
            "username":username
        }
        # 返回查询结果
        return requests.put(url,
                            json=data,
                            headers=self.headers)

    # 封装删除员工接口
    def delete_emp(self):
        url = self.emp_url + "/" + app.EMP_ID
        # 调用删除的http接口并返回响应数据
        return requests.delete(url, headers=self.headers)

9 完成修改和删除员工接口测试用例

import logging
import unittest
from api.emp_api import EmpApi
from utils import assert_common, DBUtils
import app
from parameterized.parameterized import parameterized
from utils import read_add_emp_data, read_query_emp_data, read_modify_emp_data, read_delete_emp_data
import pymysql


class TestIHRMEmp(unittest.TestCase):
    def setUp(self) -> None:
        pass

    @classmethod
    def setUpClass(cls) -> None:
        # 初始化员工类
        cls.emp_api = EmpApi()

    def tearDown(self) -> None:
        pass

    @classmethod
    def tearDownClass(cls) -> None:
        pass

    def test01_add_emp(self):
        # 调用添加员工接口
        response = self.emp_api.add_emp("葫芦娃", "13145201314")
        # 获取添加员工接口的json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("添加员工接口返回数据为:{}".format(jsonData))
        # 获取员工ID保存在全局变量
        app.EMP_ID = jsonData.get("data").get("id")
        logging.info("员工ID: {}".format(app.EMP_ID))
        # 断言
        assert_common(self, response, 200, True, 10000, "操作成功")

    def test02_query_emp(self, success, code, message, http_code):
        # 调用查询员工接口
        response = self.emp_api.query_emp()
        # 获取查询员工接口的返回json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("查询员工接口的返回数据为:{}".format(jsonData))
        # 断言
        assert_common(self, response, 200, True, 10000, "操作成功")

    def test03_modify_emp(self):
        # 调用修改员工接口
        response = self.emp_api.modify_emp("全知全能")
        # 获取修改员工接口的返回json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("修改员工接口的返回数据为:{}".format(jsonData))

        with DBUtils() as db_utils:
            # 执行查询语句,查询修改之后的username
            sql = "select username from bs_user where id={}".format(app.EMP_ID)
            db_utils.execute(sql)
            # 获取执行结果
            result = db_utils.fetchone()[0]
            logging.info("从数据库中查询出的员工的用户名是:{}".format(result))


        # 断言
        assert_common(self, response, 200, True, 10000, "操作成功")

    def test04_delete_emp(self):
        # 调用删除员工接口
        response = self.emp_api.delete_emp()
        # 获取修改员工接口的返回json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("删除员工接口的返回数据为:{}".format(jsonData))
        # 断言
        assert_common(self, response, 200, True, 10000, "操作成功")

10 在run_suite.py中运行,实现员工的增删改查业务场景测试

运行结果部分截图:

接口测试之代码项目实战_第10张图片

11 参数化员工管理模块

构造测试数据文件:

在data中新增employee.json文件,文件内容如下:

接口测试之代码项目实战_第11张图片

{
  "add_emp": {
    "username": "强哥xxx12",
    "mobile": "16589987635",
    "success": true,
    "code": 10000,
    "message": "操作成功",
    "http_code": 200
  },
  "query_emp": {
    "success": true,
    "code": 10000,
    "message": "操作成功",
    "http_code": 200
  },
  "modify_emp": {
    "username": "王强",
    "success": true,
    "code": 10000,
    "message": "操作成功",
    "http_code": 200
  },
  "delete_emp": {
    "success": true,
    "code": 10000,
    "message": "操作成功",
    "http_code": 200
  }
}

在utils.py中编写读取员工的函数,一共4个函数:

read_add_emp_data,read_query_emp_data,read_modify_emp_data,read_delete_emp_data

def read_add_emp_data():
    path = app.BASE_DIR + "/data/employee.json"
    # 打开文件
    with open(path, mode='r', encoding='utf-8') as f:
        jsonData = json.load(f)
        # 由于employee.json是一个字典数据,那么我们可以使用字典的get方法获取其中数据
        add_emp_data = jsonData.get("add_emp")
        result_list = []
        username = add_emp_data.get("username")
        mobile = add_emp_data.get("mobile")
        success = add_emp_data.get("success")
        code = add_emp_data.get("code")
        message = add_emp_data.get("message")
        http_code = add_emp_data.get("http_code")
        result_list.append((username, mobile, success, code, message, http_code))

    print("读取添加员工的数据: ", result_list)
    return result_list


def read_query_emp_data():
    path = app.BASE_DIR + "/data/employee.json"
    # 打开文件
    with open(path, mode='r', encoding='utf-8') as f:
        # 加载Json数据文件
        jsonData = json.load(f)
        # 由于employee.json是一个字典数据,那么我们可以使用字典的get方法获取
        result_list = []
        query_emp_data = jsonData.get("query_emp")
        success = query_emp_data.get("success")
        code = query_emp_data.get("code")
        message = query_emp_data.get("message")
        http_code = query_emp_data.get("http_code")
        result_list.append((success, code, message, http_code))
    print("查询员工数据为:", result_list)
    return result_list


def read_modify_emp_data():
    path = app.BASE_DIR + "/data/employee.json"
    # 打开文件
    with open(path, mode='r', encoding='utf-8') as f:
        # 加载Json数据文件
        jsonData = json.load(f)
        # 由于employee.json是一个字典数据,那么我们可以使用字典的get方法获取
        result_list = []
        modify_emp_data = jsonData.get("modify_emp")
        username = modify_emp_data.get("username")
        success = modify_emp_data.get("success")
        code = modify_emp_data.get("code")
        message = modify_emp_data.get("message")
        http_code = modify_emp_data.get("http_code")
        result_list.append((username, success, code, message, http_code))
    print("修改员工数据为:", result_list)
    return result_list


def read_delete_emp_data():
    path = app.BASE_DIR + "/data/employee.json"
    # 打开文件
    with open(path, mode='r', encoding='utf-8') as f:
        # 加载Json数据文件
        jsonData = json.load(f)
        # 由于employee.json是一个字典数据,那么我们可以使用字典的get方法获取
        result_list = []
        delete_emp_data = jsonData.get("delete_emp")
        success = delete_emp_data.get("success")
        code = delete_emp_data.get("code")
        message = delete_emp_data.get("message")
        http_code = delete_emp_data.get("http_code")
        result_list.append((success, code, message, http_code))
    print("删除员工数据为:", result_list)
    return result_list

在script.test_emp中实现参数化

import logging
import unittest
from api.emp_api import EmpApi
from utils import assert_common, DBUtils
import app
from parameterized.parameterized import parameterized
from utils import read_add_emp_data, read_query_emp_data, read_modify_emp_data, read_delete_emp_data


class TestIHRMEmp(unittest.TestCase):
    def setUp(self) -> None:
        pass

    @classmethod
    def setUpClass(cls) -> None:
        # 初始化员工类
        cls.emp_api = EmpApi()

    def tearDown(self) -> None:
        pass

    @classmethod
    def tearDownClass(cls) -> None:
        pass

    @parameterized.expand(read_add_emp_data)
    def test01_add_emp(self, username, mobile, success, code, message, http_code):
        # 调用添加员工接口
        response = self.emp_api.add_emp(username, mobile)
        # 获取添加员工接口的json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("添加员工接口返回数据为:{}".format(jsonData))
        # 获取员工ID保存在全局变量
        app.EMP_ID = jsonData.get("data").get("id")
        logging.info("员工ID: {}".format(app.EMP_ID))
        # 断言
        assert_common(self, response, http_code, success, code, message)

    @parameterized.expand(read_query_emp_data)
    def test02_query_emp(self, success, code, message, http_code):
        # 调用查询员工接口
        response = self.emp_api.query_emp()
        # 获取查询员工接口的返回json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("查询员工接口的返回数据为:{}".format(jsonData))
        # 断言
        assert_common(self, response, http_code, success, code, message)

    @parameterized.expand(read_modify_emp_data)
    def test03_modify_emp(self, username, success, code, message, http_code):
        # 调用修改员工接口
        response = self.emp_api.modify_emp(username)
        # 获取修改员工接口的返回json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("修改员工接口的返回数据为:{}".format(jsonData))

        # 断言
        assert_common(self, response, http_code, success, code, message)

    @parameterized.expand(read_delete_emp_data)
    def test04_delete_emp(self, success, code, message, http_code):
        # 调用删除员工接口
        response = self.emp_api.delete_emp()
        # 获取修改员工接口的返回json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("删除员工接口的返回数据为:{}".format(jsonData))
        # 断言
        assert_common(self, response, http_code, success, code, message)

12 封装数据库工具类

在utils.py的末尾增加代码


class DBUtils:

    def __init__(self, host="182.92.81.159",
                 user='readuser',
                 password='iHRM_user_2019',
                 database='ihrm'):
        self.host = host
        self.user = user
        self.password = password
        self.database = database

    #  __enter__和__exit__是一对魔法方法,主要要和with结合使用
    # 如:with DBUtils() as db:
    # 我们就可以在with的代码快中使用db来执行sql语句。这个db就相当于cursor
    def __enter__(self):
        self.conn = pymysql.connect(self.host, self.user, self.password, self.database)
        self.cursor = self.conn.cursor()
        return self.cursor

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.cursor:
            self.cursor.close()
        if self.conn:
            self.conn.close()

13 使用封装的数据库工具类,在修改员工(test_modify_emp)增加查询数据的代码

# script.test_emp
import logging
import unittest
from api.emp_api import EmpApi
from utils import assert_common, DBUtils
import app
from parameterized.parameterized import parameterized
from utils import read_add_emp_data, read_query_emp_data, read_modify_emp_data, read_delete_emp_data
import pymysql


class TestIHRMEmp(unittest.TestCase):
    def setUp(self) -> None:
        pass

    @classmethod
    def setUpClass(cls) -> None:
        # 初始化员工类
        cls.emp_api = EmpApi()

    def tearDown(self) -> None:
        pass

    @classmethod
    def tearDownClass(cls) -> None:
        pass

    @parameterized.expand(read_add_emp_data)
    def test01_add_emp(self, username, mobile, success, code, message, http_code):
        # 调用添加员工接口
        response = self.emp_api.add_emp(username, mobile)
        # 获取添加员工接口的json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("添加员工接口返回数据为:{}".format(jsonData))
        # 获取员工ID保存在全局变量
        app.EMP_ID = jsonData.get("data").get("id")
        logging.info("员工ID: {}".format(app.EMP_ID))
        # 断言
        assert_common(self, response, http_code, success, code, message)

    @parameterized.expand(read_query_emp_data)
    def test02_query_emp(self, success, code, message, http_code):
        # 调用查询员工接口
        response = self.emp_api.query_emp()
        # 获取查询员工接口的返回json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("查询员工接口的返回数据为:{}".format(jsonData))
        # 断言
        assert_common(self, response, http_code, success, code, message)

    @parameterized.expand(read_modify_emp_data)
    def test03_modify_emp(self, username, success, code, message, http_code):
        # 调用修改员工接口
        response = self.emp_api.modify_emp(username)
        # 获取修改员工接口的返回json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("修改员工接口的返回数据为:{}".format(jsonData))

        with DBUtils() as db_utils:
            # 执行查询语句,查询修改之后的username
            sql = "select username from bs_user where id={}".format(app.EMP_ID)
            db_utils.execute(sql)
            # 获取执行结果
            result = db_utils.fetchone()[0]
            logging.info("从数据库中查询出的员工的用户名是:{}".format(result))


        # 断言
        assert_common(self, response, http_code, success, code, message)

    @parameterized.expand(read_delete_emp_data)
    def test04_delete_emp(self, success, code, message, http_code):
        # 调用删除员工接口
        response = self.emp_api.delete_emp()
        # 获取修改员工接口的返回json数据
        jsonData = response.json()
        # 输出json数据
        logging.info("删除员工接口的返回数据为:{}".format(jsonData))
        # 断言
        assert_common(self, response, http_code, success, code, message)

13 在run_suite.py中执行,查看结果

接口测试之代码项目实战_第12张图片

14 修改run_suite.py代码,把登陆的测试用例添加到测试套件中

# run_suite.py
import time
import unittest
import app
from script.login import Login
from script.test_emp import TestIHRMEmp
from script.test_login import TestIHRMLogin
from tools.HTMLTestRunner import HTMLTestRunner

# 1 初始化测试套件
suite = unittest.TestSuite()
# 2 将测试用例添加到测试套件
suite.addTest(unittest.makeSuite(Login))
suite.addTest(unittest.makeSuite(TestIHRMEmp))
suite.addTest(unittest.makeSuite(TestIHRMLogin))
# 3 使用HTMLTestRunner执行测试套件,生成测试报告
report_path = app.BASE_DIR + "/report/ihrm{}.html".format(time.strftime('%Y%m%d %H%M%S'))
with open(report_path, mode='wb') as f:
    # 初始化HTMLTestRunner
    runner = HTMLTestRunner(f, verbosity=1, title="IHRM人力资源接口测试", description="V1.0.0")
    # 使用Runner运行测试套件
    runner.run(suite)

15 在run_suite.py中运行登陆和员工模块的测试用例,查看结果

接口测试之代码项目实战_第13张图片

查看Web页面的结果

接口测试之代码项目实战_第14张图片

你可能感兴趣的:(接口测试之代码项目实战)