当前位置: 首页 > news >正文

带你一步步搭建Web自动化测试框架

测试框架的设计有两种思路,一种是自底向上,从脚本逐步演变完善成框架,这种适合新手了解框架的演变过程。另一种则是自顶向下,直接设计框架结构和选取各种问题的解决方案,这种适合有较多框架事件经验的人。本章和下一张分别从两种设计思路来介绍框架的搭建过程。

从脚本到用例

相比于一堆测试脚本,使用规范化的测试用例格式会方便我们灵活的执行和管理用例。一个完整的自动化测试用例应包含:

  • 测试准备(setup):测试准备步骤、用例辅助方法或工具,可以共用;

  • 测试步骤(test steps):核心测试步骤;

  • 断言(assertions):期望结果于实际结果的比对,用例可以报告不止一个断言;

  • 测试清理(teardown):对执行测试造成的影响进行清理和还原,以免影响后续执行,可以共用。

编写测试函数

将测试脚本转化为Pytest测试用例的方法非常简单,只要将测试过程编写为test开头的测试函数即可。

有时候我们为了快速实现一个功能,会直接把代码按步骤写到模块里,如下例:

代码test_baidu_search_v0.9.py内容

Copyfrom selenium import webdriver
from time import sleepdriver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.find_element_by_id('kw').send_keys('博客园 韩志超')
driver.find_element_by_id('su').click()
sleep(1)
if'韩志超'in driver.title:print('通过')
else:print('失败')
driver.quit()

然后我们开启第一步优化,首先,可以把步骤写到一个函数里,这样方便在脚步中写多个用例,另外,我们可以按照Pytest测试框架用例到写法,写成标准的用例。期望结果的判断我们使用标准的assert断言语句,修改后如下:

代码test_baidu_search_v1.0.py内容

Copyfrom selenium import webdriver
from time import sleepdeftest_baidu_search_01():driver = webdriver.Chrome()driver.get("https://www.baidu.com")driver.find_element_by_id('kw').send_keys('博客园 韩志超')driver.find_element_by_id('su').click()sleep(1)assert'韩志超'in driver.title, '标题不包含韩志超'# 自定义失败消息driver.quit()

不同于v0.9版Python脚本的运行方法(命令行使用python <脚步路径>),Pytest用例脚本使用pytest <脚本路径>或python -m pytest <脚本路径>来执行。

我们也可以在Pytest用例脚本下面加上以下语句,

Copyif __name__ == '__main__':pytest.main([__file__])

这样便可以像Python脚本一样直接运行。其中__file__指当前脚本,也可以添加其他运行参数,如-qs等。

使用断言

测试用例中必须包含期望结果来验证执行的通过与否。不同于“调试”,需要有人值守来人工判断没个执行过程是否通过,自动化“测试”往往需要批量运行,并自动判断用例是否通过。断言即是执行过程中的实际结果与期望结果的自动对比。

Pytest中使用标准的assert语句来进行断言。assert断言语句在用例执行失败时(和期望结果不一致)会抛出AssertionError异常,测试框架会自动捕获该异常,并将用例标记为执行失败状态,并且不会因为异常导致执行中断而影响其他用例的执行。

注:在用例中也可以使用if判断配合pytest.fail()或者手动抛出AsserionError异常来将用例设置为失败状态,示例如下:
Copyif'韩志超'notin driver.title:# rasie AssersionError('标题不包含韩志超')pytest.fail('标题不包含韩志超')

Web UI自动化测试过程中常用的断言策略有以下几种:

  • 流程成功执行视为通过:按确定的元素操作步骤,可以正常完成整个流程视为通过;

  • 通过标题断言:通过当前网页标题driver.title来判断处于某一页面上;

  • 通过URL断言:通过当前URL,driver.current_url来判断处于某一页面上;

  • 通过页面源码断言:通过网页源代码driver.page_source中包含特定信息来判断处于某一页面上;

  • 通过存在特定元素断言:通过存在某个特定元素来判断处于某一页面上。

通过元素判断是否在某一页面上的示例如下:

Copyfrom selenium import webdriver
from selenium.common.exceptions import NoSuchElementExceptiondeftest_open_baidu():driver = webdriver.Chrome()driver.get("https://www.baidu.com")try:driver.find_element_by_id('kw')  # 尝试定位搜索框except NoSuchElementException:pytest.fail('不存在搜索框')

在框架中,可以将常用的断言方法进行封装以方便使用。

分离测试准备及清理方法

在测试用例中,我们要尽可能的分离核心测试步骤,将可以共用的测试准备及测试清理步骤单独提取出来,以方便复用。

在上例中,我们核心的测试步骤是从打开百度网站到断言网页标题,而启动浏览器和关闭浏览器可以视为测试准备和测试清理方法。

测试准备和测试清理方法我们可以使用Pytest中的setup_function()及teardown_function()方法,也可以使用自定义的Fixture方法来吧两个方法集中的一个函数中,如下例:

代码test_baidu_search_v3.py内容

Copyfrom time import sleepfrom selenium import webdriver
import pytestdefsetup_function():global driverdriver = webdriver.Chrome()defteardown_function():driver.quit()deftest_baidu_search_01(driver):driver.get("https://www.baidu.com")driver.find_element_by_id('kw').send_keys('博客园 韩志超')driver.find_element_by_id('su').click()sleep(1)assert'韩志超'in driver.titleif __name__ == '__main__':pytest.main([__file__])

使用自定义Fixture方法

代码test_baidu_search_v4.py内容

Copyfrom time import sleepfrom selenium import webdriver
import pytest@pytest.fixturedefdriver():dr = webdriver.Chrome()yield drdr.quit()deftest_baidu_search_01(driver):driver.get("https://www.baidu.com")driver.find_element_by_id('kw').send_keys('博客园 韩志超')driver.find_element_by_id('su').click()sleep(1)assert'韩志超'in driver.titleif __name__ == '__main__':# --html需要pip install pytest-htmlpytest.main([__file__, '--html=report.html','--self-contained-html'])

上例中我们自定义了一个名为driver的Fixture方法。yield上面对的所有语句属于测试准,这里创建了一个浏览器驱动对象dr。yield语句将dr对象交给用例执行,并等待用例执行完毕,再执行下面的测试清理语句,退出浏览器。

用例中使用Fixture函数名driver作为参数来注入测试准备和测试清理方法,用例中使用的driver即Fixture函数yield返回的dr,浏览器驱动对象。

使用Pytest-selenium插件

