不会做UI自动化测试?一起设计框架再实践吧

目的
相信做过测试的同学都听说过自动化测试,而UI自动化无论何时对测试来说都是比较吸引人的存在。
相较于接口自动化来说它可以最大程度的模拟真实用户的日常操作与特定业务场景的模拟,那么存在即合理,自动化UI测试自然也是广大测试同学职业道路上必不可少的必修课题之一了。
意义
说到UI自动化,不同的公司、不同的团队往往看待它的态度也存在着很大的差异:
项目或产品是否值得做UI自动化?
执行的方向是否正确?
落地的成本是否过大?
大部分的测试团队都会有同样的疑问,不管初衷如何(KPI、晋升、内部推广、效率优化),最大的难点一般都在于落地后如何保持一个稳定的使用周期与实际维护的成本是否小于团队投入期望值,说人话就是用来UI自动化之后是否大家都能用且可以长久的持续与维护下去。
这里博主建议的是,在做UI自动化之前先想清楚动机是什么:
如果真的只是自我拓展、KPI或者个人成果展示,那就掌握掌握原理与实操一下即可,没有必要在团队内进行推广;
如果真的是解决团队的实际需求:历次回归都需大量的手工,每次右移后需要全功能回归,功能数量大、场景多、功能增量后耦合较低的情况,则可以简单的评估一下引入自动化UI测试预计带来的成果与提升效果。
设计理念
之所以选用PO模式,也正是因为一般的APP项目或产品功能都是增量式迭代开发的,那么必定会面临需要维护的功能页面越来越多的处境。
如果是传统的设计模式,页面的元素与业务的操作会全部放在一个脚本内,有点类似于面向过程的编程理念。这样的模式必定会导致编写与维护的周期与成本增加,同样也不利于团队内成员共同维护的模式。
相较于传统模式,PO(Page Object)模式则是将一个页面的所有元素对象定位和对元素对象的操作封装成类,测试用例的编写也依照单个页面来进行,目的就是实现页面对象和测试用例的分离。
这样做的好处有3点:
低耦合:将每个页面单独进行封装,类似与面向对象,互相之间低耦合,即时需要业务流程连续执行也不会互相影响;
易维护:当界面发生变化时,只需修改对应的页面类中的元素即可,其他相关的不会受到影响也无需修改;
易上手:基于PO模式的设计理念,页面类的实现与细节不会暴露在外,都通过公共方法进行提供,使用者无需对代码的实现逻辑进行学习,只需要对业务与编程语言有足够的了解后直接编写与使用。
接下来了解了PO模式的优势之后,就需要对自动化框架进行设。
先考虑清楚使用了自动化测试框架是要解决什么问题,这里的问题不能是模糊且没有边界的,之后将要自动化的产品、模块、流程进行分类与整理,这里一般来说推荐产品的核心主流程,一般覆盖happy path即可,但如果需要加入一些反向用例与使用场景也是可以的,但切忌不要一股脑的把团队的手工测试用例都加进去,到了后期你会体验到什么叫维护的时间比测试的时间更长。
决定好以上这些了之后,就可以进行技术栈与框架的选择了,那这里我们选用的是appium+python+unittest的组合来进行PO模式测试框架的设计。当然,这里还是推荐大家根据自己的技术栈与公司环境现状来进行有效选择。
PO模式
这里先声明一点,所有的框架都不是一蹴而就的,和我们熟知的软件一样,无论是结构还是代码都是的一版一版优化出来的,所以大家现在看到的框架不会是最初与最终的模样,无论是拿来优化、二开还是直接使用都是可以的。
如果是自己写,哪怕一开始写得很简单也无所谓,要始终记住你落地自动化的目的是什么,只要能针对产品持续优化与反复总结,相信会有令人满意的结果的。
这里我们先将一个页面类分成两层,一个是对象操作层、另一个是业务层。
对象操作层指的是页面中的元素定位与单个元素操作;
业务层顾名思义是把对应的元素操作组合起来形成一些列的业务操作。
基于PO模式设计框架之前,我们还需要了解一下PO模式的6大原则,了解了原则之后才能更好地在实现过程中将PO模式的优势融入自己的框架之中。
6大原则
1.用公共方法代表页面提供的功能;
2.不要暴露页面元素到外部;
3.一般不在方法内加断言;
4.方法应该返回其他PO对象;
5.不需要封装页面内所有元素;
6.同样的行为不同的结果可以封装成不同的方法。
个人解读
1.一些可复用的操作,可以用公共的方法进行统一封装,即使不在同一页面;
2.封装实现方法,对外只提供方法名或接口名;
3.封装的实现方法中不要使用断言,把断言可以统一放在测试用例中;
4.可以使用其他对象作为一个方法的返回结果,比如页面的跳转,就可以用方法的结果进行返回;
5.页面中只对重要的元素进行PO设计,不重要的、非主流程的可以舍弃(这里可以更好的迎合只覆盖happy path);
6.如果一个操作可能有多种结果的时候,将结果封装成不同的方法,比如保存成功与保存失败。
框架设计
目录结构

