Web自动化测试进阶-PO模式+数据驱动+日志收集

一、PO模式入门到深入

学习路线: 

V1:不使用任何设计模式和单元测试框架

V2:pytest管理测试用例

V3:使用方法封装的思想,对代码进行优化

V4:采用PO模式的分层思想对代码进行拆分,分离page

V5:对PO分层之后的代码继续优化,分离page中的元素和操作

V6:PO模式深入封装,把共同的操作提取封装

1、V1-无模式 

案例需求:对电商网站对登录模块进行自动化测试

登录模块包含很多测试用例,如:账号不存在,密码错误,验证码错误,登录成功等等 

选择测试用例 

选择账号不存在和密码错误为例 

账号不存在:

1、点击首页登录链接,进入登录页面

2、输入一个不存在的用户名

3、输入密码

4、输入验证码

5、点击登录按钮

6、获取错误提示信息

密码错误:

1、点击首页登录链接,进入登录页面

2、输入用户名

3、输入错误密码

4、输入验证码

5、点击登录按钮

6、获取错误提示信息

1.1 总体介绍

不使用任何设计模式和单元测试框架,每个文件对应编写一个测试用例,完全面向过程的编程方式

文件名:test_login_account_not_exist.py和test_login_password_error.py

 账号不存在测试用例代码:

# 账号不存在
import time

from selenium import webdriver

# 实例化浏览器驱动
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost/")

# 1、点击首页登录链接,进入登录页面
driver.find_element_by_class_name("red").click()
# 2、输入一个不存在的用户名
driver.find_element_by_id("username").send_keys("18000021")
# 3、输入密码
driver.find_element_by_id("password").send_keys("123456")
# 4、输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 5、点击登录按钮
driver.find_element_by_name("sbtbutton").click()
# 6、获取错误提示信息
msg = driver.find_element_by_css_selector(".layui-layer-content").text
print(msg)
# 关闭浏览器
time.sleep(3)
driver.quit()

密码错误测试用例: 

# 密码错误
import time

from selenium import webdriver

# 实例化浏览器驱动
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost/")

# 1、点击首页登录链接,进入登录页面
driver.find_element_by_class_name("red").click()
# 2、输入用户名
driver.find_element_by_id("username").send_keys("18800123")
# 3、输入错误的密码
driver.find_element_by_id("password").send_keys("error")
# 4、输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 5、点击登录按钮
driver.find_element_by_name("sbtbutton").click()
# 6、获取错误提示信息
msg = driver.find_element_by_css_selector(".layui-layer-content").text
print(msg)
# 关闭浏览器驱动
time.sleep(3)
driver.quit()

 1.2 无模式存在的问题

  • 一条测试用例对应一个文件,用例多的时候,不方便维护管理
  • 代码高度冗余 

2、V2-pytest单元测试框架管理测试用例

2.1 总体介绍 

引入pytest框架管理测试用例,并断言执行结果,输出测试报告

好处:

  • 方便组织和管理测试用例
  • 提供了丰富的断言方法
  • 方便生成测试报告
  • 减少代码冗余 

 代码:

import time

from selenium import webdriver

# 定义测试类
class TestLogin:
    def setup_method(self):
        # 实例化浏览器驱动
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.implicitly_wait(10)
        self.driver.get("http://localhost/")

    def teardown_method(self):
        # 关闭浏览器
        time.sleep(3)
        self.driver.quit()

    # 定义用户不存在的测试方法
    def test_login_account_not_exist(self):
        # 1、点击首页登录链接,进入登录页面
        self.driver.find_element_by_class_name("red").click()
        # 2、输入一个不存在的用户名
        self.driver.find_element_by_id("username").send_keys("18000021")
        # 3、输入密码
        self.driver.find_element_by_id("password").send_keys("123456")
        # 4、输入验证码
        self.driver.find_element_by_id("verify_code").send_keys("8888")
        # 5、点击登录按钮
        self.driver.find_element_by_name("sbtbutton").click()
        # 6、获取错误提示信息
        msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
        print(msg)
        # 断言
        assert "账号不存在!" ==msg

    # 定义密码错误的测试方法
    def test_login_password_error(self):
        # 1、点击首页登录链接,进入登录页面
        self.driver.find_element_by_class_name("red").click()
        # 2、输入用户名
        self.driver.find_element_by_id("username").send_keys("18800123")
        # 3、输入错误密码
        self.driver.find_element_by_id("password").send_keys("error")
        # 4、输入验证码
        self.driver.find_element_by_id("verify_code").send_keys("8888")
        # 5、点击登录按钮
        self.driver.find_element_by_name("sbtbutton").click()
        # 6、获取错误提示信息
        msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
        print(msg)
        # 断言
        assert "密码错误!" == msg

