Flutter 中的单元测试:从工作流基础到复杂场景

news2024/11/29 0:43:11

对 Flutter 的兴趣空前高涨——而且早就应该出现了。 Google 的开源 SDK 与 Android、iOS、macOS、Web、Windows 和 Linux 兼容。单个 Flutter 代码库支持所有这些。单元测试有助于交付一致且可靠的 Flutter 应用程序,通过在组装之前先发制人地提高代码质量来确保不会出现错误、缺陷和缺陷。

在本教程中,分享了 Flutter 单元测试的工作流程优化,演示了基本的 Flutter 单元测试,然后转向更复杂的 Flutter 测试用例和库。

Flutter单元测试的流程

在 Flutter 实现单元测试的方式与在其他技术栈中的方式大致相同:

1.评估代码

2.设置模拟数据

3.定义测试组

4.为每个测试组定义测试函数签名

5.写测试用例

为了演示单元测试,我准备了一个示例 Flutter 项目。该项目使用外部 API 来获取和显示可以按国家过滤的大学列表。

关于 Flutter 工作原理的一些注意事项: 该框架通过在创建项目时自动加载 flutter_test库来促进测试。该库使 Flutter 能够读取、运行和分析单元测试。Flutter 还会自动创建用于存储测试的test文件夹。避免重命名和/或移动test文件夹至关重要,因为这会破坏其功能,从而破坏运行测试的能力。在测试文件名中包含 _test.dart也很重要,因为这个后缀是 Flutter 识别测试文件的方式。

测试目录结构

为了在项目中进行单元测试,使用干净的架构实现了 MVVM和依赖注入 (DI) ,正如为源代码子文件夹选择的名称所证明的那样。MVVM 和 DI 原则的结合确保了关注点分离:

1.每个项目类都支持一个目标。

2.类中的每个函数只完成它自己的范围。

给编写的测试文件创建一个有组织的存储空间,在这个系统中,测试组将具有易于识别的“家”。鉴于 Flutter 要求在测试文件夹中定位测试,我们将test目录下test文件组织成和源码相同的结构。然后,编写测试时,将其存储在适当的子文件夹中:就像干净的袜子放在梳妆台的袜子抽屉里,折叠的衬衫放在衬衫抽屉里一样,Model类的单元测试放在名为 model 的文件夹中 , 例如。

图片

项目的测试文件夹结构反映了源代码结构,采用此文件系统可以使项目透明化,并为团队提供一种简单的方法来查看代码的哪些部分具有相关测试。现在准备将单元测试付诸实践。

一个简单的 Flutter 单元测试

现在将从model类(在源代码的data层中)开始,并将示例限制为仅包含一个model类 ApiUniversityModel。此类拥有两个功能:

●通过使用 Map模拟 JSON 对象来初始化模型。
●构建University数据模型。

为了测试模型的每个功能,这里自定义一下前面描述的通用步骤:

1.评估代码

2.设置数据模拟:将定义服务器对 API 调用的响应

3.定义测试组:将有两个测试组,每个功能一个

4.为每个测试组定义测试函数签名

5.编写测试用例

评估我们的代码后,我们准备实现第二个目标:设置特定于ApiUniversityModel类中的两个函数的数据模拟。
为了模拟第一个函数(通过使用 Map模拟 JSON 来初始化模型)fromJson,创建两个 Map 对象来模拟函数的输入数据。再创建两个等效的 ApiUniversityModel 对象,以表示具有所提供输入的函数的预期结果。
为了模拟第二个函数(构建University数据模型)toDomain,创建两个University对象,这是在先前实例化的ApiUniversityModel 对象中运行此函数后的预期结果:

void main() {

    Map<String, dynamic> apiUniversityOneAsJson = {

        "alpha_two_code": "US",

        "domains": ["marywood.edu"],

        "country": "United States",

        "state-province": null,

        "web_pages": ["http://www.marywood.edu"],

        "name": "Marywood University"

    };

    ApiUniversityModel expectedApiUniversityOne = ApiUniversityModel(

        alphaCode: "US",

        country: "United States",

        state: null,

        name: "Marywood University",

        websites: ["http://www.marywood.edu"],

        domains: ["marywood.edu"],

    );

    University expectedUniversityOne = University(

        alphaCode: "US",

        country: "United States",

        state: "",

        name: "Marywood University",

        websites: ["http://www.marywood.edu"],

        domains: ["marywood.edu"],

    );


    Map<String, dynamic> apiUniversityTwoAsJson = {

        "alpha_two_code": "US",

        "domains": ["lindenwood.edu"],

        "country": "United States",

        "state-province":"MJ",

        "web_pages": null,

        "name": "Lindenwood University"

    };

    ApiUniversityModel expectedApiUniversityTwo = ApiUniversityModel(

        alphaCode: "US",

        country: "United States",

        state:"MJ",

        name: "Lindenwood University",

        websites: null,

        domains: ["lindenwood.edu"],

    );

    University expectedUniversityTwo = University(

        alphaCode: "US",

        country: "United States",

        state: "MJ",

        name: "Lindenwood University",

        websites: [],

        domains: ["lindenwood.edu"],

    );

}

接下来,第三个和第四个目标,将添加描述性语言来定义测试组和测试函数签名:

   void main() {

    // Previous declarations

        group("Test ApiUniversityModel initialization from JSON", () {

            test('Test using json one', () {});

            test('Test using json two', () {});

        });

        group("Test ApiUniversityModel toDomain", () {

            test('Test toDomain using json one', () {});

            test('Test toDomain using json two', () {});

        });

}

现在定义了两个测试的签名来检查 fromJson 函数,两个测试来检查 toDomain函数。
为了实现第五个目标并编写测试,将使用 flutter_test库的 expect 方法将函数的结果与预期进行比较:

void main() {

    // Previous declarations

        group("Test ApiUniversityModel initialization from json", () {

            test('Test using json one', () {

                expect(ApiUniversityModel.fromJson(apiUniversityOneAsJson),

                    expectedApiUniversityOne);

            });

            test('Test using json two', () {

                expect(ApiUniversityModel.fromJson(apiUniversityTwoAsJson),

                    expectedApiUniversityTwo);

            });

        });


        group("Test ApiUniversityModel toDomain", () {

            test('Test toDomain using json one', () {

                expect(ApiUniversityModel.fromJson(apiUniversityOneAsJson).toDomain(),

                    expectedUniversityOne);

            });

            test('Test toDomain using json two', () {

                expect(ApiUniversityModel.fromJson(apiUniversityTwoAsJson).toDomain(),

                    expectedUniversityTwo);

            });

        });

}

完成五个目标后,现在可以从 IDE 或命令行运行测试。

图片

在终端,可以通过输入 flutter test 命令来运行test文件夹中包含的所有测试,并查看测试是否通过。或者,可以通过输入 flutter test --plain-name "ReplaceWithName"命令来运行单个测试或测试组,用测试或测试组的名称替换 ReplaceWithName。

在 Flutter 中对端点进行单元测试

完成了一个没有依赖项的简单测试后,让我们探索一个更有趣的示例:将测试endpoint类,其范围包括:

●执行对服务器的 API 调用。
●将 API JSON 响应转换为不同的格式。
在评估了代码之后,将使用 flutter_test库的 setUp方法来初始化测试组中的类:

group("Test University Endpoint API calls", () {

    setUp(() {

        baseUrl = "https://test.url";

        dioClient = Dio(BaseOptions());

        endpoint = UniversityEndpoint(dioClient, baseUrl: baseUrl);

    });

}

要向 API 发出网络请求,更喜欢使用改造库,它会生成大部分必要的代码。 为了正确测试 UniversityEndpoint类,将强制 dio 库(Retrofit 用于执行 API 调用)通过自定义响应适配器模拟 Dio 类的行为来返回所需的结果。

自定义网络拦截器

由于通过 DI 构建了UniversityEndpoint类,因此可以进行自定义网络拦截器。 (如果 UniversityEndpoint 类自己初始化一个 Dio 类,就没有办法模拟类的行为。)
为了模拟Dio类的行为,需要知道 Retrofit库中使用的 Dio方法—— 但无法直接访问 Dio。 因此,将使用自定义网络响应拦截器模拟 Dio:

class DioMockResponsesAdapter extends HttpClientAdapter {

  final MockAdapterInterceptor interceptor;


  DioMockResponsesAdapter(this.interceptor);


  @override

  void close({bool force = false}) {}


  @override

  Future<ResponseBody> fetch(RequestOptions options,

      Stream<Uint8List>? requestStream, Future? cancelFuture) {

    if (options.method == interceptor.type.name.toUpperCase() &&

        options.baseUrl == interceptor.uri &&

        options.queryParameters.hasSameElementsAs(interceptor.query) &&

        options.path == interceptor.path) {

      return Future.value(ResponseBody.fromString(

        jsonEncode(interceptor.serializableResponse),

        interceptor.responseCode,

        headers: {

          "content-type": ["application/json"]

        },

      ));

    }

    return Future.value(ResponseBody.fromString(

        jsonEncode(

              {"error": "Request doesn't match the mock interceptor details!"}),

        -1,

        statusMessage: "Request doesn't match the mock interceptor details!"));

  }

}


enum RequestType { GET, POST, PUT, PATCH, DELETE }


class MockAdapterInterceptor {

  final RequestType type;

  final String uri;

  final String path;

  final Map<String, dynamic> query;

  final Object serializableResponse;

  final int responseCode;


  MockAdapterInterceptor(this.type, this.uri, this.path, this.query,

      this.serializableResponse, this.responseCode);

}

现在已经创建了拦截器来模拟网络响应,接下来可以定义测试组和测试函数签名。在例子中,只有一个函数要测试 (getUniversitiesByCountry),因此将只创建一个测试组。现测试函数对三种情况的响应:

1.Dio类的函数是否真的被 getUniversitiesByCountry 调用了?

2.如果API 请求返回错误,会发生什么?

3.如果 API 请求返回预期结果,会发生什么?

这是测试组和测试函数签名:

 group("Test University Endpoint API calls", () {


    test('Test endpoint calls dio', () async {});


    test('Test endpoint returns error', () async {});


    test('Test endpoint calls and returns 2 valid universities', () async {});

  });

现在准备好编写测试用例了。对于每个测试用例,要创建一个具有相应配置的 DioMockResponsesAdapter 实例:

group("Test University Endpoint API calls", () {

    setUp(() {

        baseUrl = "https://test.url";

        dioClient = Dio(BaseOptions());

        endpoint = UniversityEndpoint(dioClient, baseUrl: baseUrl);

    });


    test('Test endpoint calls dio', () async {

        dioClient.httpClientAdapter = _createMockAdapterForSearchRequest(

            200,

            [],

        );

        var result = await endpoint.getUniversitiesByCountry("us");

        expect(result, <ApiUniversityModel>[]);

    });


    test('Test endpoint returns error', () async {

        dioClient.httpClientAdapter = _createMockAdapterForSearchRequest(

            404,

            {"error": "Not found!"},

        );

        List<ApiUniversityModel>? response;

        DioError? error;

        try {

            response = await endpoint.getUniversitiesByCountry("us");

        } on DioError catch (dioError, _) {

            error = dioError;

        }

        expect(response, null);

        expect(error?.error, "Http status error [404]");

    });


    test('Test endpoint calls and returns 2 valid universities', () async {

        dioClient.httpClientAdapter = _createMockAdapterForSearchRequest(

            200,

            generateTwoValidUniversities(),

        );

        var result = await endpoint.getUniversitiesByCountry("us");

        expect(result, expectedTwoValidUniversities());

    });

});

现在端点测试已经完成,开始测试数据源类 UniversityRemoteDataSource。早些时候,可以看到UniversityEndpoint类是构造函数UniversityRemoteDataSource({UniversityEndpoint? universityEndpoint}) 的一部分,这表明 UniversityRemoteDataSource使用 UniversityEndpoint 类来实现其范围,因此这是将模拟的类。

使用 Mockito 进行模拟

在之前的示例中,使用自定义 NetworkInterceptor 手动模拟了 Dio 客户端的请求适配器。手动执行此操作(模拟类及其函数)将非常耗时。 幸运的是,模拟库旨在处理此类情况,并且可以毫不费力地生成模拟类。 使用 mockito 库,这是 Flutter 中用于模拟的行业标准库。为了通过 Mockito 进行模拟,
首先在测试代码之前添加注释“@GenerateMocks([class_1,class_2,…])”——就在void main() {}函数之上。 在注释中,将包含一个类名列表作为参数(代替 class_1、class_2…)。
接下来,运行 Flutter 的flutter pub run build_runner构建命令,在与测试相同的目录中为我们的模拟类生成代码。 生成的模拟文件的名称将是测试文件名加上.mocks.dart的组合,替换测试的 .dart后缀。
该文件的内容将包括名称以前缀 Mock开头的模拟类。 例如,UniversityEndpoint 变为 MockUniversityEndpoint。
现在,将 university_remote_data_source_test.dart.mocks.dart(模拟文件)导入 university_remote_data_source_test.dart(测试文件)。
然后,在 setUp 函数中,通过使用 MockUniversityEndpoint并初始化 UniversityRemoteDataSource类来模拟 UniversityEndpoint:

import 'university_remote_data_source_test.mocks.dart';


@GenerateMocks([UniversityEndpoint])

void main() {

    late UniversityEndpoint endpoint;

    late UniversityRemoteDataSource dataSource;

    group("Test function calls", () {

        setUp(() {

            endpoint = MockUniversityEndpoint();

            dataSource = UniversityRemoteDataSource(universityEndpoint: endpoint);

        });

}

成功模拟了UniversityEndpoint,然后初始化了UniversityRemoteDataSource 类。 现在准备好定义测试组和测试函数签名:

group("Test function calls", () {


  test('Test dataSource calls getUniversitiesByCountry from endpoint', () {});


  test('Test dataSource maps getUniversitiesByCountry response to Stream', () {});


  test('Test dataSource maps getUniversitiesByCountry response to Stream with error', () {});

});

这样,模拟、测试组和测试函数签名就设置好了。 已准备好编写实际测试。
第一个测试检查当数据源启动国家信息获取时是否调用了 UniversityEndpoint 函数。 首先定义每个类在调用其函数时将如何反应。 由于模拟了 UniversityEndpoint类,这就是将使用的类,使用 when(function_that_will_be_called).then(what_will_be_returned)代码结构。
正在测试的函数是异步的(返回 Future 对象的函数),因此使用when(function name).thenanswer( () {modified function result} )代码结构来修改结果。要检查 getUniversitiesByCountry 函数是否调用了 UniversityEndpoint类中的 getUniversitiesByCountry 函数,使用 when(…).thenAnswer( () {…} )来模拟 UniversityEndpoint 类中的 getUniversitiesByCountry 函数:

when(endpoint.getUniversitiesByCountry("test"))

    .thenAnswer((realInvocation) => Future.value(<ApiUniversityModel>[]));

现在已经模拟了响应,调用数据源函数并使用验证函数检查是否调用了UniversityEndpoint函数:

test('Test dataSource calls getUniversitiesByCountry from endpoint', () {

    when(endpoint.getUniversitiesByCountry("test"))

        .thenAnswer((realInvocation) => Future.value(<ApiUniversityModel>[]));

    dataSource.getUniversitiesByCountry("test");

    verify(endpoint.getUniversitiesByCountry("test"));

});

可以使用相同的原则来编写额外的测试来检查函数是否正确地将端点结果转换为相关的数据流:

import 'university_remote_data_source_test.mocks.dart';


@GenerateMocks([UniversityEndpoint])

void main() {

    late UniversityEndpoint endpoint;

    late UniversityRemoteDataSource dataSource;


    group("Test function calls", () {

        setUp(() {

            endpoint = MockUniversityEndpoint();

            dataSource = UniversityRemoteDataSource(universityEndpoint: endpoint);

        });


        test('Test dataSource calls getUniversitiesByCountry from endpoint', () {

            when(endpoint.getUniversitiesByCountry("test"))

                    .thenAnswer((realInvocation) => Future.value(<ApiUniversityModel>[]));


            dataSource.getUniversitiesByCountry("test");

            verify(endpoint.getUniversitiesByCountry("test"));

        });


        test('Test dataSource maps getUniversitiesByCountry response to Stream',

                () {

            when(endpoint.getUniversitiesByCountry("test"))

                    .thenAnswer((realInvocation) => Future.value(<ApiUniversityModel>[]));


            expect(

                dataSource.getUniversitiesByCountry("test"),

                emitsInOrder([

                    const AppResult<List<University>>.loading(),

                    const AppResult<List<University>>.data([])

                ]),

            );

        });


        test(

                'Test dataSource maps getUniversitiesByCountry response to Stream with error',

                () {

            ApiError mockApiError = ApiError(

                statusCode: 400,

                message: "error",

                errors: null,

            );

            when(endpoint.getUniversitiesByCountry("test"))

                    .thenAnswer((realInvocation) => Future.error(mockApiError));


            expect(

                dataSource.getUniversitiesByCountry("test"),

                emitsInOrder([

                    const AppResult<List<University>>.loading(),

                    AppResult<List<University>>.apiError(mockApiError)

                ]),

            );

        });

    });

}

我们已经执行了许多 Flutter 单元测试并演示了不同的模拟方法。 可以继续使用示例Flutter 项目来运行其他测试。

Flutter 单元测试:实现卓越用户体验的关键

如果已经将单元测试整合到 Flutter 项目中,本文可能已经介绍了一些可以注入到工作流程中的新选项。 在本教程中,演示了将单元测试合并到下一个 Flutter 项目中是多么简单,以及如何应对更细微的测试场景的挑战。你可能再也不想跳过 Flutter 中的单元测试了。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

在这里插入图片描述

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!   

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

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

相关文章

【多尺度增强网络:超分】

Remote Sensing Image Super-Resolution via Multiscale Enhancement Network &#xff08;基于多尺度增强网络的遥感图像超分辨率&#xff09; 近年来&#xff0c;遥感图像因其特殊的价值而引起了人们的广泛关注。然而&#xff0c;卫星遥感图像通常是低分辨率的&#xff0c;…

案例实践丨基于SkyWalking全链路监控的微服务系统性能调优实践篇

1背景 随着开源社区和云计算的快速推进&#xff0c;云原生微服务作为新型应用系统的核心架构&#xff0c;得到了越来越广泛的应用。根据Gartner对微服务的定义&#xff1a;“微服务是范围狭窄、封装紧密、松散耦合、可独立部署且可独立伸缩的应用程序组件。” 微服务之父&…

markdown学习笔记

markdown学习笔记 1.文字&#xff08;依靠HTML&#xff09; 1.1文字缩进-空格转义符 单字符空&#xff1a;&emsp; 半字符空&#xff1a;&ensp;1.2文字对齐 「居中&#xff1a;」<center> 居中 </center> or <p align"center"> 居中 …

Java线程池中哪些事??

需要提前把线程准备好&#xff01;&#xff01;创建线程不是直接从系统申请&#xff0c;而是从池子里拿&#xff01; 等到线程不用了&#xff0c;也是还给池子&#xff01;&#xff01; 池子的目的是为了提高效率 线程的创建虽然比进程轻量&#xff0c;但是在频繁创建的情况下…

【计算机网络】图解应用层协议

图解应用层协议 1.应用层2.远程登录2.1 Telnet2.2 SSH 3.文件传输3.1 FTP3.2 TFTP 4.电子邮件4.1 通信架构4.2 邮件地址4.3 SMTP 协议4.4 POP3 协议4.5 IMAP 协议 5.WWW5.1 URI5.2 HTML5.3 HTTP 6.网络管理应用6.1 DHCP 协议6.2 DNS 协议 1.应用层 我们前面介绍过 TCP / IP 模…

【深度学习】 Python 和 NumPy 系列教程(十一):NumPy详解:3、数组数学(元素、数组、矩阵级别的各种运算)

目录 一、前言 二、实验环境 三、NumPy 0、多维数组对象&#xff08;ndarray&#xff09; 多维数组的属性 1、创建数组 2、数组操作 3、数组数学 1. 元素级别 a. 直接运算 b. 加法&#xff1a;np.add()函数 c. 减法&#xff1a;np.subtract()函数 d. 乘法&#xf…

Redis-带你深入学习数据类型zset

目录 1、zset有序集合 2、zset相关命令 2.1、添加或更新指定的元素——zadd 2.2、获取有序集合zset的元素个数相关命令&#xff1a;zcard、zcount 2.3、返回指定区间元素相关命令&#xff1a;zrange、arevrange、zrangebyscore 2.4、删除相关命令&#xff1a;zpopmax、zp…

C# 模拟button按钮批量锁住与打开

项目需求&#xff1a; 当winform界面上存在多个按钮时&#xff08;大于2个&#xff09;&#xff0c;用户需求为当点击其中一个按钮后&#xff0c;其它按钮全部为禁用&#xff0c;当被点击的按钮后台逻辑执行完成后&#xff0c;再释放所有按钮。用户可再次点击其它按钮。 此案…

Eclipse官网下载历史版本

进入官网 https://www.eclipse.org/ 进入下载页面 选择下载包 同一版本&#xff0c;又有不同类型 Eclipse IDE for Enterprise Java and Web Developers Eclipse IDE for Java Developers 任何Java开发人员必备的工具&#xff0c;包括Java IDE、Git客户端、XML编辑器、Mave…

第33章_瑞萨MCU零基础入门系列教程之DHT11温湿度获取实验

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

数据采集:数据挖掘的基础

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

解决gyp verb `which` failed python2 Error: not found: python2

运行老项目遇到如下报错 PS C:\Users\demo02> yarn install yarn install v1.22.19 info No lockfile found. [1/5] Validating package.json... [2/5] Resolving packages... warning axios0.18.1: Critical security vulnerability fixed in v0.21.1. For more informati…

4.1.8- Web 应用程序使用的组件进行指纹识别

Web 应用程序使用的组件进行指纹识别 IDWSTG-INFO-08 总结 毫不夸张地说&#xff0c;几乎所有可以想象的Web应用程序的想法都已经投入开发。随着全球大量自由和开源软件项目的积极开发和部署&#xff0c;应用程序安全测试很可能会遇到完全或部分依赖于这些知名应用程序或框架…

vue cli npm run build打生产环境包报错Cannot read property ‘pop‘ of undefined

问题出在webpack配置的代码拆分splitChunks 解决办法&#xff1a;每个cacheGroups中配置enforce: true;

数据结构——排序算法——快速排序

快速排序算法的基本思想是 1.从数组中取出一个数&#xff0c;称之为基数&#xff08;pivot&#xff09; 2.遍历数组&#xff0c;将比基数大的数字放到它的右边&#xff0c;比基数小的数字放到它的左边。遍历完成后&#xff0c;数组被分成了左右两个区域 3.将左右两个区域视为两…

巧用Lambda表达式获取对象属性名告别魔法值

在我们日常开发中&#xff0c;使用 MyBatis-Plus 写 SQL 执行的时候&#xff0c;难免会用到表字段&#xff0c;虽然 MyBatis-Plus 提供了 LambdaQueryWrapper 帮助我们使用 Lambda 方式调用对象属性名&#xff0c;但有的时候还是不免用到魔法值&#xff0c;当对象的属性名更改了…

检索技术核心学习总结

一、学习检索技术的必要性分析 &#xff08;一&#xff09;关键原因分析 学习检索技术&#xff08;Information Retrieval&#xff0c;IR&#xff09;具有多种重要的原因&#xff0c;特别是在今天信息爆炸的数字化时代。 总的来说&#xff0c;学习检索技术有助于提高信息处理…

基于Python和mysql开发的商城购物管理系统分为前后端(源码+数据库+程序配置说明书+程序使用说明书)

一、项目简介 本项目是一套基于Python和mysql开发的商城购物管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Python学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过…

Elasticsearch:什么是生成式人工智能?

生成式人工智能定义 给学生的解释&#xff08;基本&#xff09;&#xff1a; 生成式人工智能是一种可以创造新的原创内容的技术&#xff0c;例如艺术、音乐、软件代码和写作。 当用户输入提示时&#xff0c;人工智能会根据从互联网上现有示例中学到的知识生成响应&#xff0c;…

记一次线上BUG排查过程

1. 线上遇到一个非常奇怪的bug&#xff0c;为一个用户分配业务线类型后&#xff0c;该用户登录时&#xff0c;提示502&#xff0c;但其它的用户登录完全是正常的 2. 问题现象 3. 排查思路 先去看线上日志&#xff0c;看是否有error&#xff0c;但日志里边这个接口200正常返回…