这里简单说明下目录的结构:
base:存放一些框架与页面的公共方法;
po:存放所有的页面,这里就是被测对象相关的被测页面,不需要放全部页面;
result:存放相关的自动化测试结果报告;
test_case:存放测试用例。
根目录下还有一个run文件,这个是运行主入口,可以设置运行哪些测试用例集与使用什么样的测试报告套件。
实现步骤
这里的PO模式设计其实没有那么的复杂,从目录就可以看出,首先将一些基础的元素定位、通用操作封装到对应的BasePage类中。
这里插一句,其实做APP自动化也好,做web自动化也好,很大程度上开发的代码规范性决定了你的框架实现过程是否顺畅。所以这里大家也可以在平时的工作中与开发事先沟通好一些元素的属性写法规范,别觉得不可能,行不行事在人为。
然后根据事先整理好的业务操作流程与页面跳转关系(设计理念中提到的前置工作输出)进行功能的封装,这里推荐根据6大原则对相关操作进行实现,顺了之后就是熟练工了,大同小异的。如果日后出现了布局变更或者业务变更,统一在对应的po页面中进行修改即可。
另外,一些业务逻辑的判断,(比如是否存在该用户,不存在新建,存在直接进入),也可以放在po中,但是需要谨慎,这里比较推荐的还是放在测试用例内,也方便大家根据不同的情况做断言。
最后在页面元素、业务操作齐全的状态下进行测试用例的实现,一般来说可以先使用冒烟测试的测试用例来进行简单的业务验证,当然直接使用系统测试的测试用例也是完全没问题的,之后只需要根据之前整理好的用例选单进行转化即可。至于用例的存放目录结构可以根据po页面维度来存放,也可以根据业务维度来进行存放,见仁见智。
具体实现
base部分
这边先定义一个BasePage类,用来实现一些公共方法与元素定位的实现(webdriver):
classBasePage:def__init__(self, driver):self.driver = driverself.driver.implicitly_wait(10) defby_id(self, id):returnself.driver.find_element(By.ID, id) defby_xpath(self, xpath):returnself.driver.find_element(By.XPATH, xpath)defby_class_name(self, class_name):returnself.driver.find_element(By.CLASS_NAME, class_name)defby_uiautomator(self, uiautomator): return self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, uiautomator)
(左右滑动查看完整代码)
另外后续的一些触屏的操作、元素判断也可以按需放在这里面:
defis_element(self, element): source = self.driver.page_sourceif element insource:return Trueelse:return Falsedefdrag(self, bx=0.50, bw=0.05, by=0.4, bz=0.9): x = self.driver.get_window_size()['width'] y = self.driver.get_window_size()['height'] sx = x * bx ex = x * bw sy = y * by ey = y * bz return self.driver.swipe(sx, sy, ex, ey, 1000)
(左右滑动查看完整代码)
这里我定义了另一个driver_setup的方法,方便每次设备启动使用:
defdriver_setup(): desired_caps = dict() desired_caps['platformName'] = 'Android' desired_caps['platformVersion'] = '10' desired_caps['deviceName'] = '你自己的设备名' desired_caps['appPackage'] = '包名' desired_caps['appActivity'] = '启动名' desired_caps['noReset'] = True# 不重置session信息 desired_caps['fullReset'] = False# 效果类似与卸载APP 如果不想每次重新登录,设为False return desired_caps
(左右滑动查看完整代码)
po部分