2.2 存在的问题 

依然存在代码冗余,比如有些操作步骤/方法是不是可以封装起来 

3、V3-方法封装

3.1 总体介绍

使用方法封装思想,对代码进行优化

封装的概念:

是将一些有共性的或者多次使用的代码提取到一个方法中,供其他地方调用

好处:

  • 避免代码冗余
  • 容易维护和调用
  • 隐藏代码的实现细节

目的:

用最少的代码实现最多的功能

 驱动工具类代码实现:

# 获取/关闭浏览器驱动的类
import time

from selenium import webdriver

class DriverUtils:
    __driver = None # 变量加两个下划线,防止外部方法调用,避免与测试用例的驱动混淆
    # 获取浏览器驱动
    @classmethod # 可以通过类名来调用方法
    def get_driver(cls):
        # 实例化浏览器驱动
        if cls.__driver is None:
            cls.__driver = webdriver.Chrome()
            cls.__driver.maximize_window()
            cls.__driver.implicitly_wait(10)
        return cls.__driver
    # 获关闭浏览器驱动
    @classmethod  # 可以通过类名来调用方法
    def close_driver(cls):
        if cls.__driver is not None:
            # 关闭浏览器
            time.sleep(3)
            cls.__driver.quit()
            cls.__driver = None

测试脚本代码实现:

import time

from selenium import webdriver

from v3.driver_utils import DriverUtils


# 定义测试类
class TestLogin:
    def setup_method(self):
        # 实例化浏览器驱动
        self.driver = DriverUtils.get_driver()
        self.driver.get("http://localhost/")

    def teardown_method(self):
        # 关闭浏览器
        time.sleep(3)
        DriverUtils.close_driver()

    # 定义用户不存在的测试方法
    def test_login_account_not_exist(self):
        # 1、点击首页登录链接,进入登录页面
        self.driver.find_element_by_class_name("red").click()
        # 2、输入一个不存在的用户名
        self.driver.find_element_by_id("username").send_keys("18000021")
        # 3、输入密码
        self.driver.find_element_by_id("password").send_keys("123456")
        # 4、输入验证码
        self.driver.find_element_by_id("verify_code").send_keys("8888")
        # 5、点击登录按钮
        self.driver.find_element_by_name("sbtbutton").click()
        # 6、获取错误提示信息
        msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
        print(msg)
        # 断言
        assert "账号不存在!" ==msg

    # 定义密码错误的测试方法
    def test_login_password_error(self):
        # 1、点击首页登录链接,进入登录页面
        self.driver.find_element_by_class_name("red").click()
        # 2、输入用户名
        self.driver.find_element_by_id("username").send_keys("18800123")
        # 3、输入错误密码
        self.driver.find_element_by_id("password").send_keys("error")
        # 4、输入验证码
        self.driver.find_element_by_id("verify_code").send_keys("8888")
        # 5、点击登录按钮
        self.driver.find_element_by_name("sbtbutton").click()
        # 6、获取错误提示信息
        msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
        print(msg)
        # 断言
        assert "密码错误!" == msg

3.2 存在的问题

仍然存在代码冗余,如测试脚本操作步骤时,登录,输入用户名都是重复的

 4、PO模式介绍

思考:

在做UI自动化时,定位元素特别依赖前端页面,一旦页面发生变更,就不得不跟着去修改定位元素的代码

存在的问题:

  • 如果开发人员修改了元素的id,这时候不得不修改所有对应的代码
  • 即便不修改,也存在大量冗余代码

4.1 PO模式总体介绍

概念:

PO时Page Object的缩写,PO模式是自动化测试项目开发实践的最佳设计模式之一

核心思想:

  • 通过对页面元素对封装减少冗余代码,同时在后期维护中,若元素发生变化,只需要调整页面元素封装的代码即可,提高了测试用例的可维护性,可读性 
  • 页面和测试脚本分离

 4.2 PO模式分层

分层机制,让不同层去做不同类型的事情,让代码结果清晰,增加复用性 

主要分层方式:

两层

  • 对象操作层:封装页面信息,包括元素及元素的操作
  • 业务数据层:封装多种操作组合的业务以及测试数据

三层

  • 对象库
  • 操作层
  • 业务数据层

四层

  • 对象库
  • 操作层
  • 业务层
  • 数据层

4.3 PO模式的优点

引入PO模式前:存在大量冗余代码;业务流程不清晰;后期维护成本大