Pytest框架的优点之一是,拥有很多功能丰富的插件。使用这些插件可以省略我们自己编写Fixture方法的过程,直接安装使用。

上例中我们自己编写了一个名为driver的fixture方法,我们也可以直接使用Pytest-Selenium插件,该插件提供了一个全局的driver(或selenium)Fixture方法,可以直接使用,并且支持切换使用的浏览器。安装Pytest-Selenium插件,并修改代码如下:

代码test_baidu_search_v5.py内容

Copyfrom time import sleepfrom selenium import webdriver
import pytestdeftest_baidu_search_01(driver):driver.get("https://www.baidu.com")driver.find_element_by_id('kw').send_keys('博客园 韩志超')driver.find_element_by_id('su').click()sleep(1)assert'韩志超'in driver.titleif __name__ == '__main__':# --html需要pip install pytest-html# --driver 需要pip install pytest-seleniumpytest.main([__file__, '--driver=chrome', '--html=report.html','--self-contained-html'])

pytest-selenium还支持配置浏览器选项及配合pytest-html失败自动截图等功能,详细可以参考其官方使用文档https://pytest-selenium.readthedocs.io/en/latest/。

注:pytest-selenium默认会拦截所有接口请求,可以在pytest.ini中配置sensitive_url = ''来设置无敏感url。

生成测试报告

使用Pytest框架生成测试报告最常用的插件有pytest-html和allure-pytest两种,前者简单,可以生成单文件测试报告。后者华丽,功能强大,使用较为复杂。本章我们使用pytest-html来生成报告,allure-pytest的具体使用下章讲解。

pytest-html的使用方式非常简单,安装pytest-html并使用--html来生成报告即可:

if name == 'main':

# --html需要pip install pytest-html

pytest.main([file, '--html=report.html','--self-contained-html'])

注:如果想自己生成HTML测试报告,可以在conftest.py文件中通过pytest_terminal_summary钩子方法terminalreporter参数对象的stats属性结合三方库Jinjia2来自定义生成报告。

增加易维护性

众所周知,UI的变动导致Web自动化用例的维护成本非常高,当一个元素变动时(如登录按钮),所有使用到这个元素的用例都将因此而失败,逐个修改每一条用例的成本是非常高的。

最好的做法就是使用模块封装的方式来隔离变动,隔离变动旨在隔离易变的和稳定的,常用的策略为:

  • 代码:隔离易变(如元素定位)和稳定的(页面操作),可以使用模块封装的方式对易变的操作进行封装;

  • 数据:变动较频繁,建议与代码隔离,以降低代码的修改;

  • 配置:配置也是数据的一种,主要用于增加框架使用的灵活性,配置变动也较频繁,讲义与代码隔离。

另外使用数据驱动、添加日志和失败自动截图也是快速定位问题、降低维护成本的有效方法。

元素失败自动截图

我们可以封装通用的定位元素方法来代替driver.find_element(),在其中捕获异常并截图。

并且为了方便区分元素,定位元素时为元素添加了一个高亮黄色的边框。实现方式如下:

Copyimport time
import os
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementExceptionSNAPSHOTS_DIR = 'snapshots'deffind_element(driver: webdriver.Chrome, by, value, timeout=5):style = 'background: green; border: 2px solid red;'js = 'arguments[0].setAttribute("style", arguments[1]);'try:WebDriverWait(driver, timeout).until(EC.presence_of_element_located((by,value)))except TimeoutException:snapshot_file = 'snapshot_%s.png' % int(time.time())driver.save_screenshot(os.path.join(SNAPSHOTS_DIR, snapshot_file))raise NoSuchElementException('%s 秒内未找到元素 %s=%s' % (timeout, by, value))else:element = driver.find_element(by, value)driver.execute_script(js, element, style) # 添加高亮样式return element

分层-封装测试步骤

我们可以使用分层的方式,将每个测试步骤,如打开百度、输入关键词、点击搜索按钮等,封装成函数以供用例调用。

我们可以每个元素操作封装一个函数,也可以封装一个包含这3步操作等搜索函数,来完成所有步骤。前一种方法虽然麻烦,但可以保证步骤操作的灵活性,并自由组合,如打开百度其他用例也可使用,如输入关键词后不点击搜索按钮等,示例代码如下:

代码test_baidu_search_v6.py内容

Copyfrom time import sleepfrom selenium import webdriver
import pytestdeffind_element(driver, by, value, timeout=5):...defopen_baidu(driver):print('打开百度')driver.get("https://www.baidu.com")definput_keyword(driver, keyword):print(f'输入关键字 {keyword}')find_element(driver, 'id', 'kw').send_keys(keyword)defclick_search_btn(driver):print('点击百度一下按钮')find_element(driver, 'id', 'su').click()deftest_baidu_search_01(driver):open_baidu(driver)  # Step 01input_keyword(driver, '博客园 韩志超')  # Step 02click_search_btn(driver)  # Step 03sleep(1)assert'韩志超'in driver.title  # 断言if __name__ == '__main__':# --html需要pip install pytest-htmlpytest.main([__file__, '--html=report.html','--self-contained-html'])

当我们将元素的操作进行封装,以实现只在一个地方定位和操作易变的元素。所有使用到该元素的该操作时(如输入关键词),都应该调用封装的函数,而不是直接定位函数完成操作。这样当元素变动是,只需要修复所封装的元素操作方法即可(用例不用修改)。这大大降低了维护成本。

分离测试数据

相对于代码来说,测试数据是易变的,同时不同悲催环境使用的测试数据集也应该不一样。

在数据量较少的情况下,我们可以用一个JSON或YAML文件来存储所需的测试数据。

文件data.json内容

Copy{"keywords":["博客园 韩志超","临渊","简书 韩志超"]}

文件data.yaml内容

Copykeywords:-博客园韩志超-临渊-简书韩志超

代码test_baidu_search_v7.py内容

Copyimport jsonimport yaml  # 需要pip install pyyaml安装
import pytestdef load_json(file_path):print(f'加载JSON文件{ file_path }')with open('data.json') as f:return json.load(f)def load_yaml(file_path):print(f'加载YAML文件{ file_path }')with open('data.json') as f:return yaml.safe_load(f)@pytest.fixture
def case_data():#  return load_json('demo.json')return load_yaml('demo.yaml')@pytest.fixture
def driver():dr = webdriver.Chrome()yield drdr.quit()def test_baidu_search_01(driver, case_data):keyword = case_data['keywords'][0]   # 从用例数据中选取指定数据driver.get("https://www.baidu.com")driver.find_element_by_id('kw').send_keys(keyword)driver.find_element_by_id('su').click()sleep(1)assert '韩志超' in driver.title
注:Fixtrue函数不建议用使用test_开头,如test_data定义fixture,以免识别为测试用例。

