Pytest+Yaml+Excel 接口自动化测试框架的实现示例

news2024/11/27 16:28:13

一、框架架构

二、项目目录结构 

三、框架功能说明

解决痛点:

  • 通过session会话方式,解决了登录之后cookie关联处理
  • 框架天然支持接口动态传参、关联灵活处理
  • 支持Excel、Yaml文件格式编写接口用例,通过简单配置框架自动读取并执行
  • 执行环境一键切换,解决多环境相互影响问题
  • 支持http/https协议各种请求、传参类型接口
  • 响应数据格式支持json、str类型的提取操作
  • 断言方式支持等于、包含、大于、小于、不等于等方
  • 框架可以直接交给不懂代码的功能测试人员使用,只需要安装规范编写接口用例就行

框架使用说明:

  • 安装依赖包:pip install -r requirements.txt
  • 框架主入口为 run.py文件
  • 编写用例可以在Excel或者Yaml 文件里面,按照示例编写即可,也可以在test_case 目录下通过python脚本编写case
  • 断言或者提取参数都是通过jsonpath正则表达式提取数据
  • 用例执行时默认读取Exceltest_case 目录下用例

四、核心逻辑说明

工具类封装

assert_util.py 断言工具类封装

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

def assert_result(response: Response, expected: str) -> None:

    """ 断言方法

    :param response: 实际响应对象

    :param expected: 预期响应内容,从excel中或者yaml读取、或者手动传入

    return None

    """

    if expected is None:

        logging.info("当前用例无断言!")

        return

    if isinstance(expected, str):

        expect_dict = eval(expected)

    else:

        expect_dict = expected

    index = 0

    for k, v in expect_dict.items():

        # 获取需要断言的实际结果部分

        for _k, _v in v.items():

            if _k == "http_code":

                actual = response.status_code

            else:

                if response_type(response) == "json":

                    actual = json_extractor(response.json(), _k)

                else:

                    actual = re_extract(response.text, _k)

            index += 1

            logging.info(f'第{index}个断言数据,实际结果:{actual} | 预期结果:{_v} 断言方式:{k}')

            allure_step(f'第{index}个断言数据', f'实际结果:{actual} = 预期结果:{v}')

            try:

                if k == "eq"# 相等

                    assert actual == _v

                elif k == "in"# 包含关系

                    assert _v in actual

                elif k == "gt"# 判断大于,值应该为数值型

                    assert actual > _v

                elif k == "lt"# 判断小于,值应该为数值型

                    assert actual < _v

                elif k == "not"# 不等于,非

                    assert actual != _v

                else:

                    logging.exception(f"判断关键字: {k} 错误!")

            except AssertionError:

                raise AssertionError(f'第{index}个断言失败 -|- 断言方式:{k} 实际结果:{actual} || 预期结果: {_v}')

case_handle.py Case数据读取工具类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

def get_case_data():

   case_type = ReadYaml(config_path + "config.yaml").read_yaml["case"]

   if case_type == CaseType.EXCEL.value:

       cases = []

       for file in [excel for excel in os.listdir(data_path) if os.path.splitext(excel)[1] == ".xlsx"]:

           data = ReadExcel(data_path + file).read_excel()

           name = os.path.splitext(file)[0]

           class_name = name.split("_")[0].title() + name.split("_")[1].title()

           gen_case(name, data, class_name)

           cases.extend(data)

       return cases

   elif case_type == CaseType.YAML.value:

       cases = []

       for yaml_file in [yaml for yaml in os.listdir(data_path) if

                         os.path.splitext(yaml)[1] in [".yaml", "yml"]]:

           data = ReadYaml(data_path + yaml_file).read_yaml

           name = os.path.splitext(yaml_file)[0]

           class_name = name.split("_")[0].title() + name.split("_")[1].title()

           gen_case(name, data, class_name)

           cases.extend(data)

       return cases

   else:

       cases = []

       for file in [excel for excel in os.listdir(data_path) if

                    os.path.splitext(excel)[1] in [".yaml", "yml", ".xlsx"]]:

           if os.path.splitext(file)[1] == ".xlsx":

               data = ReadExcel(data_path + file).read_excel()

               name = os.path.splitext(file)[0]

               cases.extend(data)

           else:

               data = ReadYaml(data_path + file).read_yaml

               name = os.path.splitext(file)[0]

               cases.extend(data)

           class_name = name.split("_")[0].title() + name.split("_")[1].title()

           gen_case(name, data, class_name)

       return cases

