UnitTesting 单元测试

1. 测试分为两种及详细介绍测试书籍:

  1.1 Unit Test : 单元测试

  - test the business logic in your app : 测试应用中的业务逻辑

  1.2 UI  Test :  界面测试

  - test the UI of your app : 测试应用中的界面

  1.3 测试书籍网址:《Testing Swift》 icon-default.png?t=N7T8https://www.hackingwithswift.com/store/testing-swift

2. ViewModel 单元测试

  2.1 创建 ViewModel,UnitTestingBootcampViewModel.swift

import Foundation
import SwiftUI
import Combine/// 单元测试 ViewModel
class UnitTestingBootcampViewModel: ObservableObject{@Published var isPremium: Bool@Published var dataArray: [String] = []@Published var selectedItem: String? = nillet dataService: NewDataServiceProtocolvar cancellable = Set<AnyCancellable>()init(isPremium: Bool, dataService: NewDataServiceProtocol = NewMockDataService(items: nil)) {self.isPremium = isPremiumself.dataService = dataService}/// 添加子项func addItem(item: String){// 为空不往下执行guard !item.isEmpty else { return }self.dataArray.append(item)}/// 选中项func selectItem(item: String){if let x = dataArray.first(where: {$0 == item}){selectedItem = x}else{selectedItem = nil}}/// 保存项func saveItem(item: String) throws{guard !item.isEmpty else{throw DataError.noData}if let x = dataArray.first(where: {$0 == item}){print("Save item here!!! \(x)")} else {throw DataError.itemNotFound}}/// 错误信息enum DataError: LocalizedError{case noDatacase itemNotFound}/// 请求返回数据func downloadWithEscaping() {dataService.downloadItemsWithEscaping { [weak self] returnedItems inself?.dataArray = returnedItems}}/// 下载用到的组合func downloadWithCombine() {dataService.downloadItemsWithCombine().sink { _ in} receiveValue: { [weak self] returnedItems inself?.dataArray = returnedItems}.store(in: &cancellable)}
}

  2.2 创建测试文件

    当创建项目时,没有选择 Include Tests/包含测试 选项时,需要添加文件去对应项目,不然测试文件会报 No such module 'XCTest' 编译错误

    添加单元测试文件:

    方法一 : 选择项目 -> 菜单栏 Editor -> Add Target... -> 弹出对话框,选择 Test 栏下 -> Unit Testing Bundle -> 填写信息/可默认 -> Finish,完成创建单元测试文件。

    方法二 : 选择项目,点击 PROJECT 列,最下的 + 按钮,弹出对话框,选择 Test 栏下 ,后面步骤与上一致

    创建单元测试文件 UnitTestingBootcampViewModel_Tests.swift