引入PO模式后:减少冗余代码;业务代码和测试数据被分开,降低耦合性 ;维护成本低

5、V4-PO模式的实践 

 5.1 总体介绍

采用PO模式的分层思想对代码进行拆分

封装:

对登录页进行封装:类LoginPage

对测试用例封装:类TestLogin

结构:

utils包:driver_utils.py

page包:login_page.py

scripts包:test_login.py

pytest.ini

5.2 PO模式实战

5.2.1 代码结构

5.2.2 代码示例

login_page.py

class LoginPage:

    # 将driver传入初始函数以调用
    def __init__(self, driver):
        self.driver = driver

    # 点击首页登录链接,进入登录页面
    def click_login_link(self):
        return self.driver.find_element_by_class_name("red").click()

    # 输入用户名
    def input_username(self, username):
        return self.driver.find_element_by_id("username").send_keys("username")

    # 输入密码
    def input_password(self, password):
        return self.driver.find_element_by_id("password").send_keys("password")

    # 输入验证码
    def input_verifycode(self, code):
        return self.driver.find_element_by_id("verify_code").send_keys("code")

    # 点击登录按钮
    def click_login_button(self):
        return self.driver.find_element_by_name("sbtbutton").click()

    # 获取错误提示信息
    def get_error(self):
        msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
        return msg


test_login.py

import time

from selenium import webdriver

from v4.page.login_page import LoginPage
from v4.utils.driver_utils import DriverUtils


# 定义测试类
class TestLogin:
    def setup(self):
        # 实例化浏览器驱动
        self.driver = DriverUtils.get_driver()
        self.login_page = LoginPage(self.driver)
        self.driver.get("http://localhost/")

    def teardown(self):
        # 关闭浏览器
        time.sleep(3)
        DriverUtils.close_driver()

    # 定义用户不存在的测试方法
    def test_login_account_not_exist(self):
        # 点击首页登录链接,进入登录页面
        self.login_page.click_login_link()
        # 输入用户名
        self.login_page.input_username("1800000000")
        # 输入密码
        self.login_page.input_password("123456")
        # 输入验证码
        self.login_page.input_verifycode("8888")
        # 点击登录按钮
        self.login_page.click_login_button()
        # 断言
        assert "账号不存在!" == self.login_page.get_error()

    # 定义密码错误的测试方法
    def test_login_password_error(self):
        # 1、点击首页登录链接,进入登录页面
        self.login_page.click_login_link()
        # 2、输入用户名
        self.login_page.input_username("1800000000")
        # 3、输入错误密码
        self.login_page.input_password("123456")
        # 4、输入验证码
        self.login_page.input_verifycode("8888")
        # 5、点击登录按钮
        self.login_page.click_login_button()
        # 断言
        assert "密码错误!" == self.login_page.get_error()

driver_utils.py

# 获取/关闭浏览器驱动的类
import time

from selenium import webdriver

class DriverUtils:
    __driver = None # 变量加两个下划线,防止外部方法调用,避免与测试用例的驱动混淆
    # 获取浏览器驱动
    @classmethod # 可以通过类名来调用方法
    def get_driver(cls):
        # 实例化浏览器驱动
        if cls.__driver is None:
            cls.__driver = webdriver.Chrome()
            cls.__driver.maximize_window()
            cls.__driver.implicitly_wait(10)
        return cls.__driver
    # 获关闭浏览器驱动
    @classmethod  # 可以通过类名来调用方法
    def close_driver(cls):
        if cls.__driver is not None:
            # 关闭浏览器
            time.sleep(3)
            cls.__driver.quit()
            cls.__driver = None

pytest.ini

[pytest]
adopts  = -s
test paths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*

6、V5-PO模式深入优化

6.1 总体介绍

对PO分层进一步优化,优化内容:

  • 分离page页面中的元素和操作
  • 优化元素的定位方式

6.2 代码示例 

相较于V4版本的PO模式,只需要改动LoginPage页面即可,将元素定位分层,当页面元素改动时,以便更快地发现并维护自动化代码

from selenium.webdriver.common.by import By