excel_handle.py 读取Excel工具类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

def get_case_data():

   case_type = ReadYaml(config_path + "config.yaml").read_yaml["case"]

   if case_type == CaseType.EXCEL.value:

       cases = []

       for file in [excel for excel in os.listdir(data_path) if os.path.splitext(excel)[1] == ".xlsx"]:

           data = ReadExcel(data_path + file).read_excel()

           name = os.path.splitext(file)[0]

           class_name = name.split("_")[0].title() + name.split("_")[1].title()

           gen_case(name, data, class_name)

           cases.extend(data)

       return cases

   elif case_type == CaseType.YAML.value:

       cases = []

       for yaml_file in [yaml for yaml in os.listdir(data_path) if

                         os.path.splitext(yaml)[1] in [".yaml", "yml"]]:

           data = ReadYaml(data_path + yaml_file).read_yaml

           name = os.path.splitext(yaml_file)[0]

           class_name = name.split("_")[0].title() + name.split("_")[1].title()

           gen_case(name, data, class_name)

           cases.extend(data)

       return cases

   else:

       cases = []

       for file in [excel for excel in os.listdir(data_path) if

                    os.path.splitext(excel)[1] in [".yaml", "yml", ".xlsx"]]:

           if os.path.splitext(file)[1] == ".xlsx":

               data = ReadExcel(data_path + file).read_excel()

               name = os.path.splitext(file)[0]

               cases.extend(data)

           else:

               data = ReadYaml(data_path + file).read_yaml

               name = os.path.splitext(file)[0]

               cases.extend(data)

           class_name = name.split("_")[0].title() + name.split("_")[1].title()

           gen_case(name, data, class_name)

       return cases

yaml_handle.py 读取Yaml文件的工具类

1

2

3

4

5

6

7

8

9

10

class ReadYaml:

    def __init__(self, filename):

        self.filename = filename

    @property

    def read_yaml(self) -> object:

        with open(file=self.filename, mode="r", encoding="utf-8") as fp:

            case_data = yaml.safe_load(fp.read())

        return case_data

配置文件

config.yaml 配置信息

1

2

3

4

# 服务器器地址

host: http://localhost:8091/

case: 1 # 0代表执行Excel和yaml两种格式的用例, 1 代表Excel用例,2 代表 yaml文件用例

输出目录

日志输出目录

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

import logging

import time

import os