目录大致如上,这里值得注意的是,不要把APP里所有的页面都加入到自动化测试中,100%的自动化测试覆盖率会让你苦不堪言,也大可不必。将每次必须回归的重要流程与高重复业务流程、场景加入即可。
以下就是po中的创建顾客页面的实现方法了,直接继承BasePage类,这里有几个例子需要关注的是,性别选择可以封装成两个方法,尽量不用同一个;另一个如果是点击类事件(单结果事件),直接click就行,不用单独在封装完元素后再进行业务操作封装,备注这样的多结果事件则要在下面单独进行业务指定。
classCustomerCreatePage(BasePage):""" 定义封装创建客户页面的各类操作 创建客 创建客户并开卡 """# 定义会员编号输入框defcustomer_number(self):returnself.by_id('com.tiffany.rta.debug:id/edt_customer_number')# 定义姓名输入框defcustomer_name(self):returnself.by_id('com.tiffany.rta.debug:id/edt_customer_name')# 定义手机输入框defcustomer_mobile(self):returnself.by_id('com.tiffany.rta.debug:id/edt_customer_mobile')# 定义性别选择defcustomer_sex(self):returnself.by_id('com.tiffany.rta.debug:id/tv_customer_sex')# 定义性别内选择项目-男defcustomer_sex_item_male(self):returnself.by_id('com.tiffany.rta.debug:id/tv_customer_boy').click() # 定义性别内选择项目-女defcustomer_sex_item_female(self):returnself.by_id('com.tiffany.rta.debug:id/tv_customer_girl').click() # 定义生日选择框defcustomer_birthday(self):returnself.by_id('com.tiffany.rta.debug:id/tv_customer_birthday')# 定义备注输入框defcustomer_memo(self):returnself.by_id('com.tiffany.rta.debug:id/ed_remark')# 定义保存并开卡按钮defsave_and_register_card_button(self): return self.by_id('com.tiffany.rta.debug:id/mb_save_open_card').click()
(左右滑动查看完整代码)
接下来就是组合多个元素进行业务操作的定义:
# 定义新建顾客操作defdo_create_customer(self):self.customer_number().send_keys('00001')self.customer_name().send_keys('自动化测试01')self.customer_mobile().send_keys('13200000000')self.customer_sex()self.customer_sex_item_male() self.save_button()
(左右滑动查看完整代码)
test_case部分
测试用例类继承unittest下的TestCase,初始化的时候将对应的用例业务流程加入到里面,另外在具体的测试用例中需要加对应的判断逻辑与操作步骤完整的添加在里面。
使用try捕获异常的时候记得把对应的报错名也写上,一是方便定位问题,二是有可能会导致即使用例失败,测试报告上的结果也是pass。
classTestCustomerListPage(unittest.TestCase):""" 定义客户列表界面的测试用例 创建客户 """ # 初始化必要的设备信息与业务页面defsetUp(self):self.driver = webdriver.Remote('http://localhost:4723/wd/hub', driver_setup())self.base_page = BasePage(driver=self.driver)self.home_page = HomePage(driver=self.driver)self.customer_list = CustomerListPage(driver=self.driver)self.customer_detail = CustomerDetailPage(driver=self.driver)self.customer_create = CustomerCreatePage(driver=self.driver) # 测试用例1 -- 创建顾客deftest_1_create_customer(self):self.home_page.go_customer() customer_name = '自动化测试01'# 业务逻辑判断 -- 是否存在该新客ifself.base_page.is_element(customer_name):self.customer_list.select_customer()self.customer_detail.do_delete_customer()self.home_page.go_index()self.home_page.go_customer()ifself.base_page.is_element(customer_name):self.customer_check.check_pass()else:self.customer_list.goto_create_customer()self.customer_create.do_create_customer()self.customer_detail.back_button()else:self.customer_list.goto_create_customer()self.customer_create.do_create_customer()self.customer_detail.back_button()try:self.assertTrue(self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,'new UiSelector().text("自动化测试01")')) except NoSuchElementException as e:return e sleep(5)deftearDown(self):self.driver.quit()if __name__ == "__main__": unittest.main()
(左右滑动查看完整代码)
run部分
具体的测试用例报告模板,大家可以自由选择,这边使用的是HTMLTestReportCN,启动的方式都是大同小异的,无非就是根据自己的测试场景进行定制就行。另外测试模板的组合和样式有兴趣的同学可以自己对报告脚本进行修改,打造更适合自己团队需求的测试报告。
# 两套测试报告模板路径,只用一个的可以就定义一个report_path = os.path.join(os.getcwd() + '\\result')result_path = os.path.join(report_path, 'report.html')# 测试套件路径,根据需求修改test_dir = os.path.join(os.getcwd() + '\\test_case\\trade')# 执行指定测试用例def test_suit(): suit = unittest.TestSuite() suit.addTest(TestOrderResultPage('test_1_order_result')) suit.addTest(TestOrderResultPage('test_2_order_result_home_page'))return suit# 执行测试用例集dis = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py")if __name__ == "__main__":with open(result_path, 'wb') as fp: runner = HTMLTestReportCN.HTMLTestRunner(stream=fp, title='自动化APP测试报告', description='基于自动化APP测试框架产生的测试报告') runner.run(test_suit())
(左右滑动查看完整代码)
注意点
1、PO模式虽然可以解决UI自动化测试中设计的部分问题,也仍然是目前比较主流的设计方案,后期面对大量的业务页面增加的情况,虽然可以使用通用页面来解决部分问题,但仍然避免不了界面与业务改动后大量调试代码的情况出现。
所以这也是很多公司无法将大量成本聚焦在UI自动化测试的原因,将UI自动化应用于部分主要业务的做法还是值得提倡的,它也只是提高测试团队工作效率与投入产出比的一项手段而已,千万不可本末倒置。
2、测试用例的合理设计与执行安排,如果你的测试用例的相关命名、流程设计、存放路径过于凌乱与潦草的话,相信我,后期当框架具有一定的规模后,你会发现往往在维护测试用例时花费的精力要远远大于你的执行时间。
与手工测试用例一样,无效用例始终都会出现在你的框架之中,这是无可避免的,但如何快速定位与规整这些用例就成了后期需要面对的日常问题之一,所以用例实现之初的命名规则、存放路径、实现时的备注就成了日后减少维护工作量的良好开端。
3、相较于接口自动化,UI自动化的性价比还是有一定的局限性,针对这样的情况,测试团队中如果要投入UI自动化的话可能就需要将团队中的成员定位做好一定的有效安排。
框架设计与实现的问题不大,有专业的业务理解与一定的代码功底一般都可以很好的完成对应的测试框架,这里只针对维护层面的工作来说,是专职人员定岗安排还是团队成员穿插进行都需要根据各自的团队实际情况来分配,各有利弊,毕竟维护是一件费时费力的持久性工作。
四、自动化测试学习资源分享
最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
下方这份完整的软件测试视频学习教程已经上传CSDN官方认证的二维码,朋友们如果需要可以自行免费领取 【保证100%免费】