import XCTest
import Combine
/// 导入项目
@testable import SwiftfulThinkingAdvancedLearning// 《Testing Swift》 测试书籍
// 书籍网址: https://www.hackingwithswift.com/store/testing-swift
// Naming Structure: test_UnitOfWork_StateUnderTest_ExpectedBehavior -  结构体命名: 测试_工作单元_测试状态_预期的行为
// Naming Structure: test_[struct or class]_[variable or function]_[expected result] - 测试_[结构体 或者 类的名称]_[类中的变量名 或者 函数名称]_[预期结果 预期值]
// Testing Structure: Given, When, Then - 测试结构: 给定,什么时候,然后final class UnitTestingBootcampViewModel_Tests: XCTestCase {/// 解决多次引用相同的类var viewModel: UnitTestingBootcampViewModel?var cancellables = Set<AnyCancellable>()/// 开始设置数据override func setUpWithError() throws {// Put setup code here. This method is called before the invocation of each test method in the class.viewModel = UnitTestingBootcampViewModel(isPremium: Bool.random())}/// 结束重置数据override func tearDownWithError() throws {// Put teardown code here. This method is called after the invocation of each test method in the class.viewModel = nilcancellables.removeAll()}/// 单元测试函数名,根据命名规则命名:测试_类名称_是否高质量_应该为真func test_UnitTestingBootcampViewModel_isPremium_shouldBeTrue(){// Givenlet userIsPremium: Bool = true// Whenlet vm = UnitTestingBootcampViewModel(isPremium: userIsPremium)// ThenXCTAssertTrue(vm.isPremium)}/// 单元测试函数名 根据命名规则命名:测试_类名称_是否高质量_应该为假func test_UnitTestingBootcampViewModel_isPremium_shouldBeFalse(){// Givenlet userIsPremium: Bool = false// Whenlet vm = UnitTestingBootcampViewModel(isPremium: userIsPremium)// ThenXCTAssertFalse(vm.isPremium)}/// 单元测试函数名 根据命名规则命名:测试_类名称_是否高品质_注入值func test_UnitTestingBootcampViewModel_isPremium_shouldBeInjectedValue(){// Givenlet userIsPremium: Bool = Bool.random()// Whenlet vm = UnitTestingBootcampViewModel(isPremium: userIsPremium)// ThenXCTAssertEqual(vm.isPremium, userIsPremium)}/// 单元测试函数名 根据命名规则命名 - 注入值_压力 / for 循环func test_UnitTestingBootcampViewModel_isPremium_shouldBeInjectedValue_stress(){for _ in 0 ..< 10 {// Givenlet userIsPremium: Bool = Bool.random()// Whenlet vm = UnitTestingBootcampViewModel(isPremium: userIsPremium)// ThenXCTAssertEqual(vm.isPremium, userIsPremium)}}/// 单元测试函数名 根据命名规则命名 - 数组_预期值:为空func test_UnitTestingBootcampViewModel_dataArray_shouldBeEmpty(){// Given// Whenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Then 断言 = 判定XCTAssertTrue(vm.dataArray.isEmpty)XCTAssertEqual(vm.dataArray.count, 0)}/// 单元测试函数名 根据命名规则命名 - 数组_预期值:添加项func test_UnitTestingBootcampViewModel_dataArray_shouldAddItems(){// Givenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Whenlet loopCount: Int = Int.random(in: 1..<100)for _ in 0 ..< loopCount{vm.addItem(item: UUID().uuidString)}// Then 断言 = 判定XCTAssertTrue(!vm.dataArray.isEmpty)XCTAssertFalse(vm.dataArray.isEmpty)XCTAssertEqual(vm.dataArray.count, loopCount)XCTAssertNotEqual(vm.dataArray.count, 0)// GreaterThan 大于XCTAssertGreaterThan(vm.dataArray.count, 0)// XCTAssertGreaterThanOrEqual// XCTAssertLessThan// XCTAssertLessThanOrEqual}/// 单元测试函数名 根据命名规则命名 - 数组_预期值:添加空白字符func test_UnitTestingBootcampViewModel_dataArray_shouldNotAddBlankString(){// Givenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Whenvm.addItem(item: "")// Then 断言 = 判定XCTAssertTrue(vm.dataArray.isEmpty)}/// 单元测试函数名 根据命名规则命名 - 数组_预期值:添加空白字符func test_UnitTestingBootcampViewModel_dataArray_shouldNotAddBlankString2(){// Givenguard let vm = viewModel else {XCTFail()return}// Whenvm.addItem(item: "")// Then 断言 = 判定XCTAssertTrue(vm.dataArray.isEmpty)}/// 单元测试函数名 根据命名规则命名 - 选中项_预期值:开始为空func test_UnitTestingBootcampViewModel_selectedItem_shouldStartAsNil(){// Given// Whenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Then 断言 = 判定XCTAssertTrue(vm.selectedItem == nil)XCTAssertNil(vm.selectedItem)}/// 单元测试函数名 根据命名规则命名 - 选中项_预期值:应该为空 当选择无效项func test_UnitTestingBootcampViewModel_selectedItem_shouldBeNilWhenSelectingInvalidItem(){// Givenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Select valid item : 选择有效项let newItem = UUID().uuidStringvm.addItem(item: newItem)vm.selectItem(item: newItem)// Select invalid item : 选择无效项// Whenvm.selectItem(item: UUID().uuidString)// Then 断言 = 判定XCTAssertNil(vm.selectedItem)}/// 单元测试函数名 根据命名规则命名 - 选中项_预期值:应该选中func test_UnitTestingBootcampViewModel_selectedItem_shouldBeSelected(){// Givenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Whenlet newItem = UUID().uuidStringvm.addItem(item: newItem)vm.selectItem(item: newItem)// Then 断言 = 判定XCTAssertNotNil(vm.selectedItem)XCTAssertEqual(vm.selectedItem, newItem)}/// 单元测试函数名 根据命名规则命名 - 选中项_预期值:选中_压力测试func test_UnitTestingBootcampViewModel_selectedItem_shouldBeSelected_stress(){// Givenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Whenlet loopCount: Int = Int.random(in: 1..<100)var itemsArray: [String] = []for _ in 0 ..< loopCount {let newItem = UUID().uuidStringvm.addItem(item: newItem)itemsArray.append(newItem)}// 随机取一个字符串let randomItem = itemsArray.randomElement() ?? ""// 检查字符串不为空XCTAssertFalse(randomItem.isEmpty)vm.selectItem(item: randomItem)// Then 断言 = 判定XCTAssertNotNil(vm.selectedItem)XCTAssertEqual(vm.selectedItem, randomItem)}/// 单元测试函数名 根据命名规则命名 - 保存项_预期值:输出错误异常_元素没找到func test_UnitTestingBootcampViewModel_saveItem_shouldThrowError_itemNotFound(){// Givenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Whenlet loopCount: Int = Int.random(in: 1..<100)for _ in 0 ..< loopCount {vm.addItem(item: UUID().uuidString)}// Then 断言 = 判定XCTAssertThrowsError(try vm.saveItem(item: UUID().uuidString))XCTAssertThrowsError(try vm.saveItem(item: UUID().uuidString), "Should throw Item Not Found error!") { error in// 返回错误let returnedError = error as? UnitTestingBootcampViewModel.DataError// 判断错误是否相同XCTAssertEqual(returnedError, UnitTestingBootcampViewModel.DataError.itemNotFound)}}/// 单元测试函数名 根据命名规则命名 - 保存项_预期值:输出错误异常_没数据func test_UnitTestingBootcampViewModel_saveItem_shouldThrowError_noData(){// Givenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Whenlet loopCount: Int = Int.random(in: 1..<100)for _ in 0 ..< loopCount {vm.addItem(item: UUID().uuidString)}// Then 断言 = 判定do {try vm.saveItem(item: "")} catch let error {// 返回错误let returnedError = error as? UnitTestingBootcampViewModel.DataError// 判断错误是否相同XCTAssertEqual(returnedError, UnitTestingBootcampViewModel.DataError.noData)}}/// 单元测试函数名 根据命名规则命名 - 保存项_预期值:保存选项func test_UnitTestingBootcampViewModel_saveItem_shouldSaveItem(){// Givenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Whenlet loopCount: Int = Int.random(in: 1..<100)var itemsArray: [String] = []for _ in 0 ..< loopCount {let newItem = UUID().uuidStringvm.addItem(item: newItem)itemsArray.append(newItem)}// 随机取一个字符串let randomItem = itemsArray.randomElement() ?? ""// 检查字符串不为空XCTAssertFalse(randomItem.isEmpty)// Then 断言 = 判定XCTAssertNoThrow(try vm.saveItem(item: randomItem))do {try vm.saveItem(item: randomItem)} catch  {XCTFail()}}/// 单元测试函数名 根据命名规则命名 - 下载数据_预期值:返回选项func test_UnitTestingBootcampViewModel_downloadWithEscaping_shouldReturnItems(){// Givenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Whenlet expectation = XCTestExpectation(description: "Should return items after 3 seconds")// dropFirst: 删除第一个发布 数组值,因为初始化为空数组,取的是第二个数组,模拟服务数据返回的数组vm.$dataArray.dropFirst().sink { returnedItems inexpectation.fulfill()}.store(in: &cancellables)vm.downloadWithEscaping()// Then 断言 = 判定 GreaterThan:大于// 为了安全获取到值,设置等待 5 秒wait(for: [expectation], timeout: 5)XCTAssertGreaterThan(vm.dataArray.count, 0)}/// 单元测试函数名 根据命名规则命名 - 下载数据组合_预期值:返回选项func test_UnitTestingBootcampViewModel_downloadWithCombine_shouldReturnItems(){// Givenlet vm = UnitTestingBootcampViewModel(isPremium: Bool.random())// Whenlet expectation = XCTestExpectation(description: "Should return items after a seconds")// dropFirst: 删除第一个发布 数组值,因为初始化为空数组,取的是第二个数组,模拟服务数据返回的数组vm.$dataArray.dropFirst().sink { returnedItems inexpectation.fulfill()}.store(in: &cancellables)vm.downloadWithCombine()// Then 断言 = 判定 GreaterThan:大于// 为了安全获取到值,设置等待 5 秒wait(for: [expectation], timeout: 5)XCTAssertGreaterThan(vm.dataArray.count, 0)}/// 单元测试函数名 根据命名规则命名 - 下载数据组合_预期值:返回选项func test_UnitTestingBootcampViewModel_downloadWithCombine_shouldReturnItems2(){// Givenlet items: [String] = [UUID().uuidString, UUID().uuidString, UUID().uuidString, UUID().uuidString]let dataService: NewDataServiceProtocol = NewMockDataService(items: items)let vm = UnitTestingBootcampViewModel(isPremium: Bool.random(), dataService: dataService)// Whenlet expectation = XCTestExpectation(description: "Should return items after a seconds")// dropFirst: 删除第一个发布 数组值,因为初始化为空数组,取的是第二个数组,模拟服务数据返回的数组vm.$dataArray.dropFirst().sink { returnedItems inexpectation.fulfill()}.store(in: &cancellables)vm.downloadWithCombine()// Then 断言 = 判定 GreaterThan:大于// 为了安全获取到值,设置等待 5 秒wait(for: [expectation], timeout: 5)XCTAssertGreaterThan(vm.dataArray.count, 0)XCTAssertEqual(vm.dataArray.count, items.count)}
}