def get_log(logger_name):

    """

    :param logger_name: 日志名称

    :return: 返回logger handle

    """

    # 创建一个logger

    logger = logging.getLogger(logger_name)

    logger.setLevel(logging.INFO)

    # 获取本地时间,转换为设置的格式

    rq = time.strftime('%Y%m%d', time.localtime(time.time()))

    # 设置所有日志和错误日志的存放路径

    path = os.path.dirname(os.path.abspath(__file__))

    all_log_path = os.path.join(path, 'interface_logs\\All_Logs\\')

    if not os.path.exists(all_log_path):

        os.makedirs(all_log_path)

    error_log_path = os.path.join(path, 'interface_logs\\Error_Logs\\')

    if not os.path.exists(error_log_path):

        os.makedirs(error_log_path)

    # 设置日志文件名

    all_log_name = all_log_path + rq + '.log'

    error_log_name = error_log_path + rq + '.log'

    if not logger.handlers:

        # 创建一个handler写入所有日志

        fh = logging.FileHandler(all_log_name, encoding='utf-8')

        fh.setLevel(logging.INFO)

        # 创建一个handler写入错误日志

        eh = logging.FileHandler(error_log_name, encoding='utf-8')

        eh.setLevel(logging.ERROR)

        # 创建一个handler输出到控制台

        ch = logging.StreamHandler()

        ch.setLevel(logging.ERROR)

        # 以时间-日志器名称-日志级别-文件名-函数行号-错误内容

        all_log_formatter = logging.Formatter(

            '[%(asctime)s] %(filename)s - %(levelname)s - %(lineno)s - %(message)s')

        # 以时间-日志器名称-日志级别-文件名-函数行号-错误内容

        error_log_formatter = logging.Formatter(

            '[%(asctime)s] %(filename)s - %(levelname)s - %(lineno)s - %(message)s')

        # 将定义好的输出形式添加到handler

        fh.setFormatter(all_log_formatter)

        ch.setFormatter(all_log_formatter)

        eh.setFormatter(error_log_formatter)

        # 给logger添加handler

        logger.addHandler(fh)

        logger.addHandler(eh)

        logger.addHandler(ch)

    return logger

报告目录

执行case后自动生成,执行之前自动删除

allure 数据目录

执行case后自动生成,执行之前自动删除

请求工具类

base_request.py 请求封装工具类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

class BaseRequest:

    session = None

    @classmethod

    def get_session(cls):

        if cls.session is None:

            cls.session = requests.Session()

        return cls.session

    @classmethod

    def send_request(cls, case: dict) -> Response:

        """

        处理case数据,转换成可用数据发送请求

        :param case: 读取出来的每一行用例内容

        return: 响应对象

        """

        log.info("开始执行用例: {}".format(case.get("title")))

        req_data = RequestPreDataHandle(case).to_request_data

        res = cls.send_api(

            url=req_data["url"],

            method=req_data["method"],

            pk=req_data["pk"],

            header=req_data.get("header", None),

            data=req_data.get("data", None),

            file=req_data.get("file", None)

        )

        allure_step('请求响应数据', res.text)

        after_extract(res, req_data.get("extract", None))

        return res

    @classmethod

    def send_api(cls, url, method, pk, header=None, data=None, file=None) -> Response:

        """

        :param method: 请求方法

        :param url: 请求url

        :param pk: 入参关键字, params(查询参数类型,明文传输,一般在url?参数名=参数值), data(一般用于form表单类型参数)

        json(一般用于json类型请求参数)

        :param data: 参数数据,默认等于None

        :param file: 文件对象

        :param header: 请求头

        :return: 返回res对象

        """

        session = cls.get_session()

        pk = pk.lower()

        if pk == 'params':

            res = session.request(method=method, url=url, params=data, headers=header)

        elif pk == 'data':

            res = session.request(method=method, url=url, data=data, files=file, headers=header)

        elif pk == 'json':

            res = session.request(method=method, url=url, json=data, files=file, headers=header)

        else:

            raise ValueError('pk可选关键字为params, json, data')

        return res

pre_handle_utils.py 请求前置处理工具类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

def pre_expr_handle(content) -> object:

    """

    :param content: 原始的字符串内容

    return content: 替换表达式后的字符串

    """

    if content is None:

        return None

    if len(content) != 0:

        log.info(f"开始进行字符串替换: 替换字符串为:{content}")

        content = Template(str(content)).safe_substitute(GLOBAL_VARS)

        for func in re.findall('\\${(.*?)}', content):

            try:

                content = content.replace('${%s}' % func, exec_func(func))

            except Exception as e:

                log.exception(e)

        log.info(f"字符串替换完成: 替换字符串后为:{content}")

        return content

after_handle_utils.py 后置操作处理工具类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