这些资料,对于想进阶【自动化测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。希望对大家有所帮助……基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等配套学习资源免费分享~

相关文章:

不会做UI自动化测试?一起设计框架再实践吧
目的相信做过测试的同学都听说过自动化测试,而UI自动化无论何时对测试来说都是比较吸引人的存在。相较于接口自动化来说它可以最大程度的模拟真实用户的日常操作与特定业务场景的模拟,那么存在即合理,自动化UI测试自然也是广大测试同学职业道…...

数据分析实战项目3:RFM用户分群
目录1、RFM模型介绍2、Excel实际RFM划分案例3、RFM案例3.1 数据加载和基本信息查看3.2 数据预处理和RFM的初始值计算3.3 RFM区间和划分和分值计算3.4 RFM计算结果保存3.4.1 保存到excel3.4.2 保存到数据库3.5 RFM计算结果可视化3.6 结果分析(营销建议)3.…...

消息中间件概述
目录1.为什么学习消息队列2.什么是消息中间件3.消息队列应用场景3.1 应用解耦3.2 异步处理3.3 流量削峰3.4 什么是QPS,PV3.5 什么是PV,UV,PR4. AMQP 和 JMS4.1 AMQP4.2 JMS4.3. AMQP 与 JMS 区别5. 消息队列产品6. RabbitMQ6.1 RabbitMQ简介6.2 RabbitMQ 中的相关概…...
vue和js给后端接口返回的数据(如以json数据为元素的数组)添加新的json字段
文章目录vue和js给后端接口返回的数据(如以json数据为元素的数组)添加新的json字段1. res为后端接口的响应2. 获取后端接口返回的数据3. 向 tableData 添加字段3.1. 向 tableData 中添加一个新json元素( {"time", "2023-02-09"} )3.…...

负载均衡的方式
在业务初期,我们一般会先使用单台服务器对外提供服务。随着业务流量越来越大,单台服务器无论如何优化,无论采用多好的硬件,总会有性能天花板,当单服务器的性能无法满足业务需求时,就需要把多台服务器组成集…...

python(15)--函数设计
前言 函数是可重用的程序代码块。 函数的作用,不仅可以实现代码的复用,还可以保证修改函数的代码时,所有调用该函数的地方都能得到体现。目前我已知函数的作用是:对代码实现了封装、函数调用、传递参数、返回计算结果等。 正文 …...