3. 模拟请求数据 单元测试

  3.1 创建模拟请求数据类 NewMockDataService.swift

import Foundation
import SwiftUI
import Combine/// 定义协议
protocol NewDataServiceProtocol{func downloadItemsWithEscaping(completion: @escaping (_ items: [String]) -> ())func downloadItemsWithCombine() -> AnyPublisher<[String], Error>
}/// 实现模拟请求数据
class NewMockDataService: NewDataServiceProtocol {let items: [String]init(items: [String]?) {self.items = items ?? ["ONE", "TWO", "THREE"]}/// 模拟网络下载数据 escaping: 转义字符func downloadItemsWithEscaping(completion: @escaping (_ items: [String]) -> ()) {DispatchQueue.main.asyncAfter(deadline: .now() + 2) {completion(self.items)}}/// 下载组合func downloadItemsWithCombine() -> AnyPublisher<[String], Error> {// 数据转换Just(self.items).tryMap({ publishedItems inguard !publishedItems.isEmpty else {throw URLError(.badServerResponse)}return publishedItems}).eraseToAnyPublisher()}
}

  3.2 创建单元测试类 NewMockDataService_Tests.swift

import XCTest
import Combine
/// 导入项目
@testable import SwiftfulThinkingAdvancedLearningfinal class NewMockDataService_Tests: XCTestCase {/// 随时取消控制器var cancellable = Set<AnyCancellable>()override func setUpWithError() throws {// Put setup code here. This method is called before the invocation of each test method in the class.}override func tearDownWithError() throws {// Put teardown code here. This method is called after the invocation of each test method in the class.cancellable.removeAll()}//  单元测试函数名 根据命名规则命名 - 测试_类名_初始化_预期值:正确的设置值func test_NewMockDataService_init_doesSetValuesCorrectly() {// 执行// Given: 给定let items: [String]? = nillet items2: [String]? = []let items3: [String]? = [UUID().uuidString, UUID().uuidString]// When: 时间let dataService = NewMockDataService(items: items)let dataService2 = NewMockDataService(items: items2)let dataService3 = NewMockDataService(items: items3)// Then 然后XCTAssertFalse(dataService.items.isEmpty)XCTAssertTrue(dataService2.items.isEmpty)XCTAssertEqual(dataService3.items.count, items3?.count)}//  单元测试函数名 根据命名规则命名 - 测试_类名_下载转换数据项_预期值:正确的设置值func test_NewMockDataService_downloadItemsWithEscaping_doesReturnValues() {// 执行// Given: 给定let dataService = NewMockDataService(items: nil)// When: 时间var items: [String] = []let expectation = XCTestExpectation()dataService.downloadItemsWithEscaping { returnedItems initems = returnedItemsexpectation.fulfill()}// Then 然后// 等待 5 秒wait(for: [expectation], timeout: 5)// 断言两个数组大小一样XCTAssertEqual(items.count, dataService.items.count)}//  单元测试函数名 根据命名规则命名 - 测试_类名_下载数据项组合_预期值:正确的设置值func test_NewMockDataService_downloadItemsWithCombine_doesReturnValues() {// 执行// Given: 给定let dataService = NewMockDataService(items: nil)// When: 时间var items: [String] = []let expectation = XCTestExpectation()// 下载组合控制dataService.downloadItemsWithCombine().sink { completion inswitch completion{case .finished:expectation.fulfill()case .failure:XCTFail()}} receiveValue: {returnedItems in// fulfill: 完成items = returnedItems}.store(in: &cancellable)// Then 然后// 等待 5 秒wait(for: [expectation], timeout: 5)// 断言两个数组大小一样XCTAssertEqual(items.count, dataService.items.count)}//  单元测试函数名 根据命名规则命名 - 测试_类名_下载数据项组合_预期值:确实失败func test_NewMockDataService_downloadItemsWithCombine_doesFail() {// 执行// Given: 给定let dataService = NewMockDataService(items: [])// When: 时间var items: [String] = []let expectation = XCTestExpectation(description: "Does throw an error")let expectation2 = XCTestExpectation(description: "Does throw URLError.badServerResponse")// 下载组合控制dataService.downloadItemsWithCombine().sink { completion inswitch completion{case .finished:XCTFail()case .failure(let error):expectation.fulfill()//let urlError = error as? URLError// 断言,判定//XCTAssertEqual(urlError, URLError(.badServerResponse))// 错误判断if error as? URLError == URLError(.badServerResponse) {expectation2.fulfill()}}} receiveValue: {returnedItems in// fulfill: 完成items = returnedItems}.store(in: &cancellable)// Then 然后// 等待 5 秒wait(for: [expectation, expectation2], timeout: 5)// 断言两个数组大小一样XCTAssertEqual(items.count, dataService.items.count)}
}