class LoginPage:
    # 登录链接 按钮
    login_link_btn = By.CLASS_NAME, "red"
    # 用户名 输入框
    username_input = By.ID, "username"
    # 密码 输入框
    password_input = By.ID, "password"
    # 验证码 输入框
    verify_code_input = By.ID, "verify_code"
    # 登录 按钮
    login_btn = By.NAME, "sbtbutton"
    # 提示信息
    msg_info = By.CSS_SELECTOR, ".layui-layer-content"

    # 将driver传入初始函数以调用
    def __init__(self, driver):
        self.driver = driver

    # 将元组解包,将两个值分别赋值给两个参数

    def find_el(self, feature):
        return self.driver.find_element(*feature)
        # return self.driver.find_element(feature[0], feature[1])

    # 点击首页登录链接,进入登录页面
    def click_login_link(self):
        return self.driver.find_el(self.login_link_btn)
        # return self.driver.find_element_by_class_name("red").click()

    # 输入用户名
    def input_username(self, username):
        return self.driver.find_el(self.username_input)
        # return self.driver.find_element_by_id("username").send_keys("username")

    # 输入密码
    def input_password(self, password):
        return self.driver.find_el(self.password_input)
        # return self.driver.find_element_by_id("password").send_keys("password")

    # 输入验证码
    def input_verifycode(self, code):
        return self.driver.find_el(self.verify_code_input)
        # return self.driver.find_element_by_id("verify_code").send_keys("code")

    # 点击登录按钮
    def click_login_button(self):
        return self.driver.find_el(self.login_btn)
        # return self.driver.find_element_by_name("sbtbutton").click()

    # 获取错误提示信息
    def get_error(self):
        return self.driver.find_el(self.msg_info)
        # msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
        # return msg

7、V6-PO模式深入封装 

7.1 总体介绍 

把共同的方法进行封装

  • 封装操作基类:封装查找元素的方法,封装对元素的操作方法:点击、清空、输入
  • page继承操作基类

7.2 代码示例 

 base_action.py

class BaseAction:

    def __init__(self, driver):
        self.driver = driver

    def find_el(self, feature):
        return self.driver.find_element(*feature)

    def find_els(self, feature):
        return self.driver.find_elements(*feature)

    def click(self, feature):
        return self.find_els(feature).click()

    def input(self, feature, content):
        return self.find_els(feature).send_keys(content)

    def clear(self, feature):
        return self.find_els(feature).clear()

login_page.py

from selenium.webdriver.common.by import By

from v6.base.base_action import BaseAction


class LoginPage(BaseAction):
    # 登录链接 按钮
    login_link_btn = By.CLASS_NAME, "red"
    # 用户名 输入框
    username_input = By.ID, "username"
    # 密码 输入框
    password_input = By.ID, "password"
    # 验证码 输入框
    verify_code_input = By.ID, "verify_code"
    # 登录 按钮
    login_btn = By.NAME, "sbtbutton"
    # 提示信息
    msg_info = By.CSS_SELECTOR, ".layui-layer-content"

    # 点击首页登录链接,进入登录页面
    def click_login_link(self):
        return self.click(self.login_link_btn)
        # return self.driver.find_el(self.login_link_btn)

    # 输入用户名
    def input_username(self, username):
        return self.input(self.username_input, "18000000")
        # return self.driver.find_el(self.username_input)

    # 输入密码
    def input_password(self, password):
        return self.input(self.password_input, "123456")
        # return self.driver.find_el(self.password_input)

    # 输入验证码
    def input_verifycode(self, code):
        return self.input(self.verify_code_input, "8888")
        # return self.driver.find_el(self.verify_code_input)

    # 点击登录按钮
    def click_login_button(self):
        return self.click(self.login_btn)
        # return self.driver.find_el(self.login_btn)

    # 获取错误提示信息
    def get_error(self):
        return self.driver.find_el(self.msg_info)

二、数据驱动

1、数据驱动介绍

1.1 概念

以数据来驱动整个测试用例的执行,即测试数据决定测试结果

1.2 特点

  • 可以把数据驱动理解为一种模式或一种思想
  • 数据驱动技术可以让用户把关注点放在对测试数据的构建和维护上,而不是直接维护测试脚本,可以用同样的业务流程,对不同的输入数据进行测试
  • 数据驱动的实现要依赖参数化技术

1.3 数据来源

  •  直接定义在脚本中:简单直观,但是测试方法和数据未实现真正的分离,不方便后期维护
  • 从文件中读取数据:如txt、excel、xml、JSON等格式文件
  • 从数据库中读取数据 

2、JSON操作 

2.1 JSON基本介绍 

2.1.1 概念

JSON全称“Javascript Object Notation”,是Javascript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式

2.2.2 特点 
  • 是纯文本
  • 具有良好的自我描述性,便于阅读和书写
  • 具有清晰的层级结构
  • 有效提升网络传输效率 :
  1. 对比 XML可拓展标记语言,被设计用来传输和存储数据;
  2. 如果使用XML,需要读取XML文档,然后通过XML中的标签节点来遍历文档,并读取值,然后存储在变量中;
  3. 如果使用JSON,只需要读取JSON字符串