使用数据驱动

示例代码如下:

Copyimport pytestKEYWORD_LIST= load_yaml('demo.yaml')['keywords']@pytest.mark.paramitrize('keyword', KEYWORD_LIST)deftest_baidu_search_01(driver, keyword):  # keyword对应每一个要搜索的关键词driver.get("https://www.baidu.com")driver.find_element_by_id('kw').send_keys(keyword)driver.find_element_by_id('su').click()sleep(1)assert keyword in driver.title   # 有可能失败

使用日志

在项目中必要的输出信息可以帮助我们显示测试步骤的一些中间结果和快速的定位问题,虽然Pytest框架可以自动捕获print信息并输出屏幕或报告中,当时更规范的应使用logging的记录和输出日志。 相比print, logging模块可以分等级记录信息。

日志等级

实用方法层、页面对象层、Fixture业务层、用例层都可以直接使用logging来输出日志, 使用方法。

Copy# test_logging.pyimport loggingdeftest_logging():logging.debug('调试信息')logging.info('步骤信息')logging.warning('警告信息,一般可以继续进行')logging.error('出错信息')try:assert0except Exception as ex:logging.exception(ex)  # 多行异常追溯信息,Error级别logging.critical("严重出错信息")

使用pytest运行不会有任何的log信息,因为Pytest默认只在出错的信息中显示WARNING以上等级的日志。 要开启屏幕实时日志,并修改log显示等级。

Log等级: NOTSET < DEBUG < INFO < WARNING(=WARN) < ERROR < CRITICAL

Copy# pytest.ini[pytest]log_cli=Truelog_cli_level=INFO

运行pytest test_logging.py,查看结果:

Copy----------------------------- live log call -------------------------------
INFO     root:test_logging.py:5 步骤信息
WARNING  root:test_logging.py:6 警告信息,一般可以继续进行
ERROR    root:test_logging.py:7 出错信息
ERROR    root:test_logging.py:11 assert 0
Traceback (most recent call last):File "/Users/apple/Desktop/demo/test_logging.py", line 9, in test_loggingassert 0
AssertionError: assert 0
CRITICAL root:test_logging.py:12 严重出错信息

由于日志等级设置的为INFO级别,因此debug的日志不会输出。

对于不同层日志级别的使用规范,可以在实用方法层输出debug级别的日志,如组装的文件路径,文件读取的数据,执行的sql,sql查询结果等等。 在PageObject层输出info级别的日志,如执行某个页面的某项操作等。 Fixtures层和用例层可以根据需要输出一些必要的info,warning或error级别的信息。

日志格式

默认的日志格式没有显示执行时间,我们也可以自定义日志输出格式。

Copy# pytest.ini
...
log_cli_format=%(asctime)s %(levelname)s %(message)s
log_cli_date_format=%Y-%m-%d %H:%M:%S
%(asctime)s表示时间,默认为Sat Jan 13 21:56:34 2018这种格式,我们可以使用log_cli_date_format来指定时间格式。
%(levelname)s代表本条日志的级别
%(message)s为具体的输出信息

再次运行pytest test_logging.py,显示为以下格式:

Copy-------------------------------- live log call -------------------------------
2019-11-06 21:44:50 INFO 步骤信息
2019-11-06 21:44:50 WARNING 警告信息,一般可以继续进行
2019-11-06 21:44:50 ERROR 出错信息
2019-11-06 21:44:50 ERROR assert 0
Traceback (most recent call last):File "/Users/apple/Desktop/demo/test_logging.py", line 9, in test_loggingassert 0
AssertionError: assert 0
2019-11-06 21:44:50 CRITICAL 严重出错信息

更多日志显示选项

  • %(levelno)s: 打印日志级别的数值

  • %(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]

  • %(filename)s: 打印当前执行程序名

  • %(funcName)s: 打印日志的当前函数

  • %(lineno)d: 打印日志的当前行号

  • %(thread)d: 打印线程ID

  • %(threadName)s: 打印线程名称

  • %(process)d: 打印进程ID

输出日志到文件

在pytest.ini中添加以下配置

Copy...
log_file = logs/pytest.log
log_file_level = debug
log_file_format = %(asctime)s %(levelname)s %(message)s
log_file_date_format = %Y-%m-%d %H:%M:%S

log_file是输出的文件路径,输入到文件的日志等级、格式、日期格式要单独设置。 遗憾的是,输出到文件的日志每次运行覆盖一次,不支持追加模式。

用例依赖处理

一般来说,不建议用例之间存在顺序依赖。用例应该不依赖其他任何用例能够独立运行。加入确实存在步骤的先后顺序,如:

Copydeftest_add_customer():passdeftest_query_customer():passdeftest_delete_customer():pass

假设测试查询客户及测试删除用户需要先添加用户,常用的处理方法如下:

  • 使用步骤封装代替用例顺序依赖

将业务步骤单独封装,并在用例中进行调用,如:

Copydefadd_customer():passdefquery_customer():passdefdelete_customer():passdeftest_add_customer():add_customer()deftest_query_customer():add_customer()query_customer()deftest_delete_customer():add_customer()delete_customer()

虽然add_customer()方法会执行多次,但是每条用例都可以单独执行,比较推荐这种方式。

  • 使用例按顺序执行

如果想要强制用例有序可以使用插件pytest-ordering,使用pip安装后,使用方式如下:

Copy@pytest.mark.run(order=1)deftest_add_customer():pass@pytest.mark.run(order=2)deftest_query_customer():pass@pytest.mark.run(order=3)deftest_delete_customer():pass

增加灵活性

实现多环境切换

对于自动化测试框架来说,希望能一套用例来可以跑多套环境。不同环境的执行流程基本是一样的,不一样的是服务器地址(base_url)和所使用的数据。

我们可以使用pytest-base-url插件,配合pytest-variables插件来实现服务器地址和测试数据的切换。示例如下:

Copyfrom time import sleepfrom selenium import webdriver
import pytestdeftest_baidu_search_01(driver, base_url, variables):url = base_url + '/'keyword = variables['keywords'][0]driver.get(url)driver.find_element_by_id('kw').send_keys(keyword)driver.find_element_by_id('su').click()sleep(1)assert'韩志超'in driver.titleif __name__ == '__main__':# --html需要pip install pytest-html# --driver 需要pip install pytest-selenium# --base-url 需要pip install pytest-base-url# --variables 需要安装 pip install pytest-variablespytest.main([__file__, '--driver=chrome', '--html=report.html','--self-contained-html', '--base-url=https://www.baidu.com', '--variables=test.json'])

测试环境数据test.json内容如下:

Copy{"keywords":["博客园 韩志超","临渊","简书 韩志超"]}

由于pytest-selenium默认把所有url当作敏感url,我们需要在pytest.ini中通过配置进行关闭,即设置无敏感url。具体设置方法如下:

Copy[pytest]sensitive_url = None

用例标记

除了使用目录来按模块来整理用例外,我们也可以通过规范用例命令规则及自定义标签来组织用例。除@pytest.mark.skip、@pytest.mark.skipIf、@pytest.mark.xfail、@pytest.mark.paramitrize等系统标记外,我们可以自定义任何标记来使用,如使用smoke标记冒烟用例,使用destructive标记破坏性用例(有修改操作未还原的),使用abnormal标记异常用例,使用flaky标记不稳定用例,使用h5标记H5相关用例。

在严格模式下,可用标签需要在pytest.ini例出来,以防止随意使用任意标签导致的标签混乱问题。在pytest.ini文件注册标签如下:

Copy[pytest]markers =smoke: smoke test casedestructive: destructive test caseabnormal: abnormal test caseflaky: flaky test caseh5: h5 test casehzc: testcase by hzc
用例标记方式如下
```python
@pytest.mark.smoke
def test_baidu_search_01(driver, base_url, variables):...

用例可以添加多个标记,运行时可以使用pytest -m 命令挑选标签执行,如

Copypytest -m "smoke and h5"

即运行带例smoke和h5两个标签的用例,另外也支持or,not等多个标签的逻辑判断。

在规划标记是,也可以按维护人添加标记,以方便运行某人负责的所有用例。

用例等级

除了自定义用例标记外,我们可以对用例重要性进行评级,来快速回归不同优先级的用例。对用例进行标记等级,我们可以使用三方插件pytest-level。

安装方式

Copypip install pytest-level

标记用例

Copy@pytest.mark.smoke@pytest.mark.level(1)deftest_baidu_search_01(driver, base_url, variables):...

运行方式

Copypytest --level=1

用例顺序

在某些情况下我们如何希望用例有序,可以使用pytest-ordering插件实现。

安装方法

Copypip install pytest-ordering

标记用例

Copyimport pytest@pytest.mark.run(order=1)deftest_login():pass@pytest.mark.run(order=2)deftest_add_goods():pass@pytest.mark.run(order=3)deftest_query_goods():pass@pytest.mark.run(order=4)deftest_del_goods():pass

运行时用例便可按数字从小到大的顺序运行。

一般情况下,不建议用例之间有顺序依赖。每条用例应该可以独立执行的,有依赖的测试用例建议作为测试步骤放到一个大的场景用例中去,这样可以确保执行的有序,如:

Copydeflogin(username, password):passdefadd_goods(goods_name, *args):passdefquery_goods(goods_name):passdefdel_goods(goods_name):passdeftest_login():login('user', 'pwd')# ... 断言,结果判断deftest_add_goods():login('user', 'pwd')add_goods('...')# ... 断言,结果判断deftest_query_goods():login('user', 'pwd')add_goods('...')query_goods('...')# ... 断言,结果判断deftest_del_goods():login('user', 'pwd')add_goods('...')query_goods('...')del_goods('...')# ... 断言,结果判断

也可以写一个大的场景用例,包含4个测试点点验证:

这看起来有很多冗余,并且在一个用例中,如test_del_goods中,登录、添加商品、查询商品应该被视为是测试准备(setup),只保留核心的del_goods('...')作为测试步骤。

Copydeftest_login_add_query_del_goods():login('user', 'pwd')# ... 断言,结果判断add_goods('...')# ... 断言,结果判断query_goods('...')# ... 断言,结果判断del_goods('...')# ... 断言,结果判断

这样步骤永远是有序的,一个步骤失败,后续步骤将中断,不再执行。

另外,针对上面每个验证点分开的用例形式,我们可以使用Fixture模块化的特性,采用步骤渐进的方式来编写每一个带依赖的步骤,示例如下:

Copyimport pytest@pytest.fixturedeflogin():# fixture一般不使用普通参数,默认用户名密码需要确定并写在函数中useranme, password = 'user', 'pwd'# ... 业务逻辑@pytest.fixturedefadd_goods(login):  # 依赖login步骤pass@pytest.fixturedefquery_goods(add_goods):  # 依赖add_goods步骤passdeftest_login():   # 作为参数引用login步骤login('user', 'pwd')# ... 断言,结果判断deftest_add_goods(login):add_goods('...')# ... 断言,结果判断deftest_query_goods(add_goods):query_goods('...')# ... 断言,结果判断deftest_del_goods(query_goods):del_goods('...')# ... 断言,结果判断

这样做的好处是,任何一个用例都可以单独执行。缺点是一起执行时,登录、添加商品等会执行不止一遍。

不稳定用例处理

不稳定用例(flaky tests)是UI自动化测试过程中一个典型的问题。主要的策略有:

  • 暂时跳过用例,等环境或用例稳定后再运行

  • 为用例设置超时时间防止卡死

  • 用例失败后自动重试
    以下为3种策略的具体实现方式。

标记跳过用例

对于不稳定的用例,暂时跳过用例是最常用的方法之一。

在用例上使用@pytest.mark.skip()、@pytest.mark.skipIf()或在Fixture函数、测试用例中使用pytest.skip()方法即可跳过该用例。

使用超时时间

未避免用例卡死(长时间未结束),我们可以使用pytest-timeout为用例统一或分别添加超时时间。

安装方法:

Copypip install pytest-timeout

使用方法如下:

全局使用

Copypytest --timeout=300

配置方法:

Copy[pytest]timeout = 300

单独使用:

Copy@pytest.mark.timeout(60)deftest_foo():pass

用例失败重跑

对于不稳定用例,失败后立即重试可以应对一些环境或UI不稳定导致的一些用例失败的问题。我们可以很方便的借助pytest-rerunfailures这个插件来实现这个功能。

安装方法:

Copypip install pytest-rerunfailures

使用方法:

Copypytest -rerun 3 rerun-delay 1

即每次失败后,延迟1秒进行重试,最多重试3次,有一次成功,则视为成功。3次都失败则视为失败。

从面向过程到面向对象

按页面归类元素操作

Page Object模式即Page Object Module,页面对象模型模式,是一种基于模块的框架结构。以页面为对象来统一管理页面上元素的定位及操作,修改后示例如下:

代码test_baidu_search_v7内容

Copyfrom time import sleepfrom selenium import webdriver
import pytestclassBaiduHomePage:url = 'https://www.baidu.com'search_ipt_loc = ('id', 'kw')  # 百度搜索框search_btn_loc = ('id', 'su')  # 百度一下按钮def__init__(self, driver):  # 初始化传入driverself.driver = driver     # 绑定页面对象defopen(self):print('打开百度页面')self.driver.get(self.url)definput_search_keyword(self, keyword):print(f'输入搜索关键词 {keyword}')self.driver.find_element(*self.search_ipt_loc).send_keys(keyword)defclick_search_button(self):print('点击百度一下按钮')self.driver.find_element(*self.search_btn_loc).click()defsearch(self, keyword):  # 页面常用组合操作print(f'搜索关键字 {keyword}')self.open()self.input_search_keyword(keyword)self.click_search_button()sleep(0.5)@pytest.fixturedefbaidu_home(driver):   # 自定义一个fixture方法方便多个用例共享page_obj = BaiduHomePage(driver)return page_objdeftest_baidu_search_01(driver, baidu_home):baidu_home.seach('博客园 韩志超')assert'韩志超'in driver.titleif __name__ == '__main__':# --html需要pip install pytest-html# --driver 需要pip install pytest-seleniumpytest.main([__file__, '--driver=chrome', '--html=report.html','--self-contained-html'])

上例BaiduHomePage中除了封装了每个元素的单独操作外,还封装了组合的search操作,这样既可以灵活使用(如,只输入搜索词,不点击搜索按钮),也方便用例中快速使用组合操作。

上例中没有把页面对象baidu_home的实例化放到用例中,而是单独封装了一个Fixture方法,这样的好处是,所有需要用到此页面对象的用例都可以直接使用。

框架封装的一个设计方向就是让用户的使用尽可能简单。用例的编写便是用户的一个高频使用场景,我们通过设计要使的用例的编写尽可能简单。

封装常用方法

除了用例的编写外,页面模型也是需要用户进行编辑和新增的,如何使页面模型的编写更简单呢?比如每个页面模型都要编写初始化方法传入driver,比如常用的通过节点文本定位、鼠标悬浮、强制等待、主动等待、偶现元素处理等。我们可以编写一个页面基础类作为所有所有页面对象的父类,在页面基础类中实现这些操作。如下例:

代码test_baidu_search_v8.py内容

Copyfrom time import sleepfrom selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException, NoAlertPresentException
import pytestclassBasePage:url = Nonedef__init__(self, driver):  # 初始化传入driverself.driver = driver     # 绑定页面对象    @propertydeftitle(self):return self.driver.title@propertydefpage_source(self):return self.driver.page_sourcedefopen(self, url=None):url = url or self.url  # 如果没有指定url则打开页面urlprint(f'打开 {url}')if url:self.driver.get(self.url)return self  # 返回self以支持链式操作如page.open('').click_element('')defwait(self, secs=1):print(f"等待 {secs}s")sleep(secs)return selfdeffind_element(self, by, value, timeout=None, ignore_error=False):"""元素定位方法增加显式等待和忽略异常选项(处理偶现元素)"""try:if timeout isNone:return self.driver.find_element(by, value)else:return WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located((by, value)))except NoSuchElementException:if ignore_error isFalse:  # 不忽略错误则抛出异常raisedefclick_element(self, by, value, timeout=None, ignore_error=False):print(f'点击元素 {by}={value} 超时 {timeout} 忽略异常 {ignore_error}')self.find_element(by, value, timeout, ignore_error).click()return selfdefinput_text(self, by, value, text, timeout=None):print(f'向元素 {by}={value} 输入文本 {text} 超时 {timeout}')elm = self.find_element(by, value, timeout)elm.clear()elm.send_keys(text)return selfdefmove_to_element(self, by, value):print('移动到元素 {by}={value}')elm = self.find_element(by, value)ActionChains(self.driver).move_to_element(elm).perform()return selfdefswitch_to_frame(self, *frames):print(f'切换到框架 {" > ".join(frames)}')for frame in frames:self.driver.switch_to.frame(frame)defswitch_to_window(self, index):print(f'切换到第{index+1}个窗口')window_list = self.driver.window_handlesself.driver.switch_to.window(window_list[index])return selfdefdismiss_alert(self, ignore_error=False):print("关闭警告弹框")try:self.driver.switch_to.alert().dissmiss()except NoAlertPresentException:if ignore_error isFalse:raisereturn selfdefremove_attr(self, by, value, attr):print(f'移除元素 {by}={value}{attr}属性')elm = self.find_element(by, value)js_script = f'arguments[0].removeAttribute("{attr}");'self.driver.execute_script(js_script, elm)return self

我们在BasePage页面基础类里,我们将页面标题driver.title,页面源码driver.page_source绑定给页面对象以方便获取。

我们定义了一个open方法拥有打开指定url或者子类配置的url。重新封装了find_element并扩展了显式等待和对偶现元素的支持(偶现元素定位不到视作未出现,不报错)。除了find_element方法返回元素对象外,其他操作方法都返回self对象本身,这样可以使页面对象支持链式操作,如:

Copy...
page = BasePage(driver)
page.open('https://www.baidu.com/').click_element('id','su).input_text('简书韩志超').wait()

此外我们还封装了点击元素、输入文本、鼠标悬浮、切换窗口、框架、关闭警告框、移除元素属性等常用操作。我们在每种基本操作中增加了print信息,使得执行过程更透明易懂。

其他常用的操作读者可以根据需求自行补充其他封装方法,或者直接使用对象.driver来调用driver的原生方法,如定位一组元素:

Copy...
page = BasePage(driver)
elm_list = page.driver.find_elements_by_xpath('//li')

有了页面基础类,每个页面对象类写起来遍稍微简略点,BaiduHomePage类修改后代码如下:

CopyclassBaiduHomePage(BasePage):url = 'https://www.baidu.com'search_ipt_loc = ('id', 'kw')  # 百度搜索框search_btn_loc = ('id', 'su')  # 百度一下按钮definput_search_keyword(self, keyword):self.input_text(*self.search_ipt_loc, keyword)defclick_search_button(self):self.click_element(*self.search_btn_loc)defsearch(self, keyword):  # 页面常用组合操作print(f'搜索关键字 {keyword}')self.open().input_search_keyword(keyword).click_search_button().wait(0.5)

首先集成BasePage类,并无须再写__init__初始化方法,直接配置url和页面元素对象即可。这里对单个元素操作不再添加额外打印信息,使用基础方法click_element、input_text自带的打印信息。

在组合操作search方法中,我们使用了链式操作,写起来更简洁。

注:在页面对象类的元素操作中也可以每个操作都返回self,以使得上层测试用例再使用时支持链式操作。

提高运行效率

提高运行效率通常以下两种方式:

  • 优化用例执行速度:如使用Headless无界面模式、使用Cookie绕过登录、使用页面URL直达内部页面(而不是通过页面一步步操作)及使用接口、数据库(而不是页面操作)进行测准备等。

  • 并行执行:并行是开多个浏览器同时执行多条用例,这就要求我们的用例之间没有运行顺序的依赖(用例可以单独运行)。

使用Headless模式

Headless即无界面模式,可以在一定程度上提高用例的执行速度。pytest-selenium插件提供了chrome_options的Fixture函数,可以添加Chrome浏览器参数。我们只需要自定义一个--headless命令后选项,重写chrome_options参数,通过request这个内置的Fixture方法拿到配置对象config,判断命令行选项是否包含--headless来添加对应的浏览器参数即可。

实现方式如下:

文件conftest.py部分内容

Copyimport pytestdefpytest_addoption(parser):parser.addoption('--headless', action='store_true', help='run chrome headless')@pytest.fixturedefchrome_options(request, chrome_options):if request.config.getoption('--headless'):chrome_options.add_argument('--headless')return chrome_options
使用--headless运行测试用例
...
if __name__ == '__main__':# --html需要pip install pytest-html# --driver 需要pip install pytest-selenium# --base-url 需要pip install pytest-base-url# --variables 需要安装 pip install pytest-variablespytest.main([__file__, '--driver=chrome', '--headless','--html=report.html','--self-contained-html','--base-url=https://www.baidu.com','--variables=test.json'])

多进程并行测试

使用pytest-xdist可以启动多个进程来平均分发多个用例,安装方法如下:

Copypip install pytest-xdist

使用方法非常简单,命令行中添加参数-n=<进程数>即可。

Copypytest -n=3

即启动3个进程来执行所有用例。

发送邮件

添加自定义选项和配置

假设我们要实现一个运行完发送Email的功能。 我们自定义一个命令行参数项--send-email,不需要参数值。当用户带上该参数运行时,我们就发送报告,不带则不发,运行格式如下:

Copypytest test_cases/ --html=report.html --send-email

这里,一般应配合--html先生成报告。 由于Pytest本身并没有--send-email这个参数,我们需要通过Hooks方法进行添加。

文件conftest.py部分内容

Copy
def pytest_addoption(parser):"""Pytest初始化时添加选项的方法"""parser.addoption("--send-email", action="store_true", help="send email with test report")

另外,发送邮件我们还需要邮件主题、正文、收件人等配置信息。我们可以把这些信息配置到pytest.ini中,如:

文件pytest.ini部分内容

Copy...
email_subject = Test Report
email_receivers = superhin@126.com
email_body = Hi,all\n, Please check the attachment for the Test Report.

这里需要注意,自定义的配置选项需要先注册才能使用,注册方法如下:

**文件conftest.py部分内容

Copydefpytest_addoption(parser):...parser.addini('email_subject', help='test report email subject')parser.addini('email_receivers', help='test report email receivers')parser.addini('email_body', help='test report email body')

实现发送Email功能

前面我们只是添加了运行参数和Email配置,我们在某个生成报告时的Hook方法中,根据参数添加发送Email功能,示例如下:

**文件conftest.py部分内容

Copyfrom utils.notify import Emaildefpytest_terminal_summary(config):"""Pytest生成报告时的命令行报告运行总结方法"""send_email = config.getoption("--send-email")email_receivers = config.getini('email_receivers').split(',')if send_email isTrueand email_receivers:report_path = config.getoption('htmlpath')email_subject = config.getini('email_subject') or'TestReport'email_body = config.getini('email_body') or'Hi'if email_receivers:Email().send(email_subject, email_receivers, email_body, report_path)

框架整理

分类整理

一个好的框架需要清晰的结构,我们使用目录(或包)将不同的脚本进行归类,例如:

  • testcases:存放测试用例,可以按模块建立子目录存放fixtures方法集中放在conftest.py中;

  • pages:存放页面对象模型,可以按模块建立子目录存放;

  • utils:存放常用的工具,方法的封装,如发邮件功能的封装;

另外我们对输入的测试数据(资源),输出的测试报告、日志文件等也需要建立指定的目录存放,如:

  • data/:存放测试数据或资源;

  • reports/:存放测试报告,运行日志等

再加上Pytest运行配置pytest.ini和一些说明文件,如:

  • pytest.ini:Pytest配置文件;

  • requirements.txt:运行依赖的三方包;

  • README.md:框架说明文件。

整个框架结构如下:

CopyWebAuto/| -- data/| -- test.json| -- prod.json| -- pages/| -- baidu_page.py| -- base_page.py| -- reports/| -- testcases/| -- test_baidu_search.py| -- utils/| -- send_email.pyconftest.pypytest.inirequirements.txtREADME.md

敏感数据处理

在测试环境中经常会用到一些身份认证信息,如用户名、密码等,这些属于敏感数据,直接写在代码中,有可能会造成敏感信息泄露。最简单的做法是,将这些敏感信息配置到所运行机器(如本机)的环境变量中。如,我们在自己电脑上的环境变量中添加两个变量:

CopyWEBAUTO_DEFAULT_USER=admin
WEBAUTO_DEFAULT_PWD=123456

然后我们可以在代码中通过os.getenv()来获取指定的环境变量:

Copyimport osusername = os.getenv('WEBAUTO_DEFAULT_USER')
password = os.getenv('WEBAUTO_DEFAULT_PWD')

声明依赖文件

一般来说,框架不只是给自己一个人使用的,多数情况下需要大家协作完成用例的补充。这时候我们一般要在项目中新建一个requirements.txt的来列出所有需要安装的三方包,例如:

Copyselenium
pytest
pytest-selenium
pytest-html
pytest-variables
pytest-timeout
pytest-level
pytest-base-url
pytest-ordering
pytest-rerunfailures
pytest-xdist

编写使用说明

一个框架最好能有一个使用说明一样的文件,简单阐述下框架的结构、有哪些特性、如何编写维护用例、如何运行等等。一般推荐使用Markdown语法编写。Markdown是一种标记语言,可以通过不同的标记写出层次分明的文档,示例如下:

Copy# WebAuto **项目Web自动化测试框架
使用Pytest + Selenium基于POM模式搭建。## 特性
## 安装方法
##  使用方法

实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

电商项目实战

web测试项目

web+App+h5+小程序 测试项目

接口自动化测试实战项目

Linux实战项目

面试资料

我们进阶学习自动化测试必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

以上资料,对于想要测试进阶的朋友们来说应该会很有帮助,需要的小伙伴可以后台私信找我免费领取。

总结

我见过很多leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了好几年,更夸张的是7、8年工作内容的重复性比较高,没有什么技术含量的工作。

凡事要趁早,特别是技术行业,一定要提升技术功底,丰富自动化项目实战经验,这对于你未来几年职业规划,以及测试技术掌握的深度非常有帮助。

如果对你有帮助的话,点个赞收个藏,给作者一个鼓励。也方便你下次能够快速查找。

如有不懂还要咨询下方小卡片,博主也希望和志同道合的测试人员一起学习进步

在适当的年龄,选择适当的岗位,尽量去发挥好自己的优势。

我的自动化测试开发之路,一路走来都离不每个阶段的计划,因为自己喜欢规划和总结,

测试开发视频教程、学习笔记领取传送门!!!

相关文章:

带你一步步搭建Web自动化测试框架

测试框架的设计有两种思路&#xff0c;一种是自底向上&#xff0c;从脚本逐步演变完善成框架&#xff0c;这种适合新手了解框架的演变过程。另一种则是自顶向下&#xff0c;直接设计框架结构和选取各种问题的解决方案&#xff0c;这种适合有较多框架事件经验的人。本章和下一张…...

Redis进阶-缓存问题

Redis 最常用的一个场景就是作为缓存&#xff0c;本文主要探讨Redis作为缓存&#xff0c;在实践中可能会有哪些问题&#xff1f;比如一致性、击穿、穿透、雪崩、污染等。 为什么要理解Redis缓存问题 在高并发业务场景下&#xff0c;数据库大多数情况都是用户并发访问最薄弱的…...

VS Code Spring 全新功能来了!

大家好&#xff0c;欢迎来到我们 2023 年的第一篇博客&#xff01;我们想与您分享几个与 Spring 插件、代码编辑和性能相关的激动人心的更新&#xff0c;让我们开始吧&#xff01; Spring 插件包的新入门演练 演练&#xff08;Walkthrough&#xff09; 是一种多步骤、向导式的体…...

关于大数据导入流程引擎ccflow的方案

问题&#xff1a; 1. 现在的流程系统里有几百万条已经运行的流程其它的流程架构上 2. 需要把这样的数据导入到ccflow流程引擎里面去。 数据结构分析: 1. ccflow有流程引擎注册表&#xff0c;工作人表&#xff0c;业务数据表与日志表4大表. 2. ccflow的流程实例是一个int类型的…...

AI 生成二次元女孩,免费云端部署(仅需5分钟)

首先需要google的colab&#xff0c;免费版本GPU有额度。其次&#xff0c;打开github网站&#xff0c;选择一个进入colab,修改代码 !apt-get -y install -qq aria2 !pip install -q https://github.com/camenduru/stable-diffusion-webui-colab/releases/download/0.0.16/xforme…...

掌握MySQL分库分表(六)解决主键重复问题--Snowflake雪花算法

文章目录问题及需求常用ID解决方案数据库自增IDUUIDRedis发号器Snowflake雪花算法分布式 ID 生成算法Snowflake原理关于bit与byte雪花算法的位数Snowflake必须注意的地方全局唯⼀、不能重复保证各个系统时间一致Snowflake雪花算法实现雪花算法测试结果问题及需求 单库下⼀般使…...

Melis4.0[D1s]:1.启动流程(与adc按键初始化相关部分)跟踪笔记

文章目录1.启动流程1.1 最先进入的文件&#xff1a;head_s.S1.2 start_kernel()函数所在的文件&#xff1a;init.c1.3 input_init()函数所在文件&#xff1a;sys_input.c1.4 INPUT_LKeyDevInit()所在文件&#xff1a;keyboarddev.c1.5 esINPUT_RegLdev()所在文件&#xff1a;in…...

GNU make 中文手册 第三章:Makefile 总述

一、Makefile 总述 3.1 Makefile 的内容 在一个完整的 Makefile 中&#xff0c;包含了 5 个东西&#xff1a;显式规则、隐含规则、变量定义、指示符和注释。关于“规则”、“变量” 和 “Makefile 指示符” 将在后续的章节进行详细的讨论。本章讨论的是一些基本概念。 显式规…...

简历的专业技能怎么写?排版需要注意的事项

一、简历的专业技能怎么写? 首先,先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几 天时间学习一下,然后在简历上可以写上自己了解这个技…...

【Git】为什么需要版本控制?版本控制工具有那些?

目录 一、为什么需要版本控制&#xff1f; 二、版本控制工具有那些&#xff1f; &#x1f49f; 创作不易&#xff0c;不妨点赞&#x1f49a;评论❤️收藏&#x1f499;一下 一、为什么需要版本控制&#xff1f; 首先我们要知道什么是版本控制&#xff1f;对版本控制进行文字…...

SSH远程执行Python3 Error: UnicodeEncodeError: ‘ascii‘ codec

首先确定要执行脚本服务器的语言编码环境&#xff0c;执行 # locale -a C en_US.utf8 POSIX # locale LANGen_US.utf8 LC_CTYPE"en_US.utf8" LC_NUMERIC"en_US.utf8" LC_TIME"en_US.utf8" LC_COLLATE"en_US.utf8" LC_MONETARY"…...

极简TypeScript教程--面向对象

在早期的JavaScript开发中&#xff08;ES5&#xff09;我们需要通过函数和原型链来实现类和继承&#xff0c;从ES6开始&#xff0c;引入了class关键字&#xff0c;可以更加方便的定义和使用类。TypeScript作为JavaScript的超集&#xff0c;也是支持使用class关键字的&#xff0…...

java TCP/UDP、Socket、URL网络编程详解

文章目录网络通信协议通信双方地址端口号IP地址InetAddress类Socket 网路编程Socket类的常用构造器Socket类的常用方法UDP协议什么是UDP协议UDP网络编程DatagramSocket 构造方法DatagramSocket 常用方法DatagramPacket常用方法实现步骤单向数据发收的UDP程序双向数据发收的UDP程…...

【C语言】宏

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;> c语言学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是…...

【测试面试】自我分析+功能+接口自动化+性能测试面试题(大全),知己知彼百战百胜......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 分析自己和面试企业…...

ASE4N65SE-ASEMI高压MOS管ASE4N65SE

编辑-Z ASE4N65SE在TO-220F封装里的静态漏极源导通电阻&#xff08;RDS(ON)&#xff09;为2.5Ω&#xff0c;是一款N沟道高压MOS管。ASE4N65SE的最大脉冲正向电流ISM为16A&#xff0c;零栅极电压漏极电流(IDSS)为10uA&#xff0c;其工作时耐温度范围为-55~150摄氏度。ASE4N65S…...

MyBatis概述环境搭建(一)

&#x1f697;MyBatis学习起始站~ &#x1f6a9;本文已收录至专栏&#xff1a;数据库学习之旅 &#x1f44d;希望您能有所收获 一.什么是MyBatis (1) 引言 MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDB…...

3.8国际妇女节即将到来,跨境卖家如何做好选品和营销?

不知不觉&#xff0c;时间已来到了2月末&#xff0c;一年一度的三八国际妇女节也即将来临。三八节又称女神节&#xff0c;这不仅是庆祝女性伟大贡献的日子&#xff0c;也是跨境卖家们促销的大好时机。 有数据显示&#xff0c;女性是跨境消费的主力人群&#xff0c;占比超七成&…...

Glue Connector 和 Connection 的关系与区别

AWS Glue作为一种无服务器产品&#xff0c;其运行环境是“不可预知”的&#xff0c;也就是“一个黑盒”&#xff0c;所以如何能连接一些自有数据源是Glue必须考虑并给予满足的&#xff0c;为此&#xff0c;Glue给出的解决方案就是Connector和Connection&#xff0c;一个connect…...

如何使用ngxin的 upstream

1.引言&#xff1a; 1.1反向代理&#xff1a; 反向代理是充当Web服务器网关的代理服务器。当您将请求发送到使用反向代理的Web服务器时&#xff0c;他们将先转到反向代理&#xff0c;由该代理将确定是将其路由到Web服务器还是将其阻止。 这意味着有了反向代理&#xff0c;您…...

Java数组,超详细整理,适合新手入门

目录 一、什么是Java中的数组&#xff1f; 二、数组有哪些常见的操作&#xff1f; 三、数组的五种赋值方法和使用方法 声明数组 声明数组并且分配空间 声明数组同时赋值(1) 声明数组同时赋值(2) 从控制台输入向数组赋值 四、求总和平均 五、求数组中最大值最小值 六…...

1.3数据传输控制方式:IO数据传输控制方式、程序控制(查询)方式、程序中断方式、DMA方式、通道方式、I/O处理机

1.3数据传输控制方式&#xff1a;IO数据传输控制方式、程序控制&#xff08;查询&#xff09;方式、程序中断方式、DMA方式、通道方式、I/O处理机程序控制&#xff08;查询&#xff09;方式程序中断方式DMA方式通道方式、I/O处理机I/O数据传输方式&#xff0c;由软件到硬件发展…...

Linux 设置语言

文章目录1. 临时设置环境变量2. 默认语言设置3. 语言包4. 安装浏览器 chromium1. 临时设置环境变量 通过设置环境变量&#xff0c;可以使单个命令使用另一种语言LANG $ LANGfr_FR.utf8 date mar. mai 24 12:16:51 CDT 2022后续命令将恢复为使用系统的默认语言进行输出。该loc…...

Python基础-数据类型之集合

一、集合的定义 集合&#xff1a;是一个无序的没有重复元素的序列&#xff0c;因此不能通过索引来进行操作 1&#xff1a;使用set()创建集合 set(object) # 参数为一个序列&#xff0c;整型不能作为参数 set_a set("abcb") print(set_a) # {b, a, c} 2&…...

[Css]Grid属性简单陈列(适合开发时有基础的快速过一眼)

[css进阶]Grid属性简介 文章目录[css进阶]Grid属性简介典型需求网格容器的属性displaygrid-template-columns和grid-template-rowsgrid-template-areasgrid-templategrid-column-gap grid-row-gapgrid-gapjustify-itemsalign-itemsjustify-contentalign-contentgrid-auto-colum…...

100种思维模型之启发式偏差思维模型-017

曾国藩在给儿子的一封家书中曾写道&#xff1a;余于凡事皆用困知勉行工夫&#xff0c;尔不可求名太骤&#xff0c;求效太捷也。熬过此关&#xff0c;便可少进。再进再困&#xff0c;再熬再奋&#xff0c;自有亨通精进之日。 不急躁不求捷径&#xff0c;小火慢炖&#xff0c;将事…...

微服务 feign远程调用时 显示服务不可用 timed-out and no fallback

目录 第一种: failed and no fallback available 1 服务挂掉了 2 服务没有开启 3 注册中心没注册进去 -> ps: 直接调用的接口 通过网关转发失败 会报503 4 高并发下的服务熔断了 第二种: timed-out and no fallback 2.1 业务场景: A服务一切正常 但是B服务显示timeo…...

第一个Java程序(初识Java)

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【JavaSE_primary】 文章目录1.Java概述1.1什么是Java1.2Java之父2.0第一个Java程序编译运行.class3.0程序如何跑起来的&#xff1f;3.1J…...

vulnhub LordOfTheRoot_1.0.1

总结&#xff1a;端口敲门&#xff0c;CVE-2015-8660提权&#xff0c; 目录 下载地址 漏洞分析 信息收集 端口敲门 网站分析 方法一 ssh登录提权 方法二 下载地址 LordOfTheRoot_1.0.1.ova (Size: 1.6 GB)Download: http://www.mediafire.com/download/m5tbx0dua05szjm…...

MutationObserver与IntersectionObserver

MutationObserver 出现原因&#xff1a;当我们需要监听元素发生变化时&#xff0c;不借助使元素发生变化的业务动作的情况下&#xff0c;使用无污染方式监听非常困难&#xff0c;为了解决这个问题&#xff0c;MutationObserver诞生&#xff01; 概述 可以用来监听DOM的任何变化…...