【Selenium+Pytest+allure报告生成自动化测试框架】附带项目源码和项目部署文档

目录

前言

【文章末尾给大家留下了大量的福利】

测试框架简介

首先管理时间

添加配置文件

conf.py

config.ini

读取配置文件

记录操作日志

简单理解POM模型

简单学习元素定位

管理页面元素

封装Selenium基类

创建页面对象

简单了解Pytest

pytest.ini

编写测试用例

conftest.py

执行用例

发送邮件

pytest使用allure测试报告

allure安装

allure初体验

allure装饰器介绍

报告的生成和展示

allure发生错误截图

开源地址

前言

selenium自动化+ pytest测试框架+allure报告

本章你需要

  • 一定的python基础——至少明白类与对象,封装继承

  • 一定的selenium基础——本篇不讲selenium,不会的可以自己去看selenium中文翻译网

  • 文章末尾给大家留下了大量的福利】

 

测试框架简介

  • 测试框架有什么优点呢:

    • 代码复用率高,如果不使用框架的话,代码会很冗余
    • 可以组装日志、报告、邮件等一些高级功能
    • 提高元素等数据的可维护性,元素发生变化时,只需要更新一下配置文件
    • 使用更灵活的PageObject设计模式
  • 测试框架的整体目录

 

 这样一个简单的框架结构就清晰了。

知道了以上这些我们就开始吧!

我们在项目中先按照上面的框架指引,建好每一项目录。

注意:python包为是的,都需要添加一个__init__.py文件以标识此目录为一个python包。


首先管理时间

首先呢,因为我们很多的模块会用到时间戳,或者日期等等字符串,所以我们先单独把时间封装成一个模块。

然后让其他模块来调用即可。在utils目录新建times.py模块

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> time
<span style="color:#7171bf">import</span> datetime
<span style="color:#7171bf">from</span> functools <span style="color:#7171bf">import</span> wraps<span style="color:#7171bf">def</span> <span style="color:#61aeee">timestamp</span>():<span style="color:#98c379">"""时间戳"""</span><span style="color:#7171bf">return</span> time.time()<span style="color:#7171bf">def</span> <span style="color:#61aeee">dt_strftime</span>(fmt=<span style="color:#98c379">"%Y%m"</span>):<span style="color:#98c379">"""datetime格式化时间:param fmt "%Y%m%d %H%M%S"""</span><span style="color:#7171bf">return</span> datetime.datetime.now().strftime(fmt)<span style="color:#7171bf">def</span> <span style="color:#61aeee">sleep</span>(seconds=<span style="color:#d19a66">1.0</span>):<span style="color:#98c379">"""睡眠时间"""</span>time.sleep(seconds)<span style="color:#7171bf">def</span> <span style="color:#61aeee">running_time</span>(func):<span style="color:#98c379">"""函数运行时间"""</span><span style="color:#61aeee">    @wraps(func)</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">wrapper</span>(*args, **kwargs):start = timestamp()res = func(*args, **kwargs)<span style="color:#7171bf">print</span>(<span style="color:#98c379">"校验元素done!用时%.3f秒!"</span> % (timestamp() - start))<span style="color:#7171bf">return</span> res<span style="color:#7171bf">return</span> wrapper<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:<span style="color:#7171bf">print</span>(dt_strftime(<span style="color:#98c379">"%Y%m%d%H%M%S"</span>))</code></span></span>

添加配置文件

配置文件总是项目中必不可少的部分!

将固定不变的信息集中在固定的文件中

conf.py

项目中都应该有一个文件对整体的目录进行管理,我也在这个python项目中设置了此文件。

在项目config目录创建conf.py文件,所有的目录配置信息写在这个文件里面。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> os
<span style="color:#7171bf">from</span> selenium.webdriver.common.by <span style="color:#7171bf">import</span> By
<span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> dt_strftime<span style="color:#7171bf">class</span> <span style="color:#61aeee">ConfigManager</span>(<span style="color:#61aeee">object</span>):<span style="color:#5c6370"><em># 项目目录</em></span>BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))<span style="color:#5c6370"><em># 页面元素目录</em></span>ELEMENT_PATH = os.path.join(BASE_DIR, <span style="color:#98c379">'page_element'</span>)<span style="color:#5c6370"><em># 报告文件</em></span>REPORT_FILE = os.path.join(BASE_DIR, <span style="color:#98c379">'report.html'</span>)<span style="color:#5c6370"><em># 元素定位的类型</em></span>LOCATE_MODE = {<span style="color:#98c379">'css'</span>: By.CSS_SELECTOR,<span style="color:#98c379">'xpath'</span>: By.XPATH,<span style="color:#98c379">'name'</span>: By.NAME,<span style="color:#98c379">'id'</span>: By.ID,<span style="color:#98c379">'class'</span>: By.CLASS_NAME}<span style="color:#5c6370"><em># 邮件信息</em></span>EMAIL_INFO = {<span style="color:#98c379">'username'</span>: <span style="color:#98c379">'1084502012@qq.com'</span>,  <span style="color:#5c6370"><em># 切换成你自己的地址</em></span><span style="color:#98c379">'password'</span>: <span style="color:#98c379">'QQ邮箱授权码'</span>,<span style="color:#98c379">'smtp_host'</span>: <span style="color:#98c379">'smtp.qq.com'</span>,<span style="color:#98c379">'smtp_port'</span>: <span style="color:#d19a66">465</span>}<span style="color:#5c6370"><em># 收件人</em></span>ADDRESSEE = [<span style="color:#98c379">'1084502012@qq.com'</span>,]<span style="color:#61aeee">    @property</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">log_file</span>(self):<span style="color:#98c379">"""日志目录"""</span>log_dir = os.path.join(self.BASE_DIR, <span style="color:#98c379">'logs'</span>)<span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(log_dir):os.makedirs(log_dir)<span style="color:#7171bf">return</span> os.path.join(log_dir, <span style="color:#98c379">'{}.log'</span>.<span style="color:#7171bf">format</span>(dt_strftime()))<span style="color:#61aeee">    @property</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">ini_file</span>(self):<span style="color:#98c379">"""配置文件"""</span>ini_file = os.path.join(self.BASE_DIR, <span style="color:#98c379">'config'</span>, <span style="color:#98c379">'config.ini'</span>)<span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(ini_file):<span style="color:#7171bf">raise</span> FileNotFoundError(<span style="color:#98c379">"配置文件%s不存在!"</span> % ini_file)<span style="color:#7171bf">return</span> ini_filecm = ConfigManager()
<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:<span style="color:#7171bf">print</span>(cm.BASE_DIR)
</code></span></span>

这个conf文件我模仿了Django的settings.py文件的设置风格,但是又有些许差异。

在这个文件中我们可以设置自己的各个目录,也可以查看自己当前的目录。

遵循了约定:不变的常量名全部大写,函数名小写。看起来整体美观。

config.ini

在项目config目录新建一个config.ini文件,里面暂时先放入我们的需要测试的URL

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-ini"><span style="color:#e06c75">[HOST]</span>
<span style="color:#d19a66">HOST</span> = https://www.baidu.com
</code></span></span>

读取配置文件

配置文件创建好了,接下来我们需要读取这个配置文件以使用里面的信息。

我们在common目录中新建一个readconfig.py文件

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> configparser
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cmHOST = <span style="color:#98c379">'HOST'</span><span style="color:#7171bf">class</span> <span style="color:#61aeee">ReadConfig</span>(<span style="color:#61aeee">object</span>):<span style="color:#98c379">"""配置文件"""</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self):self.config = configparser.RawConfigParser()  <span style="color:#5c6370"><em># 当有%的符号时请使用Raw读取</em></span>self.config.read(cm.ini_file, encoding=<span style="color:#98c379">'utf-8'</span>)<span style="color:#7171bf">def</span> <span style="color:#61aeee">_get</span>(self, section, option):<span style="color:#98c379">"""获取"""</span><span style="color:#7171bf">return</span> self.config.get(section, option)<span style="color:#7171bf">def</span> <span style="color:#61aeee">_set</span>(self, section, option, value):<span style="color:#98c379">"""更新"""</span>self.config.<span style="color:#7171bf">set</span>(section, option, value)<span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(cm.ini_file, <span style="color:#98c379">'w'</span>) <span style="color:#7171bf">as</span> f:self.config.write(f)<span style="color:#61aeee">    @property</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">url</span>(self):<span style="color:#7171bf">return</span> self._get(HOST, HOST)ini = ReadConfig()<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:<span style="color:#7171bf">print</span>(ini.url)
</code></span></span>