手把手教你用Python做可视化数据,还能调节动画丝滑度
数据可视化动画还在用Excel做? 现在一个简单的Python包就能分分钟搞定! 而且生成的动画也足够丝滑,效果是酱紫的: 这是一位专攻Python语言的程序员开发的安装包,名叫Pynimate。 目前可以直接通过PyPI安装使用。 使用…...
湖南中创教育PMP项目管理——变更管理
【变更管理】包括 一、如何理解需求变更 二、如何控制需求变更 三、项目变更管理流程 四、如何应对“奇葩”变更 一、如何理解需求变更: 1、项目中发生变更是正常现象,变更无法回避 2、哪里都可能出现变更,任何人都有权提出变更 3、…...

IC真题 —— 刷题记录(1)
引言 记录一些 我自己刷的 IC行业招聘真题,不是每题记录,只记录一些值得记录的,写下自己的看法。主要是一些数字IC行业题目,偏前端。 1、有一个逐次逼近型 8位A/D 转换器,若时钟频率为250KHz,完成一次转换…...

【C++入门】命名空间,输出输入,缺省参数,函数重载
文章目录命名空间C输入与输出缺省参数函数重载命名空间 在C/C中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标 识符的名称进行本地化࿰…...
cmu 445 poject 2笔记
2022年的任务 https://15445.courses.cs.cmu.edu/fall2022/project2/ checkpoint 1,实现b树,读,写,删 checkpoint 2, 实现b树,迭代器,并发读写删 本文不写代码,只记录遇到的一些思维盲点 checkp…...

梅开二度的 axios 源码阅读,三千字详细分享功能函数,帮助扩展开发思维
前言 第一遍看 axios 源码,更多的是带着日常开发的习惯,时不时产生出点联想。 第二遍再看 axios 源码,目标明确,就是奔着函数来的。 当有了明确清晰的目标,阅读速度上来了,思绪也转的飞快。 按图索骥&a…...

vcs仿真教程
VCS是在linux下面用来进行仿真看波形的工具,类似于windows下面的modelsim以及questasim等工具,以及quartus、vivado仿真的操作。 1.vcs的基本指令 vcs的常见指令后缀 sim常见指令 2.使用vcs的实例 采用的是全加器的官方教程,首先介绍不使用…...

java 自定义json解析注解 复杂json解析 工具类
java 自定义json解析注解 复杂json解析 工具类 目录java 自定义json解析注解 复杂json解析 工具类1.背景2、需求-各式各样的json一、一星难度json【json对象中不分层】二、二星难度json【json对象中出现层级】三、三星难度json【json对象中存在数组】四、四星难度json【json对象…...

类的 6 个默认成员函数
文章目录一、构造函数1. 构造函数的定义2. 编译器生成的构造函数3. 默认构造函数4. 初始化列表5. 内置成员变量指定缺省值(C11)二、析构函数1. 析构函数的定义2. 编译器生成的析构函数3. 自己写的析构函数的执行方式三、拷贝构造函数1. C语言值传递和返回值时存在 bug2. 拷贝构…...

基于Verilog HDL的状态机描述方法
⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。 🔥文章和代码已归档至【Github仓库…...

6年软件测试经历:成长、迷茫、奋斗
前言 测试工作6年,经历过不同产品、共事过不同专业背景、能力的同事,踩过测试各种坑、遇到过各种bug。测试职场生涯积极努力上进业务和技术能力快速进步过、也有努力付出却一无所得过、有对测试生涯前景充满希望认为一片朝气蓬勃过、也有对中年危机思考不…...
OpenMMLab AI实战营第五次课程
语义分割与MMSegmentation 什么是语义分割 任务: 将图像按照物体的类别分割成不同的区域 等价于: 对每个像素进行分类 应用:无人驾驶汽车 自动驾驶车辆,会将行人,其他车辆,行车道,人行道、交…...

【软考】系统集成项目管理工程师(二十)项目风险管理
一、项目风险管理概述1. 风险概念2. 风险分类3. 风险成本二、项目风险管理子过程1. 规划风险管理2. 识别风险3. 实施定性风险分析4. 实施定量风险分析5. 规划风险应对6. 控制风险三、项目风险管理流程梳理一、项目风险管理概述 1. 风险概念 风险是一种不确定事件或条件,一旦…...

2017-PMLR-Neural Message Passing for Quantum Chemistry
2017-PMLR-Neural Message Passing for Quantum Chemistry Paper: https://arxiv.org/pdf/1704.01212.pdf Code: https://github.com/brain-research/mpnn 量子化学的神经信息传递 这篇文献作者主要是总结了先前神经网络模型的共性,提出了一种消息传递神经网络&am…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...