4. 创建单元测试 View,调用测试的 ViewModel UnitTestingBootcampView.swift

import SwiftUI/*1. Unit Test : 单元测试- test the business logic in your app : 测试应用中的业务逻辑2. UI  Test :  界面测试- test the UI of your app : 测试应用中的界面*//// 单元测试
struct UnitTestingBootcampView: View {@StateObject private var vm: UnitTestingBootcampViewModelinit(isPremium: Bool){_vm = StateObject(wrappedValue: UnitTestingBootcampViewModel(isPremium: isPremium))}var body: some View {Text(vm.isPremium.description)}
}struct UnitTestingBootcampView_Previews: PreviewProvider {static var previews: some View {UnitTestingBootcampView(isPremium: true)}
}

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

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

相关文章

MySQL——六、库表操作(下篇)

MySQL 一、INSERT语句二、REPLACE语句三、UPDATE语句四、delete和TRUNCATE语句五、MySQL用户授权1、密码策略2、用户授权和撤销授权 一、INSERT语句 #在表里面插入数据&#xff1a;默认情况下&#xff0c;一次插入操作只插入一行 方式1&#xff1a; INSERT [INTO] 表名 [(colu…

uni-app小程序使用DCloud(插件市场)流程

一、DCloud&#xff08;插件市场&#xff09; DCloud 是uni-app官方插件市场&#xff0c;里面有官方、团队、个人发布的众多插件&#xff0c;包括uni-ui、uni-pay 等。而像uni-ui这种大型组件库都有官方文档可参考&#xff0c;但一些团队或个人发布的小型插件没有文档&#xf…