可以看到我们用python内置的configparser模块对config.ini文件进行了读取。

对于url值的提取,我使用了高阶语法@property属性值,写法更简单。


记录操作日志

日志,大家应该都很熟悉这个名词,就是记录代码中的动作。

utils目录中新建logger.py文件。

这个文件就是我们用来在自动化测试过程中记录一些操作步骤的。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> logging
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm<span style="color:#7171bf">class</span> <span style="color:#61aeee">Log</span>:<span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self):self.logger = logging.getLogger()<span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> self.logger.handlers:self.logger.setLevel(logging.DEBUG)<span style="color:#5c6370"><em># 创建一个handle写入文件</em></span>fh = logging.FileHandler(cm.log_file, encoding=<span style="color:#98c379">'utf-8'</span>)fh.setLevel(logging.INFO)<span style="color:#5c6370"><em># 创建一个handle输出到控制台</em></span>ch = logging.StreamHandler()ch.setLevel(logging.INFO)<span style="color:#5c6370"><em># 定义输出的格式</em></span>formatter = logging.Formatter(self.fmt)fh.setFormatter(formatter)ch.setFormatter(formatter)<span style="color:#5c6370"><em># 添加到handle</em></span>self.logger.addHandler(fh)self.logger.addHandler(ch)<span style="color:#61aeee">    @property</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">fmt</span>(self):<span style="color:#7171bf">return</span> <span style="color:#98c379">'%(levelname)s\t%(asctime)s\t[%(filename)s:%(lineno)d]\t%(message)s'</span>log = Log().logger<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:log.info(<span style="color:#98c379">'hello world'</span>)
</code></span></span>

在终端中运行该文件,就看到命令行打印出了:

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-shell">INFO    2020-12-01 16:00:05,467    [logger.py:38]    hello world
</code></span></span>

然后在项目logs目录下生成了当月的日志文件。


简单理解POM模型

由于下面要讲元素相关的,所以首先理解一下POM模型

Page Object模式具有以下几个优点。

该观点来自 《Selenium自动化测试——基于Python语言》

  • 抽象出对象可以最大程度地降低开发人员修改页面代码对测试的影响, 所以, 你仅需要对页
    面对象进行调整, 而对测试没有影响;
  • 可以在多个测试用例中复用一部分测试代码;
  • 测试代码变得更易读、 灵活、 可维护

Page Object模式图

  •  basepage ——selenium的基类,对selenium的方法进行封装
  • pageelements——页面元素,把页面元素单独提取出来,放入一个文件中
  • searchpage ——页面对象类,把selenium方法和页面元素进行整合
  • testcase ——使用pytest对整合的searchpage进行测试用例编写
     

通过上图我们可以看出,通过POM模型思想,我们把:

  • selenium方法
  • 页面元素
  • 页面对象
  • 测试用例

以上四种代码主体进行了拆分,虽然在用例很少的情况下做会增加代码,但是当用例多的时候意义很大,代码量会在用例增加的时候显著减少。我们维护代码变得更加直观明显,代码可读性也变得比工厂模式强很多,代码复用率也极大的得到了提高。


简单学习元素定位

在日常的工作中,我见过很多在浏览器中直接在浏览器中右键Copy Xpath复制元素的同学。这样获得的元素表达式放在 webdriver 中去运行往往是不够稳定的,像前端的一些微小改动,都会引起元素无法定位的NoSuchElementException报错。

所以在实际工作和学习中我们应该加强自己的元素定位能力,尽可能的采用xpath和CSS selector 这种相对稳定的定位语法。由于CSS selector的语法生硬难懂,对新手很不友好,而且相比xpath缺少一些定位语法。所以我们选择xpath进行我们的元素定位语法。
 

xpath#

语法规则

 

定位工具

  • chropath
    • 优点:这是一个Chrome浏览器的测试定位插件,类似于firepath,本人试用了一下整体感觉非常好。对小白的友好度很好。
    • 缺点:安装这个插件需要FQ。
  • Katalon录制工具
    • 录制出来的脚本里面也会有定位元素的信息
  • 自己写——本人推荐这种
    • 优点:本人推荐的方式,因为当熟练到一定程度的时候,写出来的会更直观简洁,并且在运行自动化测试中出现问题时,能快速定位。
    • 缺点:需要一定xpathCSS selector语法积累,不太容易上手。

管理页面元素

本教程选择的测试地址是百度首页,所以对应的元素也是百度首页的。

项目框架设计中有一个目录page_element就是专门来存放定位元素的文件的。

通过对各种配置文件的对比,我在这里选择的是YAML文件格式。其易读,交互性好。

我们在page_element中新建一个search.yaml文件。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-yaml"><span style="color:#98c379">搜索框:</span> <span style="color:#98c379">"id==kw"</span>
<span style="color:#98c379">候选:</span> <span style="color:#98c379">"css==.bdsug-overflow"</span>
<span style="color:#98c379">搜索候选:</span> <span style="color:#98c379">"css==#form div li"</span>
<span style="color:#98c379">搜索按钮:</span> <span style="color:#98c379">"id==su"</span>
</code></span></span>

元素定位文件创建好了,下来我们需要读取这个文件。