2.2.3 JSON语法规则 
  • 大括号保存对象
  • 中括号保存数组
  • 对象和数组可以相互嵌套 
  • 数据采用键值对表示
  • 多个数据由逗号分隔
2.2.4 JSON值 
  • 数字(整数、浮点数)
  • 字符串(在双引号中)
  • 逻辑值(true、false)
  • 数组(在中括号中)
  • 对象(在大括号中)
  • null:JSON中空值用null表示,对应python中的None 

2.2 JSON操作 

2.2.1 总体介绍

操作内容:

  • Python中的字典与JSON之间的转换
  • JSON文件读写

注意点:

在python中想要操作JSON,需要导入依赖包【import json】

2.2.2 字典与JSON转换
  • 把python字典转换为json字符串 dumps
  • 把json字符串转换为python字典 loads
import json
# 把python字典转换为json字符串
dict = {
    "name" : "zhangsan",
    "age" : 18,
    "is_man" : True,
    "school" : None
}
# 使用dumps方法,得到的结果是json字符串
json_str = json.dumps(dict)
print(json_str)

# 把json字符串转换为python字典
json_str1 = '{"name": "zhangsan", "age": 18, "is_man": true, "school": null}'
#使用loads方法,得到的结果是python字典
dict2 = json.loads(json_str1)
print(dict2)

json文件读写

import json

# 读取data.json文件
with open("data.json", "r", encoding="utf-8") as f:
    data1 = json.load(f)
    print(data1)

# 把字典写入json文件 “data2.json”
data2 = data1
with open("data2.json", "w", encoding="utf-8") as f:
    json.dump(data2, f)

# 把字典写入json文件“data3.json”--------解决写入中文的问题
data3 = data1
with open("data3.json", "w", encoding="utf-8") as f:
    json.dump(data2, f, ensure_ascii=False)

2.3 JSON实战一

2.3.1 案例介绍

对tpshop电商项目的登录模块进行自动化测试,需要使用数据驱动,对PO模式框架增加数据驱动

2.3.2 实现步骤

1、编写测试用例

2、编写测试代码 -

  • 采用PO模式分层思想对页面进行封装
  • 编写测试脚本
  • 定义文件,实现参数化
2.3.3 字典数据参数化代码实战 

使用参数化优化代码前:

import time

import pytest
from selenium import webdriver

from v7_ddt.page.login_page import LoginPage
from v7_ddt.utils.driver_utils import DriverUtils


# 定义测试类
class TestLogin:
    def setup(self):
        # 实例化浏览器驱动
        self.driver = DriverUtils.get_driver()
        self.login_page = LoginPage(self.driver)
        self.driver.get("http://localhost/")

    def teardown(self):
        # 关闭浏览器
        time.sleep(3)
        DriverUtils.close_driver()

  

    # 定义用户不存在的测试方法
    def test_login_account_not_exist(self):
        # 点击首页登录链接,进入登录页面
        self.login_page.click_login_link()
        # 输入用户名
        self.login_page.input_username("1800000000")
        # 输入密码
        self.login_page.input_password("123456")
        # 输入验证码
        self.login_page.input_verifycode("8888")
        # 点击登录按钮
        self.login_page.click_login_button()
        # 断言
        assert "账号不存在!" == self.login_page.get_error()

    # 定义密码错误的测试方法
    def test_login_password_error(self):
        # 1、点击首页登录链接,进入登录页面
        self.login_page.click_login_link()
        # 2、输入用户名
        self.login_page.input_username("1800000000")
        # 3、输入错误密码
        self.login_page.input_password("123456")
        # 4、输入验证码
        self.login_page.input_verifycode("8888")
        # 5、点击登录按钮
        self.login_page.click_login_button()
        # 断言
        assert "密码错误!" == self.login_page.get_error()

 使用参数化优化代码后:

import time

import pytest
from selenium import webdriver

from v7_ddt.page.login_page import LoginPage
from v7_ddt.utils.driver_utils import DriverUtils