垃圾回收器、垃圾回收算法、空间分配担保、JVM调优、GC回收对象的过程

文章目录 &#x1f34a; 垃圾回收器、垃圾回收算法、空间分配担保&#x1f389; Serial&#x1f389; ParNew&#x1f389; Parallel scavenge&#x1f389; 复制算法&#x1f389; 分代收集算法&#x1f389; 进入老年代的几种情况&#x1f4dd; 空间分配担保 &#x1f389; S…

超火的双臂烹饪机器人Project YORI,分分钟成为你的专属大厨!

原创 | 文 BFT机器人 当前行业内有两种通用的烹饪自动化方法&#xff1a;一种是“制造一个可以在普通厨房中运作的烹饪机器人&#xff0c;因为每个人都有厨房”&#xff0c;这听起来很不错&#xff0c;但接下来你就必须使你的烹饪机器人能够在厨房环境中正常运行&#xff0c;这…

docker 复习

文章目录 1. docker 基础1.1 docker 安装配置镜像加速器拉取镜像的仓库&#xff1a; docker 部署Mysql 镜像docker 命令的详细解释docker 常见命令docker 数据卷docker 相关命令总结 2.自定义镜像2.1 dockerfile2.2 try 构建一个Java镜像&#xff0c;并部署2.3 总结: 3. docker…

