【附源码】完整版,Python+Selenium+Pytest+POM自动化测试框架封装

news2024/11/25 15:54:54

目录:导读

    • 前言
    • 一、Python编程入门到精通
    • 二、接口自动化项目实战
    • 三、Web自动化项目实战
    • 四、App自动化项目实战
    • 五、一线大厂简历
    • 六、测试开发DevOps体系
    • 七、常用自动化测试工具
    • 八、JMeter性能测试
    • 九、总结(尾部小惊喜)


前言

1、测试框架简介

1)测试框架的优点

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

2)测试框架的整体目录

目录/文件说明是否为python包
common这个包中存放的是常见的通用的类,如读取配置文件
config配置文件目录
logs日志目录
page对selenium的方放进行深度的封装
page_element页面元素存放目录
page_object页面对象POM设计模式,本人对这个的理解来自于苦叶子的博客
TestCase所有的测试用例集
utils工具类
script脚本文件
conftest.pypytest胶水文件
pytest.inipytest配置文件

【注意】 init.py 文件用以标识此目录为一个python包。

2、首先时间管理

首先,因为很多的模块都会用到当前时间的时间戳,或者日期等等字符串,所以先单独把时间操作(我们需要获取的不同格式的时间信息)封装成一个模块。

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

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import time
import datetime
from functools import wraps


def timestamp():
    """时间戳"""
    return time.time()


def dt_strftime(fmt="%Y%m"):
    """
    datetime格式化时间
    :param fmt "%Y%m%d %H%M%S
    """
    return datetime.datetime.now().strftime(fmt)


def sleep(seconds=1.0):
    """
    睡眠时间
    """
    time.sleep(seconds)