# 定义测试类
class TestLogin:
    def setup(self):
        # 实例化浏览器驱动
        self.driver = DriverUtils.get_driver()
        self.login_page = LoginPage(self.driver)
        self.driver.get("http://localhost/")

    def teardown(self):
        # 关闭浏览器
        time.sleep(3)
        DriverUtils.close_driver()

    # 定义测试登录的方法,实现脚本参数化
    dict1 = {"username": "1700000000", "password": "123456", "code": "8888", "msg": "账号不存在!"}
    dict2 = {"username": "1800000000", "password": "123456", "code": "8888", "msg": "密码错误!"}

    @pytest.mark.parametrize("params", [dict1, dict2])
    def test_login(self, params):
        # 1、点击首页登录链接,进入登录页面
        self.login_page.click_login_link()
        # 2、输入用户名
        self.login_page.input_username(params["username"])
        # 3、输入密码
        self.login_page.input_password(params["password"])
        # 4、输入验证码
        self.login_page.input_verifycode(params["code"])
        # 5、点击登录按钮
        self.login_page.click_login_button()
        # 6、断言
        assert params["msg"] == self.login_page.get_error()

2.3.4 json文件参数化代码实战

目录及json文件

 login_page.py

import json
import time

import pytest
from selenium import webdriver

from v7_ddt.page.login_page import LoginPage
from v7_ddt.utils.driver_utils import DriverUtils


def analyze_data():
    with open("./data/login_data.json", "r", encoding="utf8") as s:
        # json.load(s)中包含了键和值,但是测试不需要键,所以要遍历然后存到一个新列表中
        # print(json.load(s))
        list_data = []
        dict_data = json.load(s)
        for value in dict_data:
            # print(value)
            list_data.append(value)
        return list_data


# 定义测试类
class TestLogin:
    def setup(self):
        # 实例化浏览器驱动
        self.driver = DriverUtils.get_driver()
        self.login_page = LoginPage(self.driver)
        self.driver.get("http://localhost/")

    def teardown(self):
        # 关闭浏览器
        time.sleep(3)
        DriverUtils.close_driver()

    # 定义测试登录的方法,实现脚本参数化
    dict1 = {"username": "1700000000", "password": "123456", "code": "8888", "msg": "账号不存在!"}
    dict2 = {"username": "1800000000", "password": "123456", "code": "8888", "msg": "密码错误!"}

    # dict_data = [dict1,dict2]     这个就相当于json转换后的字典数据
    @pytest.mark.parametrize("params", analyze_data())
    def test_login(self, params):
        # 1、点击首页登录链接,进入登录页面
        self.login_page.click_login_link()
        # 2、输入用户名
        self.login_page.input_username(params["username"])
        # 3、输入密码
        self.login_page.input_password(params["password"])
        # 4、输入验证码
        self.login_page.input_verifycode(params["code"])
        # 5、点击登录按钮
        self.login_page.click_login_button()
        # 6、断言
        assert params["msg"] == self.login_page.get_error()

抽离解析函数-新建一个base_anlyze.py文件,用于存放解析方法 

这样就可以更加方便地维护和调用函数

2.4 JSON实战二

2.4.1 案例介绍
2.4.2 实现步骤

1、编写测试用例

1+1
1+2
1+2+3

2、搭建自动化测试框架(可以参考JSON实战一的框架)

2.4.3 代码实战

项目目录

base_action.py

class BaseAction:

    def __init__(self, driver):
        self.driver = driver

    def find_el(self, feature):
        return self.driver.find_element(*feature)

    def find_els(self, feature):
        return self.driver.find_elements(*feature)

    # 获取数字按钮
    def find_el_num(self, feature, num):
        return self.driver.find_element(feature[0], feature[1].format(str[num]))
    # simple{}.format(str(num))

    def click(self, feature):
        return self.find_els(feature).click()

    def input(self, feature, content):
        return self.find_els(feature).send_keys(content)

    def clear(self, feature):
        return self.find_els(feature).clear()

 base_anlyze.py

import json


def analyze_data(filename):
    with open("./data/"+filename, "r", encoding="utf8") as s:
        # json.load(s)中包含了键和值,但是测试不需要键,所以要遍历然后存到一个新列表中
        # print(json.load(s))
        list_data = []
        dict_data = json.load(s)
        for value in dict_data:
            # print(value)
            list_data.append(value)
        return list_data

 cal_data.json

{
  "add_001":{
    "data": [1,1],
    "result": 2
  },
  "add_002":{
    "data": [1,2],
    "result,": 3
  },
  "add_003": {
    "data": [1,2,3],
    "result": 6
  }

}

cal_page.py

from selenium.webdriver.common.by import By

from v7_ddt_caculator.base.base_action import BaseAction