def after_extract(response: Response, exp: str) -> None:

    """

    :param response: request 响应对象

    :param exp: 需要提取的参数字典 '{"k1": "$.data"}' 或 '{"k1": "data:(.*?)$"}'

    :return:

    """

    if exp:

        if response_type(response) == "json":

            res = response.json()

            for k, v in exp.items():

                GLOBAL_VARS[k] = json_extractor(res, v)

        else:

            res = response.text

            for k, v in exp.items():

                GLOBAL_VARS[k] = re_extract(res, v)

代码编写case

test_demo.py 用例文件示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

@allure.feature("登录")

class TestLogin:

    @allure.story("正常登录成功")

    @allure.severity(allure.severity_level.BLOCKER)

    def test_login(self):

        allure_title("正常登录")

        data = {

            "url": "api/login",

            "method": "post",

            "pk": "data",

            "data": {"userName": "king", "pwd": 123456}

        }

        expected = {

            "$.msg": "登录成功!"

        }

        # 发送请求

        response = BaseRequest.send_request(data)

        # 断言操作

        assert_result(response, expected)

程序主入口

run.py 主入口执行文件

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

def run():

    # 生成case在执行

    if os.path.exists(auto_gen_case_path):

        shutil.rmtree(auto_gen_case_path)

    get_case_data()

    if os.path.exists('outputs/reports/'):

        shutil.rmtree(path='outputs/reports/')

    # 本地调式执行

    pytest.main(args=['-s', '--alluredir=outputs/reports'])

    # 自动以服务形式打开报告

    # os.system('allure serve outputs/reports')

    # 本地生成报告

    os.system('allure generate outputs/reports -o outputs/html --clean')

    shutil.rmtree(auto_gen_case_path)

if __name__ == '__main__':

    run()

执行记录

allure 报告

 

日志记录

[2022-01-11 22:36:04,164] base_request.py - INFO - 42 - 开始执行用例: 正常登录
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 37 - 开始进行字符串替换: 替换字符串为:bank/api/login
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 44 - 字符串替换完成: 替换字符串后为:bank/api/login
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 68 - 处理请求前url:bank/api/login
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 78 - 处理请求后 url:http://localhost:8091/bank/api/login
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 90 - 处理请求前Data: {'password': '123456', 'userName': 'king'}
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 37 - 开始进行字符串替换: 替换字符串为:{'password': '123456', 'userName': 'king'}
[2022-01-11 22:36:04,166] pre_handle_utils.py - INFO - 44 - 字符串替换完成: 替换字符串后为:{'password': '123456', 'userName': 'king'}
[2022-01-11 22:36:04,166] pre_handle_utils.py - INFO - 92 - 处理请求后Data: {'password': '123456', 'userName': 'king'}
[2022-01-11 22:36:04,166] pre_handle_utils.py - INFO - 100 - 处理请求前files: None
[2022-01-11 22:36:04,175] base_request.py - INFO - 53 - 请求响应数据{"code":"0","message":"success","data":null}
[2022-01-11 22:36:04,176] data_handle.py - INFO - 29 - 提取响应内容成功,提取表达式为: $.code 提取值为 0
[2022-01-11 22:36:04,176] assert_util.py - INFO - 49 - 第1个断言数据,实际结果:0 | 预期结果:0 断言方式:eq
[2022-01-11 22:36:04,176] data_handle.py - INFO - 29 - 提取响应内容成功,提取表达式为: $.message 提取值为 success
[2022-01-11 22:36:04,176] assert_util.py - INFO - 49 - 第2个断言数据,实际结果:success | 预期结果:success 断言方式:eq
​现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。
如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受
可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛
分享他们的经验,还会分享很多直播讲座和技术沙龙
可以免费学习!划重点!开源的!!!
qq群号:485187702【暗号:csdn11】

最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走! 希望能帮助到你!【100%无套路免费领取】

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

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

相关文章

python3.5安装教程及环境配置,python3.7.2安装与配置

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;python3.5安装教程及环境配置&#xff0c;python3.7.2安装与配置&#xff0c;现在让我们一起来看看吧&#xff01; python 从爬虫开始&#xff08;一&#xff09; Python 简介 首先简介一下Python和爬虫的关系与概念&am…