def running_time(func):
    """函数运行时间"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        start = timestamp()
        res = func(*args, **kwargs)
        print("校验元素done!用时%.3f秒!" % (timestamp() - start))
        return res

    return wrapper


if __name__ == '__main__':
    print(dt_strftime("%Y%m%d%H%M%S"))

3、添加配置文件

1)conf.py

UI自动化测试框架中应该有一个文件对整体的目录进行管理;

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

import os
from selenium.webdriver.common.by import By
from utils.times import dt_strftime


class ConfigManager(object):
    # 项目目录
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

    # 页面元素目录
    ELEMENT_PATH = os.path.join(BASE_DIR, 'page_element')

    # 报告文件
    REPORT_FILE = os.path.join(BASE_DIR, 'report.html')

    # 元素定位的类型
    LOCATE_MODE = {
        'css': By.CSS_SELECTOR,
        'xpath': By.XPATH,
        'name': By.NAME,
        'id': By.ID,
        'class': By.CLASS_NAME
    }

    # 邮件信息
    EMAIL_INFO = {
        'username': 'xxxxxxx@qq.com',  # 切换成你自己的地址
        'password': 'QQ邮箱授权码',
        'smtp_host': 'smtp.qq.com',
        'smtp_port': 465
    }

    # 收件人
    ADDRESSEE = [
        'xxxxxxxx@qq.com',
    ]

    @property
    def log_file(self):
        """日志目录"""
        log_dir = os.path.join(self.BASE_DIR, 'logs')
        if not os.path.exists(log_dir):
            os.makedirs(log_dir)
        return os.path.join(log_dir, '{}.log'.format(dt_strftime()))

    @property
    def ini_file(self):
        """配置文件"""
        ini_file = os.path.join(self.BASE_DIR, 'config', 'config.ini')
        if not os.path.exists(ini_file):
            raise FileNotFoundError("配置文件%s不存在!" % ini_file)
        return ini_file


cm = ConfigManager()
if __name__ == '__main__':
    print(cm.BASE_DIR)

【注意】:QQ邮箱授权码:点击查看生成教程

2)config.ini

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

[HOST]
HOST = https://www.baidu.com

3)读取配置文件

①配置文件创建好了,接下来我们需要读取这个配置文件以使用里面的信息。
②使用python内置的 configparser 模块对 config.ini 文件信息进行了读取。
③对于测试url值的提取,使用python高阶语法 @property 属性值,写法更简单。
④在 common 目录中新建一个 readconfig.py 文件。

import configparser
from config.conf import cm

HOST = 'HOST'


class ReadConfig(object):
    """配置文件"""

    def __init__(self):
        self.config = configparser.RawConfigParser()  # 当有%的符号时请使用Raw读取
        self.config.read(cm.ini_file, encoding='utf-8')

    def _get(self, section, option):
        """获取"""
        return self.config.get(section, option)

    def _set(self, section, option, value):
        """更新"""
        self.config.set(section, option, value)
        with open(cm.ini_file, 'w') as f:
            self.config.write(f)

    @property
    def url(self):
        return self._get(HOST, HOST)


ini = ReadConfig()

if __name__ == '__main__':
    print(ini.url)

4、记录操作日志

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

import logging
from config.conf import cm


class Log:
    def __init__(self):
        self.logger = logging.getLogger()
        if not self.logger.handlers:
            self.logger.setLevel(logging.DEBUG)

            # 创建一个handle写入文件
            fh = logging.FileHandler(cm.log_file, encoding='utf-8')
            fh.setLevel(logging.INFO)

            # 创建一个handle输出到控制台
            ch = logging.StreamHandler()
            ch.setLevel(logging.INFO)

            # 定义输出的格式
            formatter = logging.Formatter(self.fmt)
            fh.setFormatter(formatter)
            ch.setFormatter(formatter)

            # 添加到handle
            self.logger.addHandler(fh)
            self.logger.addHandler(ch)

    @property
    def fmt(self):
        return '%(levelname)s\t%(asctime)s\t[%(filename)s:%(lineno)d]\t%(message)s'


log = Log().logger

if __name__ == '__main__':
    log.info('hello world')

5、项目中元素相关

POM模型:PO模式详解

元素定位:①selenium UI自动化测试xpath定位详解 ;②selenium UI自动化测试之CSS元素定位语法详解

6、管理页面元素

1)新建page_element目录并在该目录下新建search.yaml文件

①本教程选择的测试地址是百度首页,所以对应的元素也是百度首页的。
②项目框架设计中有一个page_element 目录就是专门来存放定位元素的文件的。
③通过对各种配置文件的对比,我在这里选择的是YAML文件格式。其易读,交互性好。
④在 page_element 中新建一个 search.yaml 文件。文件内容如下:

搜索框: "id==kw"
候选: "css==.bdsug-overflow"
搜索候选: "css==#form div li"
搜索按钮: "id==su"

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

①在 common 目录中创建 readelement.py 文件。实现了定位元素的存储和调用。文件内容如下:

②通过特殊方法 getitem 实现调用任意属性,读取yaml中的值。参考博客:python之__getitem__ 方法学习与使用 || python方法下划线命名规则

import os
import yaml
from config.conf import cm


class Element(object):
    """获取元素"""

    def __init__(self, name):
        self.file_name = '%s.yaml' % name
        self.element_path = os.path.join(cm.ELEMENT_PATH, self.file_name)
        if not os.path.exists(self.element_path):
            raise FileNotFoundError("%s 文件不存在!" % self.element_path)
        with open(self.element_path, encoding='utf-8') as f:
            self.data = yaml.safe_load(f)

    def __getitem__(self, item):
        """获取属性"""
        data = self.data.get(item)
        if data:
            name, value = data.split('==')
            return name, value
        raise ArithmeticError("{}中不存在关键字:{}".format(self.file_name, item))


if __name__ == '__main__':
    search = Element('search')
    print(search['搜索框'])

3)新建script脚本文件目录并新建inspect.py文件

在 script 脚本文件目录中创建 inspect.py 文件,对所有的元素yaml文件内容进行审查。

import os
import yaml
from config.conf import cm
from utils.times import running_time


@running_time
def inspect_element():
    """检查所有的元素是否正确
    只能做一个简单的检查
    """
    for files in os.listdir(cm.ELEMENT_PATH):
        _path = os.path.join(cm.ELEMENT_PATH, files)
        with open(_path, encoding='utf-8') as f:
            data = yaml.safe_load(f)
        for k in data.values():
            try:
                pattern, value = k.split('==')
            except ValueError:
                raise Exception("元素表达式中没有`==`")
            if pattern not in cm.LOCATE_MODE:
                raise Exception('%s中元素【%s】没有指定类型' % (_path, k))
            elif pattern == 'xpath':
                assert '//' in value,\
                    '%s中元素【%s】xpath类型与值不配' % (_path, k)
            elif pattern == 'css':
                assert '//' not in value, \
                    '%s中元素【%s]css类型与值不配' % (_path, k)
            else:
                assert value, '%s中元素【%s】类型与值不匹配' % (_path, k)


if __name__ == '__main__':
    inspect_element()

7、封装Selenium基类

①工厂模式的写法:很直白,简单,又明了。
【创建driver对象,打开百度网页,搜索selenium,点击搜索,然后停留5秒,查看结果,最后关闭浏览器。】

import time
from selenium import webdriver


driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.find_element_by_xpath("//input[@id='kw']").send_keys('selenium')
driver.find_element_by_xpath("//input[@id='su']").click()
time.sleep(5)
driver.quit()

②那为什么要封装selenium的方法呢?

首先我们上述这种较为原始的方法,基本不适用于平时做UI自动化测试的:因为在UI界面实际运行情况远远比较复杂,可能因为网络原因,或者控件原因,我们元素还没有显示出来,就进行点击或者输入。

所以我们需要封装selenium方法,通过内置的显式等待或一定的条件语句,才能构建一个稳定的方法。而且把selenium方法封装起来,有利于平时的代码维护。

1)新建page目录并创建webpage.py文件

①在 page 目录创建 webpage.py 文件。文件内容如下:

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

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException

from config.conf import cm
from utils.times import sleep
from utils.logger import log

"""
selenium基类
本文件存放了selenium基类的封装方法
"""


class WebPage(object):
    """selenium基类"""

    def __init__(self, driver):
        # self.driver = webdriver.Chrome()
        self.driver = driver
        self.timeout = 20
        self.wait = WebDriverWait(self.driver, self.timeout)

    def get_url(self, url):
        """打开网址并验证"""
        self.driver.maximize_window()
        self.driver.set_page_load_timeout(60)
        try:
            self.driver.get(url)
            self.driver.implicitly_wait(10)
            log.info("打开网页:%s" % url)
        except TimeoutException:
            raise TimeoutException("打开%s超时请检查网络或网址服务器" % url)

    @staticmethod
    def element_locator(func, locator):
        """元素定位器"""
        name, value = locator
        return func(cm.LOCATE_MODE[name], value)

    def find_element(self, locator):
        """寻找单个元素"""
        return WebPage.element_locator(lambda *args: self.wait.until(
            EC.presence_of_element_located(args)), locator)

    def find_elements(self, locator):
        """查找多个相同的元素"""
        return WebPage.element_locator(lambda *args: self.wait.until(
            EC.presence_of_all_elements_located(args)), locator)

    def elements_num(self, locator):
        """获取相同元素的个数"""
        number = len(self.find_elements(locator))
        log.info("相同元素:{}".format((locator, number)))
        return number

    def input_text(self, locator, txt):
        """输入(输入前先清空)"""
        sleep(0.5)
        ele = self.find_element(locator)
        ele.clear()
        ele.send_keys(txt)
        log.info("输入文本:{}".format(txt))

    def is_click(self, locator):
        """点击"""
        self.find_element(locator).click()
        sleep()
        log.info("点击元素:{}".format(locator))

    def element_text(self, locator):
        """获取当前的text"""
        _text = self.find_element(locator).text
        log.info("获取文本:{}".format(_text))
        return _text

    @property
    def get_source(self):
        """获取页面源代码"""
        return self.driver.page_source

    def refresh(self):
        """刷新页面F5"""
        self.driver.refresh()
        self.driver.implicitly_wait(30)

8、创建页面对象

新建page_object目录并创建一个searchpage.py文件

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

②在该文件中我们对,输入搜索关键词,点击搜索,搜索联想,进行了封装。【在平时中我们应该养成写注释的习惯,因为过一段时间后,没有注释,代码读起来很费劲。】

9、应用pytest测试框架

1)pytest.ini文件创建

①pytest项目中的配置文件,可以对pytest执行过程中操作做全局控制。
②在项目根目录新建 pytest.ini 文件。文件内容如下:

[pytest]
addopts = --html=report.html --self-contained-html

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

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

10、编写测试用例

1)新建TestCase目录并创建test_search.py文件

①在 TestCase 目录中创建 test_search.py 文件。文件内容如下:
② pytest.fixture装饰器实现了和unittest的setup,teardown一样的前置启动,后置清理的装饰器。

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

④第二个测试用例:实现了百度搜索selenium关键字,然后断言搜索候选中的所有结果有没有selenium关键字。

import re
import pytest
from utils.logger import log
from common.readconfig import ini
from page_object.searchpage import SearchPage


class TestSearch:
    @pytest.fixture(scope='function', autouse=True)
    def open_baidu(self, drivers):
        """打开百度"""
        search = SearchPage(drivers)
        search.get_url(ini.url)

    def test_001(self, drivers):
        """搜索"""
        search = SearchPage(drivers)
        search.input_search("selenium")
        search.click_search()
        result = re.search(r'selenium', search.get_source)
        log.info(result)
        assert result

    def test_002(self, drivers):
        """测试搜索候选"""
        search = SearchPage(drivers)
        search.input_search("selenium")
        log.info(list(search.imagine))
        assert all(["selenium" in i for i in search.imagine])


if __name__ == '__main__':
    pytest.main(['TestCase/test_search.py'])

conftest.py

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

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

import pytest
from py.xml import html
from selenium import webdriver


driver = None


@pytest.fixture(scope='session', autouse=True)
def drivers(request):
    """
    
    :param request: python内置的fixture函数,本函数中用来注册终结函数
    :return: 返回driver实例
    """
    global driver
    if driver is None:
        driver = webdriver.Chrome()
        driver.maximize_window()

    def fn():
        driver.quit()

    request.addfinalizer(fn)
    return driver


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
    """
    当测试失败的时候,自动截图,展示到html报告中
    :param item:
    """
    pytest_html = item.config.pluginmanager.getplugin('html')
    outcome = yield
    report = outcome.get_result()
    report.description = str(item.function.__doc__)
    extra = getattr(report, 'extra', [])

    if report.when == 'call' or report.when == "setup":
        xfail = hasattr(report, 'wasxfail')
        if (report.skipped and xfail) or (report.failed and not xfail):
            file_name = report.nodeid.replace("::", "_") + ".png"
            screen_img = _capture_screenshot()
            if file_name:
                html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" ' \
                       'οnclick="window.open(this.src)" align="right"/></div>' % screen_img
                extra.append(pytest_html.extras.html(html))
        report.extra = extra


def pytest_html_results_table_header(cells):
    cells.insert(1, html.th('用例名称'))
    cells.insert(2, html.th('Test_nodeid'))
    cells.pop(2)


def pytest_html_results_table_row(report, cells):
    cells.insert(1, html.td(report.description))
    cells.insert(2, html.td(report.nodeid))
    cells.pop(2)


def pytest_html_results_table_html(report, data):
    if report.passed:
        del data[:]
        data.append(html.div('通过的用例未捕获日志输出.', class_='empty log'))


def _capture_screenshot():
    '''
    截图保存为base64
    :return:
    '''
    return driver.get_screenshot_as_base64()

11、发送邮件

①当项目执行完成之后,需要发送到自己或者其他人邮箱里查看结果。
②编写发送邮件的模块。
③在 utils 目录中新建 send_mail.py 文件,文件内容如下:

import zmail
from config.conf import cm


def send_report():
    """发送报告"""
    with open(cm.REPORT_FILE, encoding='utf-8') as f:
        content_html = f.read()
    try:
        mail = {
            'from': 'xxxxxx@qq.com',
            'subject': '最新的测试报告邮件',
            'content_html': content_html,
            'attachments': [cm.REPORT_FILE, ]
        }
        server = zmail.server(*cm.EMAIL_INFO.values())
        server.send_mail(cm.ADDRESSEE, mail)
        print("测试邮件发送成功!")
    except Exception as e:
        print("Error: 无法发送邮件,{}!", format(e))


if __name__ == "__main__":
    '''请先在config/conf.py文件设置QQ邮箱的账号和密码'''
    send_report()

④在 config/conf.py 文件中设置我们自己的QQ邮箱账号以及QQ邮箱授权码。运行 send_report() 函数。

下面是我整理的2023年最全的软件测试工程师学习知识架构体系图

一、Python编程入门到精通

请添加图片描述

二、接口自动化项目实战

请添加图片描述

三、Web自动化项目实战

请添加图片描述

四、App自动化项目实战

请添加图片描述

五、一线大厂简历

请添加图片描述

六、测试开发DevOps体系

请添加图片描述

七、常用自动化测试工具

请添加图片描述

八、JMeter性能测试

请添加图片描述

九、总结(尾部小惊喜)

生命太短暂,不要浪费在平庸和懒惰上,勇敢追逐梦想,不断超越自我,坚信付出终将有回报,努力奋斗,成就不凡。

奋斗是人生的座右铭,不畏困难,不怕失败,只有拼尽全力,超越自我,方能创造属于自己的辉煌,成就无限可能。

莫等待时机,勇往直前;莫悔昨日,全情投入;莫怕失败,追求突破;只有坚持奋斗,方能成就辉煌人生,创造属于自己的无限可能。

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

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

相关文章

【S2ST】Direct Speech-to-Speech Translation With Discrete Units

【S2ST】Direct Speech-to-Speech Translation With Discrete Units AbstractIntroductionRelated workModelSpeech-to-unit translation (S2UT) modelMultitask learningUnit-based vocoder ExperimentsDataSystem setupBaselineASRMTTTSS2TTransformer Translatotron Evaluat…

【广州华锐互动】VR沉浸式体验铝厂安全事故让伤害教育更加深刻

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经逐渐渗透到各个领域&#xff0c;为我们的生活带来了前所未有的便捷和体验。在安全生产领域&#xff0c;VR技术的应用也日益受到重视。 VR公司广州华锐互动就开发了多款VR安全事故体验系统&#xff0c…

大数据技术2:大数据处理流程

前言&#xff1a;下图是一个简化的大数据处理流程图&#xff0c;大数据处理的主要流程包括数据收集、数据存储、数据处理、数据应用等主要环节。 1.1 数据收集 大数据处理的第一步是数据的收集。现在的中大型项目通常采用微服务架构进行分布式部署&#xff0c;所以数据的采集需…

如何使用Docker本地搭建开源CMF Drupal并结合内网穿透公网访问

文章目录 前言1. Docker安装Drupal2. 本地局域网访问3 . Linux 安装cpolar4. 配置Drupal公网访问地址5. 公网远程访问Drupal6. 固定Drupal 公网地址 前言 Dupal是一个强大的CMS&#xff0c;适用于各种不同的网站项目&#xff0c;从小型个人博客到大型企业级门户网站。它的学习…

【目标检测算法】IOU、GIOU、DIOU、CIOU

目录 参考链接 前言 IOU(Intersection over Union) 优点 缺点 代码 存在的问题 GIOU(Generalized Intersection over Union) 来源 GIOU公式 实现代码 存在的问题 DIoU(Distance-IoU) 来源 DIOU公式 优点 实现代码 总结 参考链接 IoU系列&#xff08;IoU, GIoU…

Java程序员,你掌握了多线程吗?(文末送书)

目录 01、多线程对于Java的意义02、为什么Java工程师必须掌握多线程03、Java多线程使用方式04、如何学好Java多线程送书规则 摘要&#xff1a;互联网的每一个角落&#xff0c;无论是大型电商平台的秒杀活动&#xff0c;社交平台的实时消息推送&#xff0c;还是在线视频平台的流…

Java API接口强势对接:构建高效稳定的系统集成方案

文章目录 1. Java API接口简介2. Java API接口的优势2.1 高度可移植性2.2 强大的网络通信能力2.3 多样化的数据处理能力 3. 实战&#xff1a;Java API接口强势对接示例3.1 场景描述3.2 用户管理系统3.3 订单处理系统3.4 系统集成 4. 拓展&#xff1a;Java API接口在微服务架构中…

麒麟信安系统下的硬盘分区情况说明

目前飞腾平台上面麒麟信安系统分区情况如下&#xff1a; Tmpfs为内存文件系统&#xff0c;可以不考虑&#xff0c;真正使用的是两个分区 两个分区加起来为51G 查看cat /etc/fstab可以看到/data这个分区下包含了home opt root等常用文件夹 再加上这个分区容量只有17G&#xff0c…

基于Browscap对浏览器工具类优化

项目背景 原有的启动平台公共组件库comm-util的浏览器工具类BrowserUtils是基于UserAgentUtils的&#xff0c;但是该项目最后一个版本发布于 2018/01/24&#xff0c;之至今日23年底&#xff0c;已有5年没有维护更新&#xff0c;会造成最新版本的部分浏览器不能正确获取到浏览器…

嵌入式工程师校招经验与学习路线总结

前言&#xff1a;不知不觉2023年秋招已经结束&#xff0c;作者本人侥幸于秋招中斩获数十份大差不差的OFFER&#xff0c;包含&#xff1a;Top级的AIGC&#xff0c;工控龙头&#xff0c;国产MCU原厂&#xff0c;医疗器械&#xff0c;新能源车企等。总而言之&#xff0c;秋招总体情…

NR重写console.log 增加时间格式

如题&#xff0c;默认console.log输出的日志是13位的时间戳&#xff0c;然后不方便查查看与对比代码运行点的耗时&#xff0c;我们可以简单的重写 console.log方法&#xff0c;增加自定义时间戳格式&#xff0c;如下是增加时间&#xff08;时&#xff0c;分&#xff0c;秒&…

苍穹外卖+git开源

搁置了很久重新开始学 为了学习方便&#xff0c;苍穹外卖的前后端代码已放至git开源。前端源代码请看给i他-->sky-take-out: 苍穹外卖 git学习-->Git基础使用-CSDN博客 后端接口员工管理和分类管理模块 添加员工&#xff0c;添加的表单账号、手机号、身份证都…

netty07-粘包半包以及解决方案

粘包指的是发送方在发送数据时&#xff0c;多个数据包被合并成一个大的数据包发送到接收方&#xff0c;接收方在接收时无法准确地区分各个数据包的边界&#xff0c;从而导致数据粘在一起。 半包指的是发送方发送的数据包被拆分成了多个小的数据包&#xff0c;在接收方接收时&a…

使用VS Code远程开发MENJA小游戏并通过内网穿透分享本地游戏到公网

文章目录 前言1. 编写MENJA小游戏2. 安装cpolar内网穿透3. 配置MENJA小游戏公网访问地址4. 实现公网访问MENJA小游戏5. 固定MENJA小游戏公网地址 推荐一个人工智能学习网站 点击跳转学习 前言 本篇教程&#xff0c;我们将通过VS Code实现远程开发MENJA小游戏&#xff0c;并通…

11月榜单亮点:单场直播GMV超过5亿,30+达人粉丝增长100万人

11月&#xff0c;在双11好物节的加持下&#xff0c;品牌商家业绩再创新高。 数据报告显示&#xff0c;10月20日至11月11日&#xff0c;抖音商城GMV同比增长119%&#xff0c;直播间累计时长达到5827万小时&#xff0c;越来越多的用户正通过抖音参与双11购物狂潮&#xff0c;而越…

《opencv实用探索·十三》opencv之canny边缘检测

1、canny边缘检测应用场景 目标检测&#xff1a; Canny边缘检测可以用于检测图像中的目标边缘&#xff0c;从而帮助识别和定位物体。在目标检测的流程中&#xff0c;边缘通常是检测的第一步。 图像分割&#xff1a; Canny边缘检测可用于图像分割&#xff0c;即将图像划分为具有…

国外企业电子邮箱使用情况:推荐与优缺点分析

Zoho Mail是专门为国际邮箱设计的电子邮件服务&#xff0c;具有多语言支持、多域名支持、全球数据中心、安全隐私保护、大容量存储、邮件过滤和排序等优点。 一、多语言支持&#xff1a; Zoho Mail提供多种语言界面&#xff0c;包括英语、汉语、西班牙语、法语、德语等&#xf…

毕业设计全流程!

先看一眼时间线&#xff1a; 1

代码随想录算法训练营第三十七天|1049. 最后一块石头的重量 II ,494. 目标和,474.一和零

1049. 最后一块石头的重量 II - 力扣&#xff08;LeetCode&#xff09; 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&am…

Java常见算法和lambda

查找算法 public class day11 {public static void main(String[] args) {//基本查找 / 顺序差宅//核心://从0索引开始挨个往后查找//需求:定义一个方法利用基本查找 查询某个元素是否存在//数据如下:{131,127,147,81,103,23,7,79}int[] arr{131,127,147,81,103,23,7,79};int…