黑马头条测试项目
1. 自动化测试流程
1. 自动化测试流程
- 需求分析
- 挑选适合做自动化测试的功能
- 设计测试用例
- 搭建自动化测试环境
- web自动化测试环境(4个)
- python开发者工具(pycharm, python解释器)
- 浏览器
- 浏览器驱动
- selenium
- app自动化测试环境(5个)
- 手机或模拟器
- 安卓sdk
- python开发工具
- appium服务器
- appium服务客户端
- web自动化测试环境(4个)
- 设计自动化测试项目架构
- 编写代码
- 执行测试用例
- 生成测试报告并分析结果
2. 黑马头条项目简介
1. 项目背景
作为一个IT教育机构,拥有自己开发且实际运营的产品,将开发和运营的技术作为授课的内容,对于学员而言学到的都是一手的真实案例和实际经验,知识内容也可以细化深入。 而且一个产品就可以涵盖公司多个学科的技术,衍生的课程价值辐射多个学科。这可以作为公司的一个核心竞争力。
2. 产品定位
一款汇集科技资讯、技术文章和问答交流的用户移动终端产品。 用户通过该产品,可以获取最新的科技资讯,发表或学习技术文章,讨论交流技术问题。
3. 项目目标
- 研发并上线运营头条产品
- 从实际的产品技术中孵化产品经理、Python 人工智能、Python 数据分析、Python Web、测试、运维等课程案例
- 构建公司自己的数据仓库和算法模型
3. 产品功能架构
自媒体:又称“个人媒体”,是指私人化、平民化、自主化的传播者, 以现代化、电子化的手段,向不特定的大多数或者特定的单个人传递信息的新媒体的总称。 自媒体平台包括:博客、微博、微信、抖音、百度贴吧、论坛/BBS等网络社区。
产品主要分为三个前端子产品:
- 用户端
APP,用户可以查看资讯、文章内容,进行问答讨论交流 - 自媒体运营平台
PC网站,自媒体用户可以管理文章、评论,查看分析粉丝数据 - 系统后台
PC网站,内部运营管理系统
产品后端系统功能可分为以下几个部分:
- 推荐系统部分
负责为用户个性化推荐资讯和文章 - 人工智能部分
机器自动审核文章、文章画像提取、机器学习推荐算法等 - 日志系统部分
收集保存用户行为数据和系统运行状态数据 - 爬虫部分
爬取网站资讯文章数据,作为产品启动的初期数据来源
4. 产品技术架构
5. 负载均衡
负载均衡器: 负载均衡(Load Balance)可以将工作任务分摊到多个处理单元, 从而提高并发处理能力
负载均衡器建立在现有网络结构上, 使用它可以实现网络设备的带宽, 增加吞吐量, 加强网络数据处理能力, 提高网络的灵活性和可用性
负载均衡器在工作当中, 要么使用nginx(软件负载均衡), 要么使用F5(硬件负载均衡), 如果使用的是阿里云或者是腾讯云, SLB负载均衡, nginx既可以做负载均衡, 也可以做web前端的中间件
6. 消息队列
消息队列(Message Queue, MQ): 是在消息传输过程中保存消息的容器
消息队列中间件是分布式系统中重要的组件, 主要解决应用解耦, 异步消息, 流量削峰等问题, 实现高性能, 高可用, 可伸缩和最终一致性架构
目前使用较多的消息队列有: Kafka, ActiveMQ, RabbitMQ, ZeroMQ, MetaMQ, RocketMQ
应用场景
- 异步处理
将业务逻辑处理由串行方式变成并行方式(好友推荐, 新闻推荐) - 应用解耦
订单系统 --> 库存系统
发送短信验证码 - 流量削峰
秒杀, 抢购活动(用户访问量过大, 导致流量暴增, 应用挂掉) - 日志处理
将消息队列用早日志处理中, 解决大量日志传输的问题
7. 用例设计
1. 编写自动化测试用例的原则
- 自动化用例一般只实现核心业务流程或者重复执行率较高的功能
- 自动化测试用例的选择一般以"正向"逻辑的验证为主
- 不是所有的手工用例都可以使用自动化测试来执行
- 尽量减少多个用例脚本之间的依赖
- 自动化测试用例执行完毕后, 一般需要回到原点
2. 编写测试用例
1. 自媒体
2. 后台管理系统
3. APP
8. 自动化框架结构设计
1. 初始化项目
1. 新建项目
项目名称: uiAutoTestHmtt
2. 创建目录结构
- uiAutoTestHmtt #项目名称
- base #封装PO基类
- page #封装PO页面对象
- script #定义测试用例脚本
- data #存放测试数据
- report #存放生成的测试报告
- log #存放测试文件
- screenshot #存放截图
- config.py #定义项目配置信息
- utils.py #定义工具类
- pytest.ini #pytest配置文件
- conftest #pytest.ini运行配置
3. 安装依赖包
- selenium包
- Appium-Python-Client包
- pytest包
- pytest-ording包
- allure-pytest包
2. 初始化代码
1. 封装工具类
- 封装PO类, 定义BasePage和BaseHandle
- pytest.ini
[pytest]
addopts = -s -v --html=report/report.html --reruns 3
testpaths = ./uiAutoTestHmtt/script
python_files = test_*.py
python_classes = Test*
python_functions = test_*
- utils.py
from selenium import webdriver
from appium import webdriver as appdriver
class UtilsDriver:
_mp_driver = None #表示的是自媒体平台的驱动
_mis_driver = None #表示的是后台管理驱动
_app_driver =None # 表示的是app的驱动
# 定义获取自媒体平台的浏览器驱动
@classmethod
def get_mp_driver(cls):
if cls._mp_driver is None:
cls._mp_driver = webdriver.Chorm()
cls._mp_driver.get("http://ttmp.research.itcast.cn/")
cls._mp_driver.maximize_window()
return cls._mp_driver
# 定义退出自媒体平台浏览器驱动
@classmethod
def quit_mp_driver(cls):
if cls._mp_driver is not None:
cls.get_mp_qdriver().quit()
cls._mp_driver = None
# 定义获取后台管理的驱动
@classmethod
def get_mis_driver(cls):
if cls._mis_driver is None:
cls._mis_driver = webdriver.Chrom()
cls._mis_driver.maximize_window()
cls._mis_driver.get("http://ttmis.research.itcast.cn/")
return cls._mis_driver
# 定义退出后台管理系统操作方法
@classmethod
def quit_mis_driver(cls):
if cls._mis_driver is not None:
cls.get_mis_driver().quit()
cls._mis_driver = None
# 定义获取app驱动
@classmethod
def get_app_driver(cls):
if cls._app_driver is None:
des_cap = {
"platformName": "android",
"platformVersion": "5.1.1",
"deviceName": "emulator-5554",
"appPackage": "com.itcast.toutiaoApp",
"appActivity": ".MainActivity",
"noReset": True, #用来记住app的session
"resetKeyboard": True, #重置设备输入键盘
"unicodeKeyboard": True # 采用unicode编码输入
}
cls._app_driver = appdriver.Romate("http://127.0.0.1:4723/wd/hub", des_cap)
return cls._app_driver
# 退出app浏览器驱动
@classmethod
def quit_app_driver(cls):
if cls._app_driver is not None:
cls.get_app_driver().quit()
cls._app_driver = None
基类的封装
- 媒体平台的封装
base包 -> 创建mp包 -> 在mp包下创建base.py文件
from selenium.webdriver.support.wait import WebDriverWait
from utils import UtilsDriver
# 定义自媒体平台的基类
# 对象库层基类的封装
class BasePage:
def __init__(self):
self.driver = UtilsDriver.get_mp_driver() # 获取自媒体浏览器驱动
def get_element(self, location):
wait = WebDriverWait(self.driver, 10, 1)
element = wait.until(lambda x: x.find_element(*location))
return element
# 操作层基类的封装
class BaseHandle:
def input_text(self, element, text):
element.clear()
element.send_keys(text)
- 后台管理基类封装
base包 -> 创建mis包 -> 在mis包下创建base.py文件
from selenium import webdriver
from utils import UtilsDriver
# 定义后台管理的基类
# 页面对象层封装
class BasePage:
def __init__(self):
self.driver = UtilsDriver.get_mis_driver() # 获取后台管理驱动
def get_element(self, location)
wait = WebDriverWait(self.driver, 10, 1)
element = wait.until(lambda x: x.find_element(*location))
return element
# 操作层的封装
class BaseHandle:
def input_text(self, element, text):
element.clear()
element.send_keys(text)
- app基类封装
base包 -> 创建app包 -> 在app包下创建base.py文件
from selenium import webdriver
from utils import UtilsDriver
# 定义app平台的基类
# 对象库层基类封装
class BasePage:
def __init__(self):
self.driver = UtilsDriver.get_app_driver() # 获取app浏览器驱动
def get_element(self, location):
wait = WebDriverWait(self.driver, 10, 1)
element = wait.until(lambda x: x.find_element(*location))
return element
# 操作层基类封装
class BaseHandle:
def input_text(self, element, text):
element.clear()
element.send_keys(text)
自媒体平台的封装
自媒体登录页面的封装
page包 -> 新建mp包 -> 创建登录界面login_page.py
from base.mp.base import BasePage, BaseHandle
from selenium.webdriver.common.by import By
# 定义对象库层
class LoginPage(BasePage):
def __init__(self):
super().__init__()
# 手机号码输入框
self.mobile = By.XPATH, "//*[@placeholder='请输入手机号']"
# 验证码输入框
self.code = By.XPATH, "//*[@placeholder='验证码']"
# 登录按钮
self.login_btn = By.CSS_SELECTOR, ".el-button--primary"
# 定位手机号码输入框
def find_mobile(self):
return self.get_element(self.mobile)
# 定位验证码输入框
def find_code(self):
return self.get_element(self.code)
# 定位登录按钮
def find_login_btn(self):
return self.get_element(self.login_btn)
# 定义操作层
class LoginHandle(BaseHandle):
def __init__(self):
self.login_page = LoginPage()
# 输入手机号码
def input_mobile(self, mobile):
self.input_text(self.login_page.find_mobile(), mobile)
# 输入验证码
def input_code(self, code):
self.input_text(self.login_page.find_code(), code)
# 点击登录按钮
def click_login_btn(self):
self.login_page.find_login_btn().click()
# 定义业务层
class LoginProxy:
def __init__(self):
self.login_handle = LoginHandle()
def login(self, mobile, code):
self.login_page.input_mobile(mobile) # 输入手机号码
self.login_page.input_code(code) # 输入验证码
self.login_page.click_login_btn() # 点击登录按钮
自媒体平台首页封装
page包 -> 新建mp包 -> 创建登录界面home_page.py
from selenium.webdriver.common.by import By
from base.mp.base import BasePage, BaseHandle
# 定义对象层
class HomePage(BasePage):
def __init__(self):
super().__init__()
# 用户名显示元素
self.username = By.CSS_SELECTOR, ".user-name"
# 内容管理菜单
self.content_manage = By.XPATH, "//*[text()='内容管理']"
# 发布文章
self.public_btn = By.XPATH, "//*[@class='sidebar-el-menu el-menu']/div[2]/li/ul/li[1]"
# 定位用户名显示元素
def find_username(self):
return self.get_element(self.username)
# 定位内容管理菜单
def find_content_manage(self):
return self.get_element(self.content_manage)
# 定位发布文章菜单
def find_publish(self):
return self.get_element(self.publish_btn)
#定义操作层
class HomeHandle(BaseHandle):
def __init__(self):
self.home_page = HomePage()
# 获取用户名信息
def get_username(self):
return self.home_page.find_username().text
# 点击内容管理菜单
def click_content_manage(self):
self.home_page.find_content_manage().click()
# 点击发布文章
def click_publish_btn(self):
self.home_page.find_publish().click()
# 定义业务层
class HomeProxy:
def __init__(self):
self.home_handle = HomeHandle()
# 获取用户名信息
def get_username_msg(self):
return self.home_handle.get_username()
# 跳转到发布文章页面
def go_publish_page(self):
self.home_handle.click_content_manage()
self.home_handle.click_publish_btn()
自媒体平台发布文章页面封装
page包 -> 新建mp包 -> 创建登录界面publish_page.py
from base.mp.base import BasePage, BaseHandle
from selenium.webdriver.common.by import By
# 定义对象库层
class PublicPage(BasePage):
def __init__(self):
super().__init__()
#文章名称输入框
self.title = By.XPATH, "//*[@placeholder='文章名称']"
# iframe元素对象
self.iframe_ele = By.ID, "publishTinymce_ifr"
# 文章内容输入框
self.content = By.CSS_SELECTOR, ".mce-content-body"
# 封面选择
self.cover = By.XPATH, "//*[@role=radiogroup]/label[4]/span[2]"
# 频道选择
self.channel = By.XPATH, "//*[@placeholder='请选择']"
# 发表按钮
self.publish_btn = By.CSS_SELECTOR, "[class='el-button filter-item el-button--primary']"
# 寻找文章标题
def find_title(self):
return self.get_element(self.title)
# 找到切换的iframe
def find_iframe_ele(self):
return self.get_element(self.iframe_ele)
# 找到文章内容输入框
def find_content(self):
return self.get_element(self.content)
# 找到封面选择输入框
def find_cover(self):
return self.get_element(self.cover)
# 定位频道选择输入框
def find_channel(self):
return self.get_element(self.channel)
# 定位发表按钮
def find_publish_btn(self):
return self.get_element(self.publish_btn)
# 操作层
class PublishHandle(BaseHandle):
def __init__(self):
self.publish_page = PublishPage()
self.driver = UtilsDriver.get_mp_driver()
# 输入文章标题
def input_title(self, title)
self.input_text(self.publish_page.find_title(), title)
# 输入文章内容
def input_content(self)
# 切换到iframe中
self.driver.switch_to.frame(self.publish_page.find_iframe())
# 输入文章内容
self.input_text(self.publish_page.find_input_content(), content)
# 切回默认首页
self.driver.switch_to.default_content()
# 选择封面
def choice_cover(self):
self.publish_page.find_cover().click()
#选择频道
def choice_channel(self):
choice_channel(self.driver, self.publish_page.find_channel_choice(), channel)
#点击发布按钮
def click_publish_btn(self):
self.publish_page.find_publish_btn().click()
# 业务层封装
class PublishProxy:
def __init__(self):
self.publish_handle = PublishHandle()
# 发布文章
def publish_article(self, title, content, channel):
# 输入文章标题
self.publish_handle.input_title(title)
self.publish_handle.input_content(content)
self.publish_handle.choice_cover()
self.publish_handle.choice_channel(channel)
self.publish_handle.click_publish_btn()
在utils模块中封装选择频道的方法
......
def choice_channel(driver, element, channel)
"""
param driver: 浏览器驱动对象
param element: 选择元素
param channel: 要选择的文本内容
"""
element.click()
time.sleep(1)
xpath = "//*[@class='el-select-dropdown__wrap el-scrollbar__wrap']//*[text()='{0}']".format(channel)
driver.find_element(By.XPATH, xpath).click()
在utils中封装一个方法, 判断元素是否存在
......
# 封装一个方法判断元素是否存在
def is_exis(driver, text):
"""
param driver: 浏览器驱动对象
param text: 定位元素的文本内容
"""
xpath = "//*[contains(text(), '{0}')]".format(text)
try:
time.sleep(2)
return driver.find_element(By.XPATH, xpath)
expect Expection as e:
return False
自媒体测试用例的编写
scripts模块 -> 创建mp模块 -> 创建test_publish_article.py文件
from page.mp.login_page import LoginProxy
from page.mp.home_page import HomeProxy
from page.mp.publish_page import PublishProxy
class TestPublishArticle:
# 定义类级别的fixture初始化方法
def setup_class(self):
self.login_proxy = LoginProxy()
self.home_proxy = HomeProxy()
self.publish_proxy = PublishProxy()
# 定义类级别的fixture销毁方法
def teardowm_class(self):
UtilsDriver.quit_mp_driver()
# 定义登录测试用例
def test_login(self):
self.login_proxy.login("18815687257", "246810")
username = self.home_proxy.get_username_msg()
assert "test123" == username
# 定义文章测试用例
def test_publish_article(self):
self.home_proxy.go_publish_page()
self.publish_proxy.publish_article("发布文章_0321_09", "发布文章_0321_09发布文章_0321_09", "数据库")
assert is_exist(UtilsDriver.get_mp_driver(), "新增文章成功")
后台管理系统页面的封装
后台管理系统登录页面的封装
page包 -> 新建mis包 -> 创建登录界面login_page.py
from selenium.webdriver.commom.by import By
'''页面对象层'''
class LoginPage(BasePage):
def __init__(self):
super().__init__()
self.username = By.Name, "username"
self.password = By.Name, "password"
self.login_btn = By.ID, "inp1"
# 定位用户名输入框
def find_username(self):
return self.get_element(self.username)
# 定位密码框
def find_password(self):
return self.get_element(self.password)
# 定位登录按钮
def find_login_btn(self):
return self,get_element(self.login_btn)
"""操作层"""
class LoginHandle(BaseHandle):
def __init__(self):
self.login_page = LoginPage()
# 输入用户名
def input_usernane(self, username):
self.input_text(self.login_page.find_username(), username)
# 输入密码
def input_password(self, password):
self.input_text(self.login_page.find_password(), password)
# 点击登录
def _click_login_btn(self):
# 定义JS
js = "document.getElementById('inp1').removeAttribute('disabled')"
# 通过execute_script方法执行JS
self.login_page.driver.execute_script(js)
# 点击登录按钮
self.login_page.find_login_btn().click()
# 定义业务层
class LoginProxy:
def __init__(self):
self.login_handle = LoginHandle()
# 登录业务操作
def login(self, username, password):
self.login_handle.input_username(username)
self.login_handle.input_password(password)
self.login_handle.click_login_btn()
后台登录系统主页的封装
page包 -> mis包 -> 创建登录界面home_page.py
# 页面对象层
class HomePage(BasePage):
def __init__(self):
super().__init__()
# 用户信息
self.user_info = By.CSS_SELECTOR, ".user_info.span"
# 信息管理
seld.content_manage = By.XPATH, "//*[@class='side_bar']/ul/li[3]/a"
# 内容审核
self.content_audit = By.XPATH, "//*[@class='current3']/li[3]/a"
# 定位用户信息
def find_user_info(self):
return self.get_element(self.user_info)
# 定位信息管理菜单
def find_content_manage(self):
return self.get_elememt(self.content_manage)
# 定位内容审核状态
def find_context_audit(self):
return self.get_element(self.content_audit)
# 定义操作层
class HomeHandle(BaseHnadle):
def __init__(self):
self