深度学习实战65-人脸检测模型LFFD的搭建,LFFD模型的架构与原理的详细介绍

大家好,我是微学AI,今天给大家介绍一下深度学习实战65-人脸检测模型LFFD的搭建,LFFD模型的架构与原理的详细介绍。LFFD(Light and Fast Face Detector)模型是一种用于人脸检测的深度学习模型,其设计旨在实现轻量级和快速的人脸检测。本文将详细介绍LFFD模型的定义、优点、原…

类人智能体概念、能力与衍生丨AI Agents闭门研讨观点集锦

导读 在智源社区举办的「青源Workshop第27期&#xff1a;AI Agents 闭门研讨会」上&#xff0c;来自英伟达的高级应用科学家王智琳、CAMEL一作李国豪、AutoAgents一作陈光耀&#xff0c;以及相关技术专家们共同参与交流讨论&#xff0c;分享了最新的研究成果&#xff0c;共同探…

人工麝香市场分析:中国市场年需求量超过15吨

人工麝香作为濒危动物药材麝香的替代品&#xff0c;等同天然麝香配方使用。 是国家重大科研成果和保密品种&#xff0c;用人工麝香生产中成药品种近400种&#xff0c;涵盖中成药常用剂型。 是珍稀动物药材代用品研究的重大突破&#xff0c;为其它珍稀动物药材的应用开辟了一条重…

金融量化交易:使用Python实现遗传算法

大家好&#xff0c;遗传算法是一种受自然选择过程启发的进化算法&#xff0c;用于寻找优化和搜索问题的近似解决方案。本文将使用Python来实现一个用于优化简单交易策略的遗传算法。 1.遗传算法简介 遗传算法是一类基于自然选择和遗传学原理的优化算法&#xff0c;其特别适用…

【方法】Excel表格的“限制保护”不想要了,如何取消?

我们知道&#xff0c;Excel表格可以设置“限制保护”&#xff0c;保护文件不被随意更改&#xff0c;那如果后续不需要保护了&#xff0c;如何取消呢&#xff1f; 下面小编来说说Excel表格常用的三种“保护”&#xff0c;是如何取消的。 第一种&#xff0c;Excel表格的工作表或…

第15章:随堂复习与企业真题(File类与IO流)

第15章&#xff1a;随堂复习与企业真题&#xff08;File类与IO流&#xff09; 一、随堂复习 1. File类的使用 File类的一个实例对应着磁盘上的一个文件或文件目录。 ----> “万事万物皆对象”&#xff08;熟悉&#xff09;File的实例化、常用的方法File类中只有新建、删除…

Unity 自定义窗口

放在Editor文件夹下&#xff1b; #if UNITY_EDITORusing System; using UnityEditor; using UnityEngine;namespace EditorCustumTool {/// <summary>/// 自定义窗口/// </summary>public class CustomWindow : EditorWindow{public enum FlagType{Flag1 101,Fl…

Qt内存管理、UI编辑器、客制化组件、弹出对话框、常用部件类

头文件的小技巧 #include <QtWidgets> // 在自动生成的 .h 里面加上此句 适用条件&#xff1a; QT 的内存管理 当父窗体被关闭时&#xff0c;子部件的内存会自动释放。 对象树是一种管理对象生命周期的机制。当一个对象被添加到另一个对象的子对象列表中时&#xff0…

Springboot+AOP+注解实现字段AES+Base64加解密

AOP实现AESBASE64加解密 场景如下&#xff1a; 需要对数据库存储的字段&#xff0c;进行加解密的处理。如果都直接写代码的话&#xff0c;那么代码回冗余很多&#xff0c;所以使用AOP注解去实现。让代码简洁&#xff0c;方便 具体实现如下&#xff1a; 1、依赖 <depende…

C语言搭建项目-学生管理系统(非链表)