物流监管:智慧仓储数据可视化监控平台

随着市场竞争加剧和市场需求的不断提高&#xff0c;企业亟需更加高效、智能且可靠的仓储物流管理方式&#xff0c;以提升企业的物流效率&#xff0c;减少其输出成本&#xff0c;有效应对市场上的变化和挑战。 图扑自研 HT for Web 产品搭建的 2D 智慧仓储可视化平台&#xff0c…

Databend 开源周报第 115 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 聚合索引 Data…

[计算机提升] 系统及用户操作

1.4 系统及用户操作 1.4.1 系统操作 1.4.1.1 开机、关机、重启 在Windows系统中&#xff0c;开机&#xff08;Power On&#xff09;&#xff0c;关机&#xff08;Shutdown&#xff09;和重启&#xff08;Restart&#xff09;是指计算机的不同电源控制操作。 开机&#xff1a;…

苹果平板可以用别的电容笔吗?电容笔和Apple pencil区别

和苹果原装的Pencil相比&#xff0c;这种平替的电容笔并没具备重力压感&#xff0c;只有一种倾斜的压感功能。如果你不经常用来作画&#xff0c;一支普通的电容笔就足够了。不管是用来记笔记&#xff0c;还是用来解决一些数学问题&#xff0c;都能用得上。再说了&#xff0c;即…

NAND存储器转储分析 - 使用ECC修复位错误与UBI镜像固件分析