class CalPage(BaseAction):
    # 数字 按钮
    # 数字按钮的元素定位都是simple前缀
    digit_btn = By.ID, "simple{}"
    # 加号 按钮
    add_btn = By.ID, "simpleAdd"
    # 等号 按钮
    eq_btn = By.ID, "simpleEqual"
    # 计算结果
    result = By.ID, "resultIpt"

    # 点击数字
    def click_digit_btn(self, num):
        return self.find_el_num(self.digit_btn, num).click()

    # 点击加号
    def click_add_btn(self):
        return self.click(self.add_btn)

    # 点击等号
    def click_eq_btn(self):
        return self.click(self.eq_btn)

    # 获取计算结果
    def get_result(self):
        return self.find_el(self.result).get_attribute("value")


test_cal.py

import json
import time

import pytest
from selenium import webdriver

from v7_ddt_caculator.base.base_analyze import analyze_data
from v7_ddt_caculator.page.cal_page import CalPage
from v7_ddt_caculator.utils.driver_utils import DriverUtils


# 定义测试类
class TestCal:
    def setup(self):
        # 实例化浏览器驱动
        self.driver = DriverUtils.get_driver()
        self.cal_page = CalPage(self.driver)
        self.driver.get("http://cal.apple886.com/")

    def teardown(self):
        # 关闭浏览器
        time.sleep(3)
        DriverUtils.close_driver()

    @pytest.mark.parametrize("params", analyze_data("cal_data.json"))
    def test_cal(self, params):
        for i in params["data"]:
            self.cal_page.digit_btn(i)
            self.cal_page.add_btn()

        # self.cal_page.digit_btn(params["data"][0])
        # self.cal_page.add_btn()
        # self.cal_page.digit_btn(params["data"][1])
        self.cal_page.eq_btn()
        assert str(params["result"]) == self.cal_page.get_result()

driver.utils

# 获取/关闭浏览器驱动的类
import time

from selenium import webdriver


class DriverUtils:
    __driver = None  # 变量加两个下划线,防止外部方法调用,避免与测试用例的驱动混淆

    # 获取浏览器驱动
    @classmethod  # 可以通过类名来调用方法
    def get_driver(cls):
        # 实例化浏览器驱动
        if cls.__driver is None:
            cls.__driver = webdriver.Chrome()
            cls.__driver.maximize_window()
            cls.__driver.implicitly_wait(10)
        return cls.__driver

    # 获关闭浏览器驱动
    @classmethod  # 可以通过类名来调用方法
    def close_driver(cls):
        if cls.__driver is not None:
            # 关闭浏览器
            time.sleep(3)
            cls.__driver.quit()
            cls.__driver = None

三、日志收集 

3.1 日志相关概念

概念:

日志就是用于记录系统运行时的信息,也称为Log

作用:

  • 调试程序
  • 了解系统运行的情况是否正常
  • 程序运行故障分析和问题定位
  • 用来做用户行为分析和数据统计

级别:

思考:日志记录的信息重要性都一样吗?

  1. 日志级别:日志信息的重要性
  2. 常见日志级别:(从严重程度排序)
  • DEBUG        调试,打印非常详细的日志信息
  • INFO             一般信息,打印一般的日志信息,突出强调程序运行过程
  • WARNING   警告 ,打印警告日志信息,表面会出现潜在的错误,不影响程序运行
  • ERROR        错误,打印错误异常信息,该级别的错误出现表示程序一些功能无法使用
  • CRITICAL     严重错误级别,表示程序可能无法继续运行
     

当为程序指定一个日志级别后,程序会记录大于等于该级别的日志信息,而不仅仅是记录指定级别的日志
一般建议只使用DEBUG、INFO、WARNING、ERROR四个级别

3.2 日志基本用法

3.2.1 基本用法

Python中有一个标准库,logging模块可以直接记录日志

使用方法:导入logging包,输出日志

import logging

logging.debug("这是一条调试信息")
logging.info("这是一条一般信息")
logging.warning("这是一条警告信息")
logging.error("这是一条错误信息")
logging.critical("这是一条严重错误信息")

 logging中默认日志级别是warning,程序中大于等于该级别的日志信息才会输出

3.2.2 设置日志级别 

如何选择日志级别?

  • 在开发和测试环境中,为了尽可能详细查看程序的运行状态,可以使用DEBUG或INFO级别的日志获取详细的日志信息,但非常消耗计算机的性能
  • 在生产环境中,通常只记录程序的异常和错误信息,设置日志级别为WARING和ERROR级别即可,这样可以减少服务器的压力,也方便问题的排查

 

3.2.3 设置日志格式 

默认格式:日志级别:Logger名称:日志内容

 

3.2.4 将日志信息输出到文件中

python的logging模块将日志打印到了标准输出(控制台)中 

将日志信息输出到文件