、 目录 搭建offer.h文件 搭建offer.c中的main函数 密码登入系统 搭建my_oferr.c中的接口函数 使用帮助菜单接口函数 增加学生信息接口函数 查询学生信息接口函数 删除学生信息接口函数 保存学生信息接口 打开文件fopen 关闭文件fclose 判断是否保存文件fwrite 退出执行文件…

TCP传输层详解(计算机网络复习)

介绍&#xff1a;TCP/IP包含了一系列的协议&#xff0c;也叫TCP/IP协议族&#xff0c;简称TCP/IP。该协议族提供了点对点的连接机制&#xff0c;并将传输数据帧的封装、寻址、传输、路由以及接收方式都予以标准化 TCP/IP的分层模型 在讲TCP/IP协议之前&#xff0c;首先介绍一…

【MATLAB】tvfEMD信号分解+FFT+HHT组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 TVFEMDFFTHHT组合算法是一种结合了总体变分模态分解&#xff08;TVFEMD&#xff09;、傅里叶变换&#xff08;FFT&#xff09;和希尔伯特-黄变换&#xff08;HHT&#xff09;的信号分解方…

配置Smart Link负载分担示例

Smart Link和Monitor Link简介 定义 Smart Link&#xff0c;又叫做备份链路。一个Smart Link由两个接口组成&#xff0c;其中一个接口作为另一个的备份。Smart Link常用于双上行组网&#xff0c;提供可靠高效的备份和快速的切换机制。 Monitor Link是一种接口联动方案&#…

<习题集><LeetCode><链表><2/19/21/23/24>

目录 2. 两数相加 19. 删除链表的倒数第 N 个结点 21. 合并两个有序链表 23. 合并 K 个升序链表 24. 两两交换链表中的节点 2. 两数相加 https://leetcode.cn/problems/add-two-numbers/ public ListNode addTwoNumbers(ListNode l1, ListNode l2) {//head是cur链表头节点…

拼多多选品大作战:通过热词选利润赛道

相信很多人都听过一句话&#xff1a;找对了风口&#xff0c;猪都能飞起来。 我们电商人也应如此&#xff0c;从行业分析到选品都应快速跟上市场节奏。 今天就给大家分享一下如何通过热词来进行一个行业类目的分析与选择。 01 热词是什么 热词通常指的是热搜词和飙升词&#…

大华DSS S2-045 OGNL表达式注入漏洞复现

0x01 产品简介 大华DSS安防监控系统平台是一款集视频、报警、存储、管理于一体的综合安防解决方案。该平台支持多种接入方式,包括网络视频、模拟视频、数字视频、IP电话、对讲机等。此外,该平台还支持多种报警方式,包括移动侦测、区域入侵、越线报警、人员聚集等。 0x02 漏…

天津大数据培训机构品牌 数据分析师的发展方向

大数据专业还是有一定难度的&#xff0c;毕竟大数据开发技术所包含的编程技术知识是比较杂且多的如果是计算机专业的学生或者自身有一定基础的人学&#xff0c;相对来说会比较容易&#xff0c;但对于零基础小伙伴学习来说&#xff0c;想要学习大数据&#xff0c;难度还是很高的…

C# 编程新手必看,一站式学习网站,让你轻松掌握 C# 技能!

介绍&#xff1a;实际上&#xff0c;您可能弄错了&#xff0c;C#并不是一种独立的编程语言&#xff0c;而是一种由微软公司开发的面向对象的、运行于.NET Framework之上的高级程序设计语言。C#看起来与Java十分相似&#xff0c;但两者并不兼容。 C#的设计目标是简单、强大、类型…

时间序列预测 — VMD-LSTM实现单变量多步光伏预测(Tensorflow):单变量转为多变量

目录 1 数据处理 1.1 导入库文件 1.2 导入数据集 1.3 缺失值分析 2 VMD经验模态分解 3 构造训练数据 4 LSTM模型训练 5 预测 1 数据处理 1.1 导入库文件 import time import datetime import pandas as pd import numpy as np import matplotlib.pyplot as plt f…