common目录中创建readelement.py文件。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> os
<span style="color:#7171bf">import</span> yaml
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm<span style="color:#7171bf">class</span> <span style="color:#61aeee">Element</span>(<span style="color:#61aeee">object</span>):<span style="color:#98c379">"""获取元素"""</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self, name):self.file_name = <span style="color:#98c379">'%s.yaml'</span> % nameself.element_path = os.path.join(cm.ELEMENT_PATH, self.file_name)<span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(self.element_path):<span style="color:#7171bf">raise</span> FileNotFoundError(<span style="color:#98c379">"%s 文件不存在!"</span> % self.element_path)<span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(self.element_path, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:self.data = yaml.safe_load(f)<span style="color:#7171bf">def</span> <span style="color:#61aeee">__getitem__</span>(self, item):<span style="color:#98c379">"""获取属性"""</span>data = self.data.get(item)<span style="color:#7171bf">if</span> data:name, value = data.split(<span style="color:#98c379">'=='</span>)<span style="color:#7171bf">return</span> name, value<span style="color:#7171bf">raise</span> ArithmeticError(<span style="color:#98c379">"{}中不存在关键字:{}"</span>.<span style="color:#7171bf">format</span>(self.file_name, item))<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:search = Element(<span style="color:#98c379">'search'</span>)<span style="color:#7171bf">print</span>(search[<span style="color:#98c379">'搜索框'</span>])
</code></span></span>

通过特殊方法__getitem__实现调用任意属性,读取yaml中的值。

这样我们就实现了定位元素的存储和调用。

但是还有一个问题,我们怎么样才能确保我们写的每一项元素不出错,人为的错误是不可避免的,但是我们可以通过代码来运行对文件的审查。当前也不能所有问题都能发现。

所以我们编写一个文件,在script脚本文件目录中创建inspect.py文件,对所有的元素yaml文件进行审查。
 

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> os
<span style="color:#7171bf">import</span> yaml
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
<span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> running_time<span style="color:#61aeee">@running_time</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">inspect_element</span>():<span style="color:#98c379">"""检查所有的元素是否正确只能做一个简单的检查"""</span><span style="color:#7171bf">for</span> files <span style="color:#7171bf">in</span> os.listdir(cm.ELEMENT_PATH):_path = os.path.join(cm.ELEMENT_PATH, files)<span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(_path, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:data = yaml.safe_load(f)<span style="color:#7171bf">for</span> k <span style="color:#7171bf">in</span> data.values():<span style="color:#7171bf">try</span>:pattern, value = k.split(<span style="color:#98c379">'=='</span>)<span style="color:#7171bf">except</span> ValueError:<span style="color:#7171bf">raise</span> Exception(<span style="color:#98c379">"元素表达式中没有`==`"</span>)<span style="color:#7171bf">if</span> pattern <span style="color:#7171bf">not</span> <span style="color:#7171bf">in</span> cm.LOCATE_MODE:<span style="color:#7171bf">raise</span> Exception(<span style="color:#98c379">'%s中元素【%s】没有指定类型'</span> % (_path, k))<span style="color:#7171bf">elif</span> pattern == <span style="color:#98c379">'xpath'</span>:<span style="color:#7171bf">assert</span> <span style="color:#98c379">'//'</span> <span style="color:#7171bf">in</span> value,\<span style="color:#98c379">'%s中元素【%s】xpath类型与值不配'</span> % (_path, k)<span style="color:#7171bf">elif</span> pattern == <span style="color:#98c379">'css'</span>:<span style="color:#7171bf">assert</span> <span style="color:#98c379">'//'</span> <span style="color:#7171bf">not</span> <span style="color:#7171bf">in</span> value, \<span style="color:#98c379">'%s中元素【%s]css类型与值不配'</span> % (_path, k)<span style="color:#7171bf">else</span>:<span style="color:#7171bf">assert</span> value, <span style="color:#98c379">'%s中元素【%s】类型与值不匹配'</span> % (_path, k)<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:inspect_element()
</code></span></span>

执行该文件:

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">校验元素done!用时<span style="color:#d19a66">0.002</span>秒!
</code></span></span>

可以看到,很短的时间内,我们就对所填写的YAML文件进行了审查。

现在我们基本所需要的组件已经大致完成了。

接下来我们将进行最重要的一环,封装selenium。


封装Selenium基类

在工厂模式种我们是这样写的:

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> time
<span style="color:#7171bf">from</span> selenium <span style="color:#7171bf">import</span> webdriverdriver = webdriver.Chrome()
driver.get(<span style="color:#98c379">'https://www.baidu.com'</span>)
driver.find_element_by_xpath(<span style="color:#98c379">"//input[@id='kw']"</span>).send_keys(<span style="color:#98c379">'selenium'</span>)
driver.find_element_by_xpath(<span style="color:#98c379">"//input[@id='su']"</span>).click()
time.sleep(<span style="color:#d19a66">5</span>)
driver.quit()
</code></span></span>

很直白,简单,又明了。

创建driver对象,打开百度网页,搜索selenium,点击搜索,然后停留5秒,查看结果,最后关闭浏览器。

那我们为什么要封装selenium的方法呢。首先我们上述这种较为原始的方法,基本不适用于平时做UI自动化测试的,因为在UI界面实际运行情况远远比较复杂,可能因为网络原因,或者控件原因,我们元素还没有显示出来,就进行点击或者输入。所以我们需要封装selenium方法,通过内置的显式等待或一定的条件语句,才能构建一个稳定的方法。而且把selenium方法封装起来,有利于平时的代码维护。

我们在page目录创建webpage.py文件。
 

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#98c379">"""
selenium基类
本文件存放了selenium基类的封装方法
"""</span>
<span style="color:#7171bf">from</span> selenium.webdriver.support <span style="color:#7171bf">import</span> expected_conditions <span style="color:#7171bf">as</span> EC
<span style="color:#7171bf">from</span> selenium.webdriver.support.ui <span style="color:#7171bf">import</span> WebDriverWait
<span style="color:#7171bf">from</span> selenium.common.exceptions <span style="color:#7171bf">import</span> TimeoutException<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
<span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> sleep
<span style="color:#7171bf">from</span> utils.logger <span style="color:#7171bf">import</span> log<span style="color:#7171bf">class</span> <span style="color:#61aeee">WebPage</span>(<span style="color:#61aeee">object</span>):<span style="color:#98c379">"""selenium基类"""</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self, driver):<span style="color:#5c6370"><em># self.driver = webdriver.Chrome()</em></span>self.driver = driverself.timeout = <span style="color:#d19a66">20</span>self.wait = WebDriverWait(self.driver, self.timeout)<span style="color:#7171bf">def</span> <span style="color:#61aeee">get_url</span>(self, url):<span style="color:#98c379">"""打开网址并验证"""</span>self.driver.maximize_window()self.driver.set_page_load_timeout(<span style="color:#d19a66">60</span>)<span style="color:#7171bf">try</span>:self.driver.get(url)self.driver.implicitly_wait(<span style="color:#d19a66">10</span>)log.info(<span style="color:#98c379">"打开网页:%s"</span> % url)<span style="color:#7171bf">except</span> TimeoutException:<span style="color:#7171bf">raise</span> TimeoutException(<span style="color:#98c379">"打开%s超时请检查网络或网址服务器"</span> % url)<span style="color:#61aeee">    @staticmethod</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">element_locator</span>(func, locator):<span style="color:#98c379">"""元素定位器"""</span>name, value = locator<span style="color:#7171bf">return</span> func(cm.LOCATE_MODE[name], value)<span style="color:#7171bf">def</span> <span style="color:#61aeee">find_element</span>(self, locator):<span style="color:#98c379">"""寻找单个元素"""</span><span style="color:#7171bf">return</span> WebPage.element_locator(<span style="color:#7171bf">lambda</span> *args: self.wait.until(EC.presence_of_element_located(args)), locator)<span style="color:#7171bf">def</span> <span style="color:#61aeee">find_elements</span>(self, locator):<span style="color:#98c379">"""查找多个相同的元素"""</span><span style="color:#7171bf">return</span> WebPage.element_locator(<span style="color:#7171bf">lambda</span> *args: self.wait.until(EC.presence_of_all_elements_located(args)), locator)<span style="color:#7171bf">def</span> <span style="color:#61aeee">elements_num</span>(self, locator):<span style="color:#98c379">"""获取相同元素的个数"""</span>number = <span style="color:#7171bf">len</span>(self.find_elements(locator))log.info(<span style="color:#98c379">"相同元素:{}"</span>.<span style="color:#7171bf">format</span>((locator, number)))<span style="color:#7171bf">return</span> number<span style="color:#7171bf">def</span> <span style="color:#61aeee">input_text</span>(self, locator, txt):<span style="color:#98c379">"""输入(输入前先清空)"""</span>sleep(<span style="color:#d19a66">0.5</span>)ele = self.find_element(locator)ele.clear()ele.send_keys(txt)log.info(<span style="color:#98c379">"输入文本:{}"</span>.<span style="color:#7171bf">format</span>(txt))<span style="color:#7171bf">def</span> <span style="color:#61aeee">is_click</span>(self, locator):<span style="color:#98c379">"""点击"""</span>self.find_element(locator).click()sleep()log.info(<span style="color:#98c379">"点击元素:{}"</span>.<span style="color:#7171bf">format</span>(locator))<span style="color:#7171bf">def</span> <span style="color:#61aeee">element_text</span>(self, locator):<span style="color:#98c379">"""获取当前的text"""</span>_text = self.find_element(locator).textlog.info(<span style="color:#98c379">"获取文本:{}"</span>.<span style="color:#7171bf">format</span>(_text))<span style="color:#7171bf">return</span> _text<span style="color:#61aeee">    @property</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">get_source</span>(self):<span style="color:#98c379">"""获取页面源代码"""</span><span style="color:#7171bf">return</span> self.driver.page_source<span style="color:#7171bf">def</span> <span style="color:#61aeee">refresh</span>(self):<span style="color:#98c379">"""刷新页面F5"""</span>self.driver.refresh()self.driver.implicitly_wait(<span style="color:#d19a66">30</span>)
</code></span></span>

在文件中我们对主要用了显式等待对selenium的click,send_keys等方法,做了二次封装。提高了运行的成功率。

好了我们完成了POM模型的一半左右的内容。接下来我们们进入页面对象。


创建页面对象

page_object目录下创建一个searchpage.py文件。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">from</span> page.webpage <span style="color:#7171bf">import</span> WebPage, sleep
<span style="color:#7171bf">from</span> common.readelement <span style="color:#7171bf">import</span> Elementsearch = Element(<span style="color:#98c379">'search'</span>)<span style="color:#7171bf">class</span> <span style="color:#61aeee">SearchPage</span>(<span style="color:#61aeee">WebPage</span>):<span style="color:#98c379">"""搜索类"""</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">input_search</span>(self, content):<span style="color:#98c379">"""输入搜索"""</span>self.input_text(search[<span style="color:#98c379">'搜索框'</span>], txt=content)sleep()<span style="color:#61aeee">    @property</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">imagine</span>(self):<span style="color:#98c379">"""搜索联想"""</span><span style="color:#7171bf">return</span> [x.text <span style="color:#7171bf">for</span> x <span style="color:#7171bf">in</span> self.find_elements(search[<span style="color:#98c379">'候选'</span>])]<span style="color:#7171bf">def</span> <span style="color:#61aeee">click_search</span>(self):<span style="color:#98c379">"""点击搜索"""</span>self.is_click(search[<span style="color:#98c379">'搜索按钮'</span>])
</code></span></span>

在该文件中我们对,输入搜索关键词,点击搜索,搜索联想,进行了封装。

并配置了注释。

在平时中我们应该养成写注释的习惯,因为过一段时间后,没有注释,代码读起来很费劲。

好了我们的页面对象此时业已完成了。下面我们开始编写测试用例。在开始测试用了之前我们先熟悉一下pytest测试框架。


简单了解Pytest

打开pytest框架的官网。pytest: helps you write better programs — pytest documentation

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em># content of test_sample.py</em></span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">inc</span>(x):<span style="color:#7171bf">return</span> x + <span style="color:#d19a66">1</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">test_answer</span>():<span style="color:#7171bf">assert</span> inc(<span style="color:#d19a66">3</span>) == <span style="color:#d19a66">5</span>
</code></span></span>

pytest.ini

pytest项目中的配置文件,可以对pytest执行过程中操作做全局控制。

在项目根目录新建pytest.ini文件。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-ini"><span style="color:#e06c75">[pytest]</span>
<span style="color:#d19a66">addopts</span> = --html=report.html --self-contained-html
</code></span></span>

addopts 指定执行时的其他参数说明:

  • --html=report/report.html --self-contained-html 生成pytest-html带样式的报告
  • -s 输出我们用例中的调式信息
    • -q 安静的进行测试
    • -v 可以输出用例更加详细的执行信息,比如用例所在的文件及用例名称等

编写测试用例

我们将使用pytest编写测试用例。

TestCase目录中创建test_search.py文件。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> re
<span style="color:#7171bf">import</span> pytest
<span style="color:#7171bf">from</span> utils.logger <span style="color:#7171bf">import</span> log
<span style="color:#7171bf">from</span> common.readconfig <span style="color:#7171bf">import</span> ini
<span style="color:#7171bf">from</span> page_object.searchpage <span style="color:#7171bf">import</span> SearchPage<span style="color:#7171bf">class</span> <span style="color:#61aeee">TestSearch</span>:
<span style="color:#61aeee">    @pytest.fixture(scope=<span style="color:#3388aa">'function'</span>, autouse=<span style="color:#56b6c2">True</span>)</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">open_baidu</span>(self, drivers):<span style="color:#98c379">"""打开百度"""</span>search = SearchPage(drivers)search.get_url(ini.url)<span style="color:#7171bf">def</span> <span style="color:#61aeee">test_001</span>(self, drivers):<span style="color:#98c379">"""搜索"""</span>search = SearchPage(drivers)search.input_search(<span style="color:#98c379">"selenium"</span>)search.click_search()result = re.search(<span style="color:#98c379">r'selenium'</span>, search.get_source)log.info(result)<span style="color:#7171bf">assert</span> result<span style="color:#7171bf">def</span> <span style="color:#61aeee">test_002</span>(self, drivers):<span style="color:#98c379">"""测试搜索候选"""</span>search = SearchPage(drivers)search.input_search(<span style="color:#98c379">"selenium"</span>)log.info(<span style="color:#7171bf">list</span>(search.imagine))<span style="color:#7171bf">assert</span> <span style="color:#7171bf">all</span>([<span style="color:#98c379">"selenium"</span> <span style="color:#7171bf">in</span> i <span style="color:#7171bf">for</span> i <span style="color:#7171bf">in</span> search.imagine])<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:pytest.main([<span style="color:#98c379">'TestCase/test_search.py'</span>])</code></span></span>

我们测试用了就编写好了。

  • pytest.fixture 这个实现了和unittest的setup,teardown一样的前置启动,后置清理的装饰器。

  • 第一个测试用例:

    • 我们实现了在百度selenium关键字,并点击搜索按钮,并在搜索结果中,用正则查找结果页源代码,返回数量大于10我们就认为通过。
  • 第二个测试用例:

    • 我们实现了,搜索selenium,然后断言搜索候选中的所有结果有没有selenium关键字。

最后我们的在下面写一个执行启动的语句。

这时候我们应该进入执行了,但是还有一个问题,我们还没有把driver传递。

conftest.py

我们在项目根目录下新建一个conftest.py文件。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> pytest
<span style="color:#7171bf">from</span> py.xml <span style="color:#7171bf">import</span> html
<span style="color:#7171bf">from</span> selenium <span style="color:#7171bf">import</span> webdriverdriver = <span style="color:#56b6c2">None</span><span style="color:#61aeee">@pytest.fixture(scope=<span style="color:#3388aa">'session'</span>, autouse=<span style="color:#56b6c2">True</span>)</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">drivers</span>(request):<span style="color:#7171bf">global</span> driver<span style="color:#7171bf">if</span> driver <span style="color:#7171bf">is</span> <span style="color:#56b6c2">None</span>:driver = webdriver.Chrome()driver.maximize_window()<span style="color:#7171bf">def</span> <span style="color:#61aeee">fn</span>():driver.quit()request.addfinalizer(fn)<span style="color:#7171bf">return</span> driver<span style="color:#61aeee">@pytest.hookimpl(hookwrapper=<span style="color:#56b6c2">True</span>)</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_runtest_makereport</span>(item):<span style="color:#98c379">"""当测试失败的时候,自动截图,展示到html报告中:param item:"""</span>pytest_html = item.config.pluginmanager.getplugin(<span style="color:#98c379">'html'</span>)outcome = <span style="color:#7171bf">yield</span>report = outcome.get_result()report.description = <span style="color:#7171bf">str</span>(item.function.__doc__)extra = <span style="color:#7171bf">getattr</span>(report, <span style="color:#98c379">'extra'</span>, [])<span style="color:#7171bf">if</span> report.when == <span style="color:#98c379">'call'</span> <span style="color:#7171bf">or</span> report.when == <span style="color:#98c379">"setup"</span>:xfail = <span style="color:#7171bf">hasattr</span>(report, <span style="color:#98c379">'wasxfail'</span>)<span style="color:#7171bf">if</span> (report.skipped <span style="color:#7171bf">and</span> xfail) <span style="color:#7171bf">or</span> (report.failed <span style="color:#7171bf">and</span> <span style="color:#7171bf">not</span> xfail):file_name = report.nodeid.replace(<span style="color:#98c379">"::"</span>, <span style="color:#98c379">"_"</span>) + <span style="color:#98c379">".png"</span>screen_img = _capture_screenshot()<span style="color:#7171bf">if</span> file_name:html = <span style="color:#98c379">'<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" '</span> \<span style="color:#98c379">'onclick="window.open(this.src)" align="right"/></div>'</span> % screen_imgextra.append(pytest_html.extras.html(html))report.extra = extra<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_header</span>(cells):cells.insert(<span style="color:#d19a66">1</span>, html.th(<span style="color:#98c379">'用例名称'</span>))cells.insert(<span style="color:#d19a66">2</span>, html.th(<span style="color:#98c379">'Test_nodeid'</span>))cells.pop(<span style="color:#d19a66">2</span>)<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_row</span>(report, cells):cells.insert(<span style="color:#d19a66">1</span>, html.td(report.description))cells.insert(<span style="color:#d19a66">2</span>, html.td(report.nodeid))cells.pop(<span style="color:#d19a66">2</span>)<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_html</span>(report, data):<span style="color:#7171bf">if</span> report.passed:<span style="color:#7171bf">del</span> data[:]data.append(html.div(<span style="color:#98c379">'通过的用例未捕获日志输出.'</span>, class_=<span style="color:#98c379">'empty log'</span>))<span style="color:#7171bf">def</span> <span style="color:#61aeee">_capture_screenshot</span>():<span style="color:#98c379">'''截图保存为base64:return:'''</span><span style="color:#7171bf">return</span> driver.get_screenshot_as_base64()</code></span></span>

conftest.py测试框架pytest的胶水文件,里面用到了fixture的方法,封装并传递出了driver。


执行用例

以上我们已经编写完成了整个框架和测试用例。

我们进入到当前项目的主目录执行命令:

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">pytest
</code></span></span>

命令行输出:

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">Test session starts (platform: win32, Python <span style="color:#d19a66">3.7</span>.<span style="color:#d19a66">7</span>, pytest <span style="color:#d19a66">5.3</span>.<span style="color:#d19a66">2</span>, py<span style="color:#7171bf">test-sugar</span> <span style="color:#d19a66">0.9</span>.<span style="color:#d19a66">2</span>)
cachedir: .pytest_cache
metadata: {<span style="color:#98c379">'Python'</span>: <span style="color:#98c379">'3.7.7'</span>, <span style="color:#98c379">'Platform'</span>: <span style="color:#98c379">'Windows-10-10.0.18362-SP0'</span>, <span style="color:#98c379">'Packages'</span>: {<span style="color:#98c379">'pytest'</span>: <span style="color:#98c379">'5.3.2'</span>, <span style="color:#98c379">'py'</span>: <span style="color:#98c379">'1.8.0'</span>, <span style="color:#98c379">'pluggy'</span>: <span style="color:#98c379">'0.13.1'</span>}, <span style="color:#98c379">'Plugins'</span>: {<span style="color:#98c379">'forked'</span>: <span style="color:#98c379">'1.1.3'</span>, <span style="color:#98c379">'html'</span>: <span style="color:#98c379">'2.0.1'</span>, <span style="color:#98c379">'metadata'</span>: <span style="color:#98c379">'1.8.0'</span>, <span style="color:#98c379">'ordering'</span>: <span style="color:#98c379">'0.6'</span>, <span style="color:#98c379">'rerunfailures'</span>: <span style="color:#98c379">'8.0'</span>, <span style="color:#98c379">'sugar'</span>: <span style="color:#98c379">'0.9.2'</span>, <span style="color:#98c379">'xdist'</span>: <span style="color:#98c379">'1.31.0'</span>}, <span style="color:#98c379">'JAVA_HOME'</span>: <span style="color:#98c379">'D:\\Program Files\\Java\\jdk1.8.0_131'</span>}
rootdir: C:\Users\hoou\PycharmProjects\web<span style="color:#56b6c2">-demotest</span>, inifile: pytest.ini
plugins: forked<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">1.3</span>, html<span style="color:#56b6c2">-2</span>.<span style="color:#d19a66">0.1</span>, metadata<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">8.0</span>, ordering<span style="color:#56b6c2">-0</span>.<span style="color:#d19a66">6</span>, rerunfailures<span style="color:#56b6c2">-8</span>.<span style="color:#d19a66">0</span>, sugar<span style="color:#56b6c2">-0</span>.<span style="color:#d19a66">9.2</span>, xdist<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">31.0</span>
collecting ... 
DevTools listening on ws://<span style="color:#d19a66">127.0</span>.<span style="color:#d19a66">0.1</span>:<span style="color:#d19a66">10351</span>/devtools/browser/<span style="color:#d19a66">78</span>bef34d<span style="color:#56b6c2">-b94c-4087-b724-34fb6b2ef6d1</span>TestCase\test_search.py::TestSearch.test_001 ✓                                                                                              <span style="color:#d19a66">50</span>% █████     TestCase\test_search.py::TestSearch.test_002 ✓                                                                                             <span style="color:#d19a66">100</span>% ██████████
<span style="color:#56b6c2">-------------------------------</span> generated html file: file://C:\Users\hoou\PycharmProjects\web<span style="color:#56b6c2">-demotest</span>\report\report.html <span style="color:#56b6c2">--------------------------------</span> Results (<span style="color:#d19a66">12.90</span>s):<span style="color:#d19a66">2</span> passed
</code></span></span>

可以看到两条用例已经执行成功了。

项目的report目录中生成了一个report.html文件。

这就是生成的测试报告文件。


发送邮件

当项目执行完成之后,需要发送到自己或者其他人邮箱里查看结果。

我们编写发送邮件的模块。

utils目录中新建send_mail.py文件

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> zmail
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm<span style="color:#7171bf">def</span> <span style="color:#61aeee">send_report</span>():<span style="color:#98c379">"""发送报告"""</span><span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(cm.REPORT_FILE, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:content_html = f.read()<span style="color:#7171bf">try</span>:mail = {<span style="color:#98c379">'from'</span>: <span style="color:#98c379">'1084502012@qq.com'</span>,<span style="color:#98c379">'subject'</span>: <span style="color:#98c379">'最新的测试报告邮件'</span>,<span style="color:#98c379">'content_html'</span>: content_html,<span style="color:#98c379">'attachments'</span>: [cm.REPORT_FILE, ]}server = zmail.server(*cm.EMAIL_INFO.values())server.send_mail(cm.ADDRESSEE, mail)<span style="color:#7171bf">print</span>(<span style="color:#98c379">"测试邮件发送成功!"</span>)<span style="color:#7171bf">except</span> Exception <span style="color:#7171bf">as</span> e:<span style="color:#7171bf">print</span>(<span style="color:#98c379">"Error: 无法发送邮件,{}!"</span>, <span style="color:#7171bf">format</span>(e))<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">"__main__"</span>:<span style="color:#98c379">'''请先在config/conf.py文件设置QQ邮箱的账号和密码'''</span>send_report()
</code></span></span>

执行该文件:

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-shell">测试邮件发送成功!
</code></span></span>

可以看到测试报告邮件已经发送成功了。打开邮箱。

 

成功收到了邮件。

这个demo项目就算是整体完工了;是不是很有心得,在发送邮件的那一刻很有成就感。

最后,想必你已经对pytest+selenium框架有了一个整体的认知了,在自动化测试的道路上又上了一层台阶。


pytest使用allure测试报告

  • 选用的项目为Selenium自动化测试Pytest框架实战,在这个项目的基础上说allure报告。

allure安装

  • 首先安装python的allure-pytest包
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-shell">pip install allure-pytest
</code></span></span>
  • 然后安装allure的command命令行程序

MacOS直接使用homebrew工具执行 brew install allure 即可安装,不用配置下载包和配置环境

在GitHub下载安装程序https://github.com/allure-framework/allure2/releases

但是由于GitHub访问太慢,我已经下载好并放在了群文件里面

下载完成后解压放到一个文件夹。我的路径是D:\Program Files\allure-2.13.3

然后配置环境变量: 在系统变量path中添加D:\Program Files\allure-2.13.3\bin,然后确定保存。

打开cmd,输入allure,如果结果显示如下则表示成功了:

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">C:\Users\hoou>allure
Usage: allure [<span style="color:#d19a66">options</span>] [<span style="color:#d19a66">command</span>] [<span style="color:#d19a66">command</span> <span style="color:#d19a66">options</span>]Options:<span style="color:#56b6c2">--help</span>Print commandline help.<span style="color:#56b6c2">-q</span>, <span style="color:#56b6c2">--quiet</span><span style="color:#7171bf">Switch</span> on the quiet mode.Default: false<span style="color:#56b6c2">-v</span>, <span style="color:#56b6c2">--verbose</span><span style="color:#7171bf">Switch</span> on the verbose mode.Default: false<span style="color:#56b6c2">--version</span>Print commandline version.Default: falseCommands:generate      Generate the reportUsage: generate [<span style="color:#d19a66">options</span>] The directories with allure resultsOptions:<span style="color:#56b6c2">-c</span>, <span style="color:#56b6c2">--clean</span>Clean Allure report directory before generating a new one.Default: false<span style="color:#56b6c2">--config</span>Allure commandline config path. <span style="color:#7171bf">If</span> specified overrides values from<span style="color:#56b6c2">--profile</span> and <span style="color:#56b6c2">--configDirectory</span>.<span style="color:#56b6c2">--configDirectory</span>Allure commandline configurations directory. By default usesALLURE_HOME directory.<span style="color:#56b6c2">--profile</span>Allure commandline configuration profile.<span style="color:#56b6c2">-o</span>, <span style="color:#56b6c2">--report-dir</span>, <span style="color:#56b6c2">--output</span>The directory to generate Allure report into.Default: allure<span style="color:#56b6c2">-report</span>serve      Serve the reportUsage: serve [<span style="color:#d19a66">options</span>] The directories with allure resultsOptions:<span style="color:#56b6c2">--config</span>Allure commandline config path. <span style="color:#7171bf">If</span> specified overrides values from<span style="color:#56b6c2">--profile</span> and <span style="color:#56b6c2">--configDirectory</span>.<span style="color:#56b6c2">--configDirectory</span>Allure commandline configurations directory. By default usesALLURE_HOME directory.<span style="color:#56b6c2">-h</span>, <span style="color:#56b6c2">--host</span>This host will be used to <span style="color:#7171bf">start</span> web server <span style="color:#7171bf">for</span> the report.<span style="color:#56b6c2">-p</span>, <span style="color:#56b6c2">--port</span>This port will be used to <span style="color:#7171bf">start</span> web server <span style="color:#7171bf">for</span> the report.Default: <span style="color:#d19a66">0</span><span style="color:#56b6c2">--profile</span>Allure commandline configuration profile.open      Open generated reportUsage: open [<span style="color:#d19a66">options</span>] The report directoryOptions:<span style="color:#56b6c2">-h</span>, <span style="color:#56b6c2">--host</span>This host will be used to <span style="color:#7171bf">start</span> web server <span style="color:#7171bf">for</span> the report.<span style="color:#56b6c2">-p</span>, <span style="color:#56b6c2">--port</span>This port will be used to <span style="color:#7171bf">start</span> web server <span style="color:#7171bf">for</span> the report.Default: <span style="color:#d19a66">0</span>plugin      Generate the reportUsage: plugin [<span style="color:#d19a66">options</span>]Options:<span style="color:#56b6c2">--config</span>Allure commandline config path. <span style="color:#7171bf">If</span> specified overrides values from<span style="color:#56b6c2">--profile</span> and <span style="color:#56b6c2">--configDirectory</span>.<span style="color:#56b6c2">--configDirectory</span>Allure commandline configurations directory. By default usesALLURE_HOME directory.<span style="color:#56b6c2">--profile</span>Allure commandline configuration profile.
</code></span></span>

allure初体验

改造一下之前的测试用例代码

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> re
<span style="color:#7171bf">import</span> pytest
<span style="color:#7171bf">import</span> allure
<span style="color:#7171bf">from</span> utils.logger <span style="color:#7171bf">import</span> log
<span style="color:#7171bf">from</span> common.readconfig <span style="color:#7171bf">import</span> ini
<span style="color:#7171bf">from</span> page_object.searchpage <span style="color:#7171bf">import</span> SearchPage<span style="color:#61aeee">@allure.feature(<span style="color:#3388aa">"测试百度模块"</span>)</span>
<span style="color:#7171bf">class</span> <span style="color:#61aeee">TestSearch</span>:
<span style="color:#61aeee">    @pytest.fixture(scope=<span style="color:#3388aa">'function'</span>, autouse=<span style="color:#56b6c2">True</span>)</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">open_baidu</span>(self, drivers):<span style="color:#98c379">"""打开百度"""</span>search = SearchPage(drivers)search.get_url(ini.url)<span style="color:#61aeee">    @allure.story(<span style="color:#3388aa">"搜索selenium结果用例"</span>)</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">test_001</span>(self, drivers):<span style="color:#98c379">"""搜索"""</span>search = SearchPage(drivers)search.input_search(<span style="color:#98c379">"selenium"</span>)search.click_search()result = re.search(<span style="color:#98c379">r'selenium'</span>, search.get_source)log.info(result)<span style="color:#7171bf">assert</span> result<span style="color:#61aeee">    @allure.story(<span style="color:#3388aa">"测试搜索候选用例"</span>)</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">test_002</span>(self, drivers):<span style="color:#98c379">"""测试搜索候选"""</span>search = SearchPage(drivers)search.input_search(<span style="color:#98c379">"selenium"</span>)log.info(<span style="color:#7171bf">list</span>(search.imagine))<span style="color:#7171bf">assert</span> <span style="color:#7171bf">all</span>([<span style="color:#98c379">"selenium"</span> <span style="color:#7171bf">in</span> i <span style="color:#7171bf">for</span> i <span style="color:#7171bf">in</span> search.imagine])<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:pytest.main([<span style="color:#98c379">'TestCase/test_search.py'</span>, <span style="color:#98c379">'--alluredir'</span>, <span style="color:#98c379">'./allure'</span>])os.system(<span style="color:#98c379">'allure serve allure'</span>)
</code></span></span>

然后运行一下:

注意:如果你使用的是pycharm编辑器,请跳过该运行方式,直接使用.bat.sh的方式运行

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-shell">***------------------------------- generated html file: file://C:\Users\hoou\PycharmProjects\web-demotest\report.html -------------------------------- Results (12.97s):2 passed
Generating report to temp directory...
Report successfully generated to C:\Users\hoou\AppData\Local\Temp\112346119265936111\allure-report
Starting web server...
2020-06-18 22:52:44.500:INFO::main: Logging initialized @1958ms to org.eclipse.jetty.util.log.StdErrLog
Server started at <http://172.18.47.241:6202/>. Press <Ctrl+C> to exit
</code></span></span>

命令行会出现如上提示,接着浏览器会自动打开:

 点击左下角En即可选择切换为中文。

 

是不是很清爽很友好,比pytest-html更舒服。

allure装饰器介绍

 

报告的生成和展示

刚才的两个命令:

  • 生成allure原始报告到report/allure目录下,生成的全部为json或txt文件。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">pytest TestCase/test_search.py <span style="color:#56b6c2">--alluredir</span> ./allure
</code></span></span>
  • 在一个临时文件中生成报告并启动浏览器打开
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">allure serve allure
</code></span></span>

但是在关闭浏览器之后这个报告就再也打不开了。不建议使用这种。

所以我们必须使用其他的命令,让allure可以指定生成的报告目录。

我们在项目的根目录中创建run_case.py文件,内容如下:

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> sys
<span style="color:#7171bf">import</span> subprocessWIN = sys.platform.startswith(<span style="color:#98c379">'win'</span>)<span style="color:#7171bf">def</span> <span style="color:#61aeee">main</span>():<span style="color:#98c379">"""主函数"""</span>steps = [<span style="color:#98c379">"venv\\Script\\activate"</span> <span style="color:#7171bf">if</span> WIN <span style="color:#7171bf">else</span> <span style="color:#98c379">"source venv/bin/activate"</span>,<span style="color:#98c379">"pytest --alluredir allure-results --clean-alluredir"</span>,<span style="color:#98c379">"allure generate allure-results -c -o allure-report"</span>,<span style="color:#98c379">"allure open allure-report"</span>]<span style="color:#7171bf">for</span> step <span style="color:#7171bf">in</span> steps:subprocess.run(<span style="color:#98c379">"call "</span> + step <span style="color:#7171bf">if</span> WIN <span style="color:#7171bf">else</span> step, shell=<span style="color:#56b6c2">True</span>)<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">"__main__"</span>:main()
</code></span></span>

命令释义:

1、使用pytest生成原始报告,里面大多数是一些原始的json数据,加入--clean-alluredir参数清除allure-results历史数据。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">pytest <span style="color:#56b6c2">--alluredir</span> allure<span style="color:#56b6c2">-results</span> <span style="color:#56b6c2">--clean-alluredir</span>
</code></span></span>
  • --clean-alluredir 清除allure-results历史数据

2、使用generate命令导出HTML报告到新的目录

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">allure generate allure<span style="color:#56b6c2">-results</span> <span style="color:#56b6c2">-o</span> allure<span style="color:#56b6c2">-report</span>
</code></span></span>

3、使用open命令在浏览器中打开HTML报告

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">allure open allure<span style="color:#56b6c2">-report</span>
</code></span></span>

好了我们运行一下该文件。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">Results (<span style="color:#d19a66">12.85</span>s):<span style="color:#d19a66">2</span> passed
Report successfully generated to c:\Users\hoou\PycharmProjects\web<span style="color:#56b6c2">-demotest</span>\allure<span style="color:#56b6c2">-report</span>
Starting web server...
<span style="color:#d19a66">2020</span><span style="color:#56b6c2">-06-18</span> <span style="color:#d19a66">23</span>:<span style="color:#d19a66">30</span>:<span style="color:#d19a66">24.122</span>:INFO::main: Logging initialized @<span style="color:#d19a66">260</span>ms to org.eclipse.jetty.util.log.StdErrLog
Server started at <http://<span style="color:#d19a66">172.18</span>.<span style="color:#d19a66">47.241</span>:<span style="color:#d19a66">7932</span>/>. Press <Ctrl+C> to <span style="color:#7171bf">exit</span>
</code></span></span>

 

可以看到运行成功了。

在项目中的allure-report文件夹也生成了相应的报告。

 

allure发生错误截图

上面的用例全是运行成功的,没有错误和失败的,那么发生了错误怎么样在allure报告中生成错误截图呢,我们一起来看看。

首先我们先在config/conf.py文件中添加一个截图目录和截图文件的配置。

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python">+++<span style="color:#7171bf">class</span> <span style="color:#61aeee">ConfigManager</span>(<span style="color:#61aeee">object</span>):...<span style="color:#61aeee">    @property</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">screen_path</span>(self):<span style="color:#98c379">"""截图目录"""</span>screenshot_dir = os.path.join(self.BASE_DIR, <span style="color:#98c379">'screen_capture'</span>)<span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(screenshot_dir):os.makedirs(screenshot_dir)now_time = dt_strftime(<span style="color:#98c379">"%Y%m%d%H%M%S"</span>)screen_file = os.path.join(screenshot_dir, <span style="color:#98c379">"{}.png"</span>.<span style="color:#7171bf">format</span>(now_time))<span style="color:#7171bf">return</span> now_time, screen_file...
+++
</code></span></span>

然后我们修改项目目录中的conftest.py

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> base64
<span style="color:#7171bf">import</span> pytest
<span style="color:#7171bf">import</span> allure
<span style="color:#7171bf">from</span> py.xml <span style="color:#7171bf">import</span> html
<span style="color:#7171bf">from</span> selenium <span style="color:#7171bf">import</span> webdriver<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
<span style="color:#7171bf">from</span> common.readconfig <span style="color:#7171bf">import</span> ini
<span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> timestamp
<span style="color:#7171bf">from</span> utils.send_mail <span style="color:#7171bf">import</span> send_reportdriver = <span style="color:#56b6c2">None</span><span style="color:#61aeee">@pytest.fixture(scope=<span style="color:#3388aa">'session'</span>, autouse=<span style="color:#56b6c2">True</span>)</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">drivers</span>(request):<span style="color:#7171bf">global</span> driver<span style="color:#7171bf">if</span> driver <span style="color:#7171bf">is</span> <span style="color:#56b6c2">None</span>:driver = webdriver.Chrome()driver.maximize_window()<span style="color:#7171bf">def</span> <span style="color:#61aeee">fn</span>():driver.quit()request.addfinalizer(fn)<span style="color:#7171bf">return</span> driver<span style="color:#61aeee">@pytest.hookimpl(hookwrapper=<span style="color:#56b6c2">True</span>)</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_runtest_makereport</span>(item):<span style="color:#98c379">"""当测试失败的时候,自动截图,展示到html报告中:param item:"""</span>pytest_html = item.config.pluginmanager.getplugin(<span style="color:#98c379">'html'</span>)outcome = <span style="color:#7171bf">yield</span>report = outcome.get_result()report.description = <span style="color:#7171bf">str</span>(item.function.__doc__)extra = <span style="color:#7171bf">getattr</span>(report, <span style="color:#98c379">'extra'</span>, [])<span style="color:#7171bf">if</span> report.when == <span style="color:#98c379">'call'</span> <span style="color:#7171bf">or</span> report.when == <span style="color:#98c379">"setup"</span>:xfail = <span style="color:#7171bf">hasattr</span>(report, <span style="color:#98c379">'wasxfail'</span>)<span style="color:#7171bf">if</span> (report.skipped <span style="color:#7171bf">and</span> xfail) <span style="color:#7171bf">or</span> (report.failed <span style="color:#7171bf">and</span> <span style="color:#7171bf">not</span> xfail):screen_img = _capture_screenshot()<span style="color:#7171bf">if</span> screen_img:html = <span style="color:#98c379">'<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" '</span> \<span style="color:#98c379">'onclick="window.open(this.src)" align="right"/></div>'</span> % screen_imgextra.append(pytest_html.extras.html(html))report.extra = extra<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_header</span>(cells):cells.insert(<span style="color:#d19a66">1</span>, html.th(<span style="color:#98c379">'用例名称'</span>))cells.insert(<span style="color:#d19a66">2</span>, html.th(<span style="color:#98c379">'Test_nodeid'</span>))cells.pop(<span style="color:#d19a66">2</span>)<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_row</span>(report, cells):cells.insert(<span style="color:#d19a66">1</span>, html.td(report.description))cells.insert(<span style="color:#d19a66">2</span>, html.td(report.nodeid))cells.pop(<span style="color:#d19a66">2</span>)<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_html</span>(report, data):<span style="color:#7171bf">if</span> report.passed:<span style="color:#7171bf">del</span> data[:]data.append(html.div(<span style="color:#98c379">'通过的用例未捕获日志输出.'</span>, class_=<span style="color:#98c379">'empty log'</span>))<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_report_title</span>(report):report.title = <span style="color:#98c379">"pytest示例项目测试报告"</span><span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_configure</span>(config):config._metadata.clear()config._metadata[<span style="color:#98c379">'测试项目'</span>] = <span style="color:#98c379">"测试百度官网搜索"</span>config._metadata[<span style="color:#98c379">'测试地址'</span>] = ini.url<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_summary</span>(prefix, summary, postfix):<span style="color:#5c6370"><em># prefix.clear() # 清空summary中的内容</em></span>prefix.extend([html.p(<span style="color:#98c379">"所属部门: XX公司测试部"</span>)])prefix.extend([html.p(<span style="color:#98c379">"测试执行人: 随风挥手"</span>)])<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_terminal_summary</span>(terminalreporter, exitstatus, config):<span style="color:#98c379">"""收集测试结果"""</span>result = {<span style="color:#98c379">"total"</span>: terminalreporter._numcollected,<span style="color:#98c379">'passed'</span>: <span style="color:#7171bf">len</span>(terminalreporter.stats.get(<span style="color:#98c379">'passed'</span>, [])),<span style="color:#98c379">'failed'</span>: <span style="color:#7171bf">len</span>(terminalreporter.stats.get(<span style="color:#98c379">'failed'</span>, [])),<span style="color:#98c379">'error'</span>: <span style="color:#7171bf">len</span>(terminalreporter.stats.get(<span style="color:#98c379">'error'</span>, [])),<span style="color:#98c379">'skipped'</span>: <span style="color:#7171bf">len</span>(terminalreporter.stats.get(<span style="color:#98c379">'skipped'</span>, [])),<span style="color:#5c6370"><em># terminalreporter._sessionstarttime 会话开始时间</em></span><span style="color:#98c379">'total times'</span>: timestamp() - terminalreporter._sessionstarttime}<span style="color:#7171bf">print</span>(result)<span style="color:#7171bf">if</span> result[<span style="color:#98c379">'failed'</span>] <span style="color:#7171bf">or</span> result[<span style="color:#98c379">'error'</span>]:send_report()<span style="color:#7171bf">def</span> <span style="color:#61aeee">_capture_screenshot</span>():<span style="color:#98c379">"""截图保存为base64"""</span>now_time, screen_file = cm.screen_pathdriver.save_screenshot(screen_file)allure.attach.file(screen_file,<span style="color:#98c379">"失败截图{}"</span>.<span style="color:#7171bf">format</span>(now_time),allure.attachment_type.PNG)<span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(screen_file, <span style="color:#98c379">'rb'</span>) <span style="color:#7171bf">as</span> f:imagebase64 = base64.b64encode(f.read())<span style="color:#7171bf">return</span> imagebase64.decode()
</code></span></span>

来看看我们修改了什么:

  • 我们修改了_capture_screenshot函数

在里面我们使用了webdriver截图生成文件,并使用allure.attach.file方法将文件添加到了allure测试报告中。

并且我们还返回了图片的base64编码,这样可以让pytest-html的错误截图和allure都能生效。

运行一次得到两份报告,一份是简单的一份是好看内容丰富的。

现在我们在测试用例中构建一个预期的错误测试一个我们的这个代码。
 

修改test_002测试用例

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python">        <span style="color:#7171bf">assert</span> <span style="color:#7171bf">not</span> <span style="color:#7171bf">all</span>([<span style="color:#98c379">"selenium"</span> <span style="color:#7171bf">in</span> i <span style="color:#7171bf">for</span> i <span style="color:#7171bf">in</span> search.imagine])
</code></span></span>

运行一下:

可以看到allure报告中已经有了这个错误的信息。

再来看看pytest-html中生成的报告:

 

可以看到两份生成的报告都附带了错误的截图,真是鱼和熊掌可以兼得呢。

好了,到这里可以说allure的报告就先到这里了,以后发现allure其他的精彩之处我再来分享。


开源地址

为了方便学习交流,本次的示例项目已经保存在网盘了,直接点击下方小卡片免费获取。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/5514.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php使用PDO_sqlsrv

php拓展下载&#xff1a;Microsoft Drivers for PHP 发行说明 - PHP drivers for SQL Server | Microsoft Learn 参考文章&#xff1a;php7.3.4 pdo方式连接sqlserver 设置方法_pdo sqlserver_黑贝是条狗的博客-CSDN博客 php5.6.9安装sqlsrv扩展&#xff08;windows&#xff0…

CXL Bias Mode (1) - Bias Mode 背景与分类

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

SUSE宣布推出免费RHEL分叉以保留企业级Linux的选择权

导读在Red Hat宣布将限制AlmaLinuxOS或Rocky Linux等社区发行版对其公共仓库的访问后&#xff0c;最近Red Hat与IBM之间发生了一些争论&#xff0c;有鉴于此&#xff0c;SUSE今天宣布计划为RHEL和CentOS用户提供一个免费的替代方案。 SUSE已经开发了SUSE Linux Enterprise (SLE…

【问题记录】Ubuntu 22.04 环境下,打开 VS Code 老是访问密钥环该怎么解决?

目录 环境 问题情况 解决方法 环境 VMware Workstation 16 Pro &#xff08;版本&#xff1a;16.1.2 build-17966106&#xff09;ubuntu-22.04.2-desktop-amd64 问题情况 在Ubuntu下&#xff0c;每次运行 VS Code时&#xff0c;老是提示要输入密钥密码来解锁保存在密钥环&am…

C语言程序运行需要的两大环境《C语言进阶》

目录 程序的翻译环境和执行环境 翻译环境分为两部分&#xff0c;编译链接 第一步&#xff1a;预编译&#xff08;预处理&#xff09; 第二步&#xff0c;编译 第三步&#xff1a;汇编 关于运行环境分为四点&#xff1a; 关于链接库 程序的翻译环境和执行环境 在 ANSI C(标…

【全面解析】Windows 如何使用 SSH 密钥远程连接 Linux 服务器

创建密钥 创建 linux 服务器端的终端中执行命令 ssh-keygen&#xff0c;之后一直按Enter即可&#xff0c;这样会在将在 ~/.ssh/ 路径下生成公钥(id_rsa.pub)和私钥(id_rsa) 注意&#xff1a;也可以在 windows 端生成密钥&#xff0c;只需要保证公钥在服务器端&#xff0c;私钥…

Apache Struts2漏洞复现之s2-001漏洞复现

0x01 声明&#xff1a; 仅供学习参考使用&#xff0c;请勿用作违法用途&#xff0c;否则后果自负。 0x02 简介&#xff1a; Apache Struts 2是一个用于开发Java EE网络应用程序的开放源代码网页应用程序架构。它利用并延伸了Java ServletAPI&#xff0c;鼓励开发者采用MVC架构…

Android ObjectBox数据库的使用与详解

一、介绍 Room数据库 之前我已介绍了jetpack组件的数据库&#xff1a;Room&#xff0c;有小伙伴需要了解Room数据库可以查看这个地址&#xff1a;Android JetPack组件之Room数据库的集成与详解_android room数据库_蜗牛、Z的博客-CSDN博客 数据库的性能对设备来说很重要&#…

安全开发-JS应用原生开发JQuery库Ajax技术加密编码库断点调试逆向分析元素属性操作

文章目录 JS原生开发-文件上传-变量&对象&函数&事件JS导入库开发-登录验证-JQuery库&Ajax技术JS导入库开发-编码加密-逆向调试 JS原生开发-文件上传-变量&对象&函数&事件 1、布置前端页面 2、JS获取提交数据 3、JS对上传格式判断 <script>…

抖音seo开源源码,抖音优化系统定制方案

抖音作为目前最火热的短视频平台之一&#xff0c;其在移动互联网领域的影响越来越大。然而&#xff0c;一款成功的产品未必仅仅靠着其自身的功能和品质就能获得市场的认可&#xff0c;还需要通过优化SEO来实现更好的曝光率。下面&#xff0c;本文将介绍如何优化抖音SEO源码开发…

Vue3通透教程【十六】TS编译配置

文章目录 &#x1f31f; 写在前面&#x1f31f; 初始化配置文件⭐ target⭐ module⭐ lib⭐ types/node⭐ include⭐ outDir&#x1f31f; 写在最后 &#x1f31f; 写在前面 专栏介绍&#xff1a; 凉哥作为 Vue 的忠实 粉丝输出过大量的 Vue 文章&#xff0c;应粉丝要求开始更…

【英杰送书第三期】Spring 解决依赖版本不一致报错 | 文末送书

Yan-英杰的主 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 问题描述 报错信息如下 报错描述 解决方法 总结 【粉丝福利】 【文末送书】 目录&#xff1a; 本书特色&#xff1a; 问题描述 报错信息如下 Description:An attempt…

Data Transfer Object-DTO,数据传输对象,前端参数设计多个数据表对象

涉及两张表的两个实体对象 用于在业务逻辑层和持久层&#xff08;数据库访问层&#xff09;之间传输数据。 DTO的主要目的是将多个实体&#xff08;Entity&#xff09;的部分属性或多个实体关联属性封装成一个对象&#xff0c;以便在业务层进行数据传输和处理&#xff0c;从而…

Python 算法基础篇:冒泡排序和选择排序

Python 算法基础篇&#xff1a;冒泡排序和选择排序 引言 1. 冒泡排序算法概述2. 冒泡排序算法实现实例1&#xff1a;冒泡排序 3. 选择排序算法概述4. 选择排序算法实现实例2&#xff1a;选择排序 5. 冒泡排序与选择排序的对比总结 引言 冒泡排序和选择排序是两种常用的排序算法…

高校大数据教材推荐-《Python中文自然语言处理基础与实战》

《Python中文自然语言处理基础与实战》是“十四五”职业教育国家规划教材&#xff0c;是大数据应用开发&#xff08;Python&#xff09;“1X”职业技能等级证书配套系列教材。本书以项目为载体&#xff0c;突出职业技能。坚持理实一体化的理念。理实一体化&#xff0c;就是理论…

iOS--虚拟内存

参考文章 要想了解什么是VM Regions&#xff0c;就得先了解什么是虚拟内存。当我们向系统申请内存时&#xff0c;系统并不会给你返回物理内存的地址&#xff0c;而是给你一个虚拟内存地址。每个进程都拥有相同大小的虚拟地址空间&#xff0c;对于32位的进程&#xff0c;可以拥有…

面试 | 双法妙解压缩字符串【遍历统计 + 双指针】

一、题目描述 原题传送门 二、思路分析 首先我们来分析一下解决本题所需要的思路 题目的意思很简单&#xff0c;就是统计原本的字符串中的每个字符出现的次数&#xff0c;然后以【字符&#xff0c;出现的次数】这样的结构来字符串&#xff0c;以起到一个压缩的效果&#xff0c…

网络安全:密码学基本理论.

网络安全&#xff1a;密码学基本理论. 密码学是研究编制密码和破译密码的技术科学。研究密码变化的客观规律&#xff0c;应用于编制密码以保守通信秘密的&#xff0c;称为编码学&#xff1b;应用于破译密码以获取通信情报的&#xff0c;称为破译学&#xff0c;总称密码学. 目录…

ffmpeg离线安装ffmpeg-4.1.4-amd64-static.tar.xz

1.下载离线包 John Van Sickle - FFmpeg Static Builds 找历史版本&#xff1a;Index of /ffmpeg/old-releases 我选择是的4.1.4版本 2.解压 tar -xvJf ffmpeg-4.1.4-amd64-static.tar.xz 3.移动文件到opt目录下 4.添加全局链接 ln -s /opt/ffmpeg-4.1.4-amd64-static/ffm…

在macOS、Windows上使用VSCode + SSH实现远程Matplotlib图形显示

简介 在 macOS 上使用 VSCode SSH 环境来显示 Matplotlib 绘制的图形需要进行一些配置。因为默认情况下&#xff0c; Matplotlib 的图形是无法显示在远程计算机的桌面上的。您可以通过设置 Matplotlib 使用不同的后端(backend)来实现将图形显示在本地计算机上。 第一步&…