logging.basicConfig(filename="xxx.log") 

3.3 日志高级用法 

3.3.1 总体介绍

思考

  • 如何将日志信息同时输出到控制台和日志文件中?
  • 如何将不同级别日志输出到不同日志文件中?
  • 如何解决日志文件过大的问题? 

3.3.2 logging日志模块四大组件 

3.3.2.1 总体介绍 
  • 日志器:Logger,提供了程序使用日志的入口
  • 处理器:Handler,将logger创建的日志记录发送到合适的输出
  • 格式器:Fomatter,决定日志的输出格式
  • 过滤器:Filter,提供更细粒度的控制工具,决定输出哪条日志,丢弃哪条日志

整个logging模块就是靠这些组件完成日志处理的 

3.3.2.2 组件之间的关系 

日志器(Logger)是入口,真正干活的是处理器(Handler),处理器还可以通过格式器(Formatter)和过滤器(Filter)对要输出的日志内容做格式化和过滤

  • 日志器可以通过处理器将日志信息输出到目标位置
  • 不同处理器可以输出到不同位置
  • 日志器可以设置多个处理器将同一条日志记录输出到不同位置
  • 每个处理器可以设置自己的格式器实现同一条日志不同格式输出
  • 每个处理器都可以设置自己的格式器实现日志过滤
3.3.2.3 日志器Logger

创建Logger对象:

logger = logging.getLogger(name)

可选参数name:

如果不写name,日志器默认为root,如果写了name,如logger=logging.getLogger("mylogger"),则日志器名称为mylogger

打印日志:

logging.debug("这是一条调试信息")
logging.info("这是一条一般信息")
logging.warning("这是一条警告信息")
logging.error("这是一条错误信息")
logging.critical("这是一条严重错误信息")

设置日志级别:

logger.setLevel()

为logger对象添加一个handler对象:

logger.addHandler()

为logger对象添加一个filter对象

logger.addFilter

3.3.2.4 处理器Handler

创建一个Handler对象

在程序中不应该直接实例化和使用Handler实例,因为Handler是一个基类,它只定义了Handler应有的接口,应该使用Handler实现类创建对象

解释:Handler相当于工人,但是工人下面有很多工种,比如园丁、建筑工人等,直接实例化不具体,需要实现类创建对象 

创建方式:

  • 输出日志到控制台        logging.StreamHandler()
  • 输出到文件,按时间切割        logging.handlers.TimeRotatingFileHandler()
  • 输出到磁盘文件        默认文件大小会无限增长
  • 输出到文件,按文件大小切割
  • 将日志消息以get或post请求方式发送给http服务器
  • 将日志消息发送给一个指定的email地址

 常用方法

为handler设置格式器对象        handler.setFormatter()

3.3.2.5 格式器Formatter

作用:用于配置日志信息的格式

创建Formatter对象:

  • logging.Formatter(fmt=None,datefmt=None)
  • fmt:消息格式化字符串,如果不指定该参数默认使用message的原始值
  • datefmt:日期格式化字符串,如果不指定则使用默认日期

3.4 日志案例分析

3.4.1 案例说明

可读性比较好的需要具备一些特征:

  • 在控制台和文件都能输出

  • 文件输出能够按时间切割 

实现步骤: 

1、导包

2、创建日志器对象,设置日志级别

3、创建处理器对象:输出到控制台+文件(按时间切割)

4、创建格式器对象

5、将格式器添加到处理器

6、将处理器添加到日志器

7、打印日志 

示例代码: 

# 1、导包
import logging
import logging.handlers
# 2、创建日志器对象,设置日志级别
# logger = logging.getLogger(不设置默认为root)
logger = logging.getLogger("jack")
logger.setLevel(level=logging.DEBUG)
# 3、创建处理器对象:输出到控制台+文件(按时间切割)
ls = logging.StreamHandler()
# filename表示输出日志文件路径,when表示切割时间单位,backCount表示备份
ln = logging.handlers.TimedRotatingFileHandler(filename="jack.log", when="s", backupCount=3)
# 4、创建格式器对象
fmt = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(funcName)s:%(lineno)d] --%(message)s"
formatter = logging.Formatter(fmt=fmt)
# 5、将格式器添加到处理器
ls.setFormatter(formatter)
ln.setFormatter(formatter)
# 6、将处理器添加到日志器
logger.addHandler(ls)
logger.addHandler(ln)
# 7、打印日志
while True:
    logger.debug("这是一条日志消息")

运行结果:

如图所示,在控制台和文件中都打印了日志消息,并且备份了三个文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值