一、 简介 这篇研究论文将通过黑客的视角&#xff0c;详细阐述如何操作 NAND dump 以及如何获取 dump 文件中的所有文件。每一步骤以及所使用的方法均会细致解析&#xff0c;并配以实例说明。本文主要关注的是物理 NAND dump&#xff0c;这是从通用编程器中提取出的 dump 文件…

项目平台——测试报告的实现(七)

这里写目录标题 一、Table表格组件的使用1、Table表格组件中的插槽使用 二、点击查看测试报告&#xff0c;跳转到测试报告详情页实现1、新建Report.vue组件2、配置路由3、查看报告按钮添加事件 三、页面布局1、Layout布局2、卡片设计3、打开页面发送请求加载报告数据4、对接口进…

Java版本+企业电子招投标系统源代码+支持二开+招投标系统+中小型企业采购供应商招投标平台

功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外部供…

大数据flink篇之三-flink运行环境安装后续一yarn-session安装

前提&#xff1a; Hadoop 必須保证在 2.2 以上&#xff0c;且必須裝有 hdfs 服务。Hadoop安装后续会有相关说明。 具体的&#xff0c;在生产环境中&#xff0c;flink一般会交由yarn、k8s等资源管理平台来处理。本章主要讲解yarn模式下的session cluster模式。 flink Session-C…

Java模拟双向链表,增删操作

public static void main(String[] args) {ArrayList arrayList new ArrayList();Node zhangsan new Node("张三");Node lisi new Node("李四");Node wanger new Node("王二");//模拟双向链表&#xff0c;将表中元素依次连接起来zhangsan.ne…

Windows运维相关经验技巧

常用工具 在线PS Photoshop在线 FAQ 电脑能上网&#xff0c;浏览器上不了网 # 错误原因&#xff1a; 设置了网络代理&#xff0c;浏览器无法通过网络代理上网# 解决办法 关闭网络代理 &#xff08;1&#xff09;wini&#xff0c;打开设置 &#xff08;2&#xff09;网络和I…

【前端学习】—函数防抖(十)

【前端学习】—函数防抖&#xff08;十&#xff09; 一、什么是函数防抖 函数防抖&#xff1a;事件被触发n秒后再执行回调&#xff0c;如果在这n秒内又被触发&#xff0c;则重新计时。 二、代码实现 <script>const searchElement document.getElementById("searc…

多语言跨境商城源码搭建(定制代码+源码开源)

在全球化的背景下&#xff0c;跨境电商发展迅猛&#xff0c;多语言商城成为企业拓展海外市场的首要选择。本文将为大家介绍跨境多语言商城的源码搭建方法&#xff0c;以及相关的定制代码和源码开源信息。 一、什么是跨境多语言商城 跨境多语言商城是一种能够支持多国语言的电子…

站外引流之道:跨境电商如何吸引更多流量?

随着跨境电商行业的蓬勃发展&#xff0c;站外引流成为卖家们必须掌握的关键技能。站外引流不仅有助于扩大产品曝光度&#xff0c;还能吸引更多潜在客户&#xff0c;提高销售额。 然而&#xff0c;站外引流并非易事&#xff0c;需要精心策划和执行。本文将探讨站外引流的策略&a…

外置告警蜂鸣器使用小坑

告警蜂鸣器调试小坑 昨天调试新产品&#xff0c;由于IMO、MSC组织和IEC标准规定&#xff0c;不能使用带红色指示灯的蜂鸣器&#xff0c;于是更换了个不带灯。然而奇怪的现象出现了两次短响的程序在有的页面正常&#xff0c;有的页面就变成一声了。搞了一天&#xff0c;把各种寄…

服务器数据恢复-linux+raid+VMwave ESX数据恢复案例

服务器数据恢复环境&#xff1a; 一台某品牌x3950 X6型号服务器&#xff0c;linux操作系统&#xff0c;12块硬盘组建了一组raid阵列&#xff0c;上层运行VMwave ESX虚拟化平台。 服务器故障&#xff1a; 在服务器运行过程中&#xff0c;该raid阵列中有硬盘掉线&#xff0c;linu…