selenium替代----playwright

安装

好处特点:这个东西不像selenium需要固定版本的驱动

 pip config set global.index-url https://mirrors.aliyun.com/pypi/simplepip install --upgrade pippip install playwright   playwright installplaywright install ffmpeg   (处理音视频的)

验证:

from playwright.sync_api import sync_playwright
pw =sync_playwright().start()
driver = pw.chromium.launch(headless=False)
page =driver.new_page()
page.goto("https://www.baidu.com")
input("回车结束")

加等待

page.wait_for_timeout(3000)  # 等待3秒

定位元素

1.locator (唯一才行,不然要报错)、介绍有哪些

  • id        
  • text  (似乎a标签才可以)
  • tag name 
  • css  

 百度首页、案例、 简单定位

from playwright.sync_api import sync_playwright
pw = sync_playwright().start()
driver = pw.chromium.launch(headless=False)
page = driver.new_page()
page.goto("https://www.baidu.com")
page.locator("#kw").fill("python")      # css定位(推荐)
page.wait_for_timeout(1500)
page.locator("id=kw").fill("java")      # id定位
page.wait_for_timeout(1500)
page.locator("text=百度首页").click()       # (a标签)的文本定位方式input("回车结束")

2.query_selector (找一个的)

(找不到时报错,找一个,若有多个,也返回第一个)

3. query_selector_all  (找多个,若没有是空列表)

css定位

【总结】ui自动化selenium知识点总结-CSDN博客

补充

  • 通用选择器 *  ,比如div>* 表示div下面的所有儿子
  • 可以用, 逗号分割,一起多定位一些元素  (用query_selector_all  查时 ,如下:
  • from playwright.sync_api import sync_playwright
    pw = sync_playwright().start()
    driver = pw.chromium.launch(headless=False)
    page = driver.new_page()
    page.goto("https://www.baidu.com")list1 = page.query_selector_all("#kw,#su")
    print(list1)
    for i in list1:print(i.get_attribute("id"))
    input("回车结束")
    

Xpath定位(css没有的):

        //*[text(),"文本"]         //*[contains(text(),"文本")]

        //input[@id='kw' and @class=‘xx’]   ---多属性联合查

        //xxx/..   用..找到上级

        

元素操作

  • .fill("xxxx")            覆盖写入   .click()   .press()   .hover()
  • .clear()
  • 最大化窗口启动 driver = pw.chromium.launch(headless=False,args=["--start-maximized"])
    page =driver.new_page(no_viewport=True)
  • 设置窗口大小
    • site =page.viewport_size        # page =driver.new_page(no_viewport=True)  用这个不能这样
      print(site)
      # 设置框高
      page.set_viewport_size({'width': 2000, 'height': 720})
  • 浏览器操作和获取
    • page.go_forward()       # 前进
      page.go_back()          # 后退
      page.reload()           # 刷新
      print(page.url)     # 这个没有括号哦
      print(page.title())

  • print(ele.get_attribute('name')) # 获取属性值, 获取不到就是None
  • .text_content()           # 获取元素的文本
  • .input_value()            # 获取value属性 
  • 获取元素的坐标和尺寸大小
  • 是否可见    是否选中 is_selected (例子如下) 是否可用 is_enabled
  • from playwright.sync_api import sync_playwright
    pw =sync_playwright().start()
    driver = pw.chromium.launch(headless=False)
    page =driver.new_page()
    ui_host= "http://121.43.36.83:8088"
    page.goto(f"{ui_host}/index.html#/")page.locator("#username").fill("sq2")
    page.query_selector("#password").fill("123")
    page.locator("#code").fill("999999")
    page.query_selector("#submitButton").click()# 先等待一下,不然直接刷新有问题
    page.wait_for_url(f"{ui_host}/index.html#/home")
    # 刷新
    page.reload()# 按钮-----单选
    page.query_selector(".el-menu >.el-submenu:nth-child(1)").click()
    page.query_selector(".el-menu >.el-submenu:nth-child(1)  ul  li:nth-child(1)").click()eles = page.query_selector_all(".el-row .el-radio__label")
    for ele in eles:# print(ele.text_content(),"是否可见",ele.is_visible())print(ele.text_content(),"是否选中",ele.is_checked())input("回车结束")
  • 窗口fang 截图、元素截图。screenshot()
    • OCR识别方式1:没试过
    • ocr识别方式2: 它返回的是 字节流,用dddocr,就可以识别解析
      • from playwright.sync_api import sync_playwright
        pw = sync_playwright().start()
        driver = pw.chromium.launch(headless=True)
        page = driver.new_page()
        page.goto("http://121.43.36.83:8088/index.html#/")
        ele = page.locator(".el-form-item__content img")
        ele_bytes = ele.screenshot(path="1.png")
        import ddddocr      # pip install ddddocr
        ocr=ddddocr.DdddOcr(show_ad=False)
        text =ocr.classification(ele_bytes)
        print(text)
        
      • 也可以用rb读取本地的图片,得到字节流,然后用他来
      • with open("1.png", "rb") as f:pic_bytes = f.read()
        import ddddocr      # pip install ddddocr
        ocr=ddddocr.DdddOcr(show_ad=False)
        text =ocr.classification(pic_bytes)
        print(text)
        
  •  键盘操作

  • 鼠标操作(--待验证)

    •       是针对元素的, ele.hover() 
    •        drag_to(ele1,ele2)
      • # 鼠标拖拽
        page.goto(f"https://sahitest.com/demo/dragDropMooTools.htm")
        ele2 = page.locator('.item:nth-of-type(4)')
        page.locator('#dragger').drag_to(ele2)

弹窗处理

是监听,和selenium有所不同

监听事件,监听到了,就会走lambda 的匿名函数。

from playwright.sync_api import sync_playwright
pw =sync_playwright().start()
driver = pw.chromium.launch(headless=False)
page =driver.new_page()
ui_host= "http://121.43.36.83:8088"
page.goto(f"{ui_host}/index.html#/")page.locator("#username").fill("sq2")
page.query_selector("#password").fill("123")
page.locator("#code").fill("999999")
page.query_selector("#submitButton").click()# 先等待一下,不然直接刷新有问题
page.wait_for_url(f"{ui_host}/index.html#/home")
# 刷新
page.reload()# 按钮-----弹窗
page.query_selector(".el-menu >.el-submenu:nth-child(5)").click()
page.query_selector(".el-menu >.el-submenu:nth-child(5)  ul  li:nth-child(1)").click()# 开启监听-----
page.on('dialog',lambda x:print(x.message)) # 获取信息page.on('dialog',lambda dialog:dialog.accept('333333'))       # 点击弹窗的确定page.locator('//button/span[contains(text(),"alert")]').click()
page.wait_for_timeout(2000)
page.locator('//button/span[contains(text(),"prompt")]').click()
input("回车结束")

执行js语句

用page.evaluate(js)        

from playwright.sync_api import sync_playwright
pw =sync_playwright().start()
driver = pw.chromium.launch(headless=False)
page =driver.new_page()
page.goto(f"https://www.baidu.com/")page.locator("#kw").fill("迪丽热巴")
page.set_viewport_size({'width': 720, 'height': 720})page.wait_for_timeout(2000)
js = 'window.scrollTo(0,400);'  # 绝对的移动,往下400
page.evaluate(js)page.wait_for_timeout(2000)
js2 = 'window.scrollBy(300,0);'      # 相对的移动,往右 300
page.evaluate(js2)input("回车结束")

文件上传

input 标签的话,好操些。

input标签(好处理):

其他的(需要借助pyautogui库) 

from playwright.sync_api import sync_playwright
pw =sync_playwright().start()
driver = pw.chromium.launch(headless=False)
page =driver.new_page()
ui_host= "http://121.43.36.83:8088"
page.goto(f"{ui_host}/index.html#/")page.locator("#username").fill("sq2")
page.query_selector("#password").fill("123")
page.locator("#code").fill("999999")
page.query_selector("#submitButton").click()# 先等待一下,不然直接刷新有问题
page.wait_for_url(f"{ui_host}/index.html#/home")
# 刷新
page.reload()# page.locator("//span[text()='文件上传']")
page.query_selector(".el-menu >.el-submenu:nth-child(6)").click()  # 同上# 单文件上传
page.query_selector(".el-menu >.el-submenu:nth-child(6)  ul  li:nth-child(1)").click()# ----------------------单文件上传--------------------
page.set_input_files('#cover','ele.png')  # #cover 是 input标签
# page.set_input_files('#cover',r'D:\123.png')  # 全路径推荐这个写法page.wait_for_timeout(2000)# ----------------------单文件上传(非input)--------------------需要装pyautogui
import pyautogui
import pyperclip
# 点击那个文件上传的按钮
page.query_selector(".el-menu >.el-submenu:nth-child(6)  ul  li:nth-child(2)").click()
page.locator(".el-icon-upload").click()page.wait_for_timeout(1000)
pyperclip.copy(r'D:\123.png')       # 将图片复制到剪贴板--- mac电脑暂不知道怎么写
#pyperclip.copy(r'"","",""')       # 将 多文件写法pyperclip.paste()   # 获取剪贴板的内容
pyautogui.hotkey("ctrl", "v")  # 模拟 ctrl + v
pyautogui.hotkey("enter")   # 回车input("回车结束")

iframe操作

遇到iframe标签时。 需要指定哪个frame,然后操作, (它相比与selenium,不需要切进去切出来)

方式1

可以用page.frame('name值')。或者 page.frame(url="")

方式2-- 通过frame的元素定位选到frame

窗口对象获取

相比selenium ,也是不用在切来切去。 每个窗口都可以得到一个对象

from playwright.sync_api import sync_playwright
pw =sync_playwright().start()
driver = pw.chromium.launch(headless=False)#page = driver.new_page()        # 原来的写法
context= driver.new_context()       # 创建上下文
page1 = context.new_page()page1.goto(f"https://www.baidu.com/")
page1.locator('//a[contains(text(),"新闻")]').click()page1.wait_for_timeout(2000)# 打印 窗口们
print(context.pages)  # page.context.pages 这样写结果也是一样的page2 =context.pages[-1]
page2.locator("#ww").fill("22222")page1.locator("#kw").fill("11111")input("回车结束")

某些元素的处理方式

        1. 单选框,click一下就好了

复选框处理

选之前,可以先做一个是否选中的判断,不然再点两次点没了。

from playwright.sync_api import sync_playwright
pw =sync_playwright().start()
driver = pw.chromium.launch(headless=False)
page =driver.new_page()
ui_host= "http://121.43.36.83:8088"
page.goto(f"{ui_host}/index.html#/")page.locator("#username").fill("sq2")
page.query_selector("#password").fill("123")
page.locator("#code").fill("999999")
page.query_selector("#submitButton").click()# 先等待一下,不然直接刷新有问题
page.wait_for_url(f"{ui_host}/index.html#/home")
# 刷新
page.reload()# 按钮-----复选框
page.query_selector(".el-menu >.el-submenu:nth-child(1)").click()
page.query_selector(".el-menu >.el-submenu:nth-child(1)  ul  li:nth-child(2)").click()eles = page.query_selector_all("//input[@value='11']/..")   # xpath 定位,  唱歌 和水果
for ele in eles:# print(ele)checked = ele.is_checked()# print(checked)if checked:print("选了")else:print("没有选")ele.click()input("回车结束")

下拉框处理

  如果是select 和option标签的。 

from playwright.sync_api import sync_playwright
pw =sync_playwright().start()
driver = pw.chromium.launch(headless=False)
page =driver.new_page()
ui_host= "http://121.43.36.83:8088"
page.goto(f"{ui_host}/index.html#/")page.locator("#username").fill("sq2")
page.query_selector("#password").fill("123")
page.locator("#code").fill("999999")
page.query_selector("#submitButton").click()# 先等待一下,不然直接刷新有问题
page.wait_for_url(f"{ui_host}/index.html#/home")
# 刷新
page.reload()# 按钮-----复选框
page.query_selector(".el-menu >.el-submenu:nth-child(1)").click()
page.query_selector(".el-menu >.el-submenu:nth-child(1)  ul  li:nth-child(3)").click()# select 标签
page.wait_for_timeout(1000)
ele = page.locator('select#categoryId')
ele.select_option(index=2)  # 根据索引选
page.wait_for_timeout(1000)
ele.select_option(value="1") # 根据value值
page.wait_for_timeout(1000)
ele.select_option(label="家居家装") # 中间的文本信息# 如果支持多选,就写成。label=('',''), 这种写法,或者结合三种方式都可以的
ele.select_option(label=("家居家装","xxx"), index=(1,3), value="xxxxx") # 中间的文本信息input("回车结束")

额外知识

1 操作方法,可以直接定位,设置超时(不认知默认是30s)等

1. 操作上可以添加超时方法,fill上、text_content 等操作方法里添加 timeout

        全局设置:page.set_default_timeout(10000), 

2 、操作上可以直接加 元素定位。

 。fill()。也可以直接用的, page.fill('元素定位', text,timeout=1000) 

2. 等待

  1.  感觉有点像sleep

         

  1. 等到某个url出现

        

      1. 等到某个元素出现(类似显示等待+)

       

3. 容易消失的元素找定位技巧

表示,5秒后,冻屏

setTimeout(function()  {debugger
},5000)

框架实现(demo)

框架思路和selenium一样:

【ui自动化】框架搭建v2.0_ui自动化框架如何实现-CSDN博客

【PO框架总结】ui自动化selenium,清新脱俗代码,框架升级讲解_ui自动化po博客园-CSDN博客

1 .新建common 包,新建 driver_page.py

里面获取page对象(selenium是获取driver)

并且,代码最后创建一个 page对象,后续都用这一个对象,实现单例的效果。只有一个浏览器页面存在

from playwright.sync_api import sync_playwright
# from configs.config import Configdef get_page(browser_type="chrome"):pw = sync_playwright().start()if browser_type == 'chrome':driver = pw.chromium.launch(headless=False, args=["--start-maximized"])  # 获取一个浏览器对象# 设置全屏# page = driver.new_page(no_viewport=True)# 多窗口时的上下文context = driver.new_context()# page = context.new_page(no_viewport=True)  #使用context.new_page,不能加这个参数page = context.new_page()  # 新建一个页面page.set_viewport_size({'width': 1920, 'height': 1080})# input("---调试用---")return pageelif browser_type == 'firefox': # 有点慢driver = pw.firefox.launch(headless=False)  # 获取一个浏览器对象page = driver.new_page(viewport={'width': 1920, 'height': 1080})  # 新建一个页面,(firefox全屏时有点点问题)# input("---调试用---")return pageyourpage = get_page()

2. 新建的common 包下,新建 basePage.py  页面基类

它作为所有页面类的基础类

  • 获取到唯一page对象,或者selenium里面的driver。这个就可以操作浏览器,也可以二次封装一些基础方法。
  • 统一管理一个  元素yaml文件。页面类继承basePage.py后获取文件中各自的元素定位
from common.dirver_page import yourpageclass BasePage(object):def __init__(self):# 1. 获取到那个唯一的页面对象,拿来操作浏览器self.page = yourpage# 2. 获取到页面元素  (从元素管理文件) (不同的页面类 获取 自己的元素)# self.locators = get_yaml_data(PollyPath.configs_path / 'allelements.yaml')[self.__class__.__name__]xxxx = {'LoginPage': {'百度首页输入框': '#kw','百度搜索按钮': '#su','login_button': '#btnLogin'},'MainPage': {'home_page': '//*[text()="首页"]','logout_button': "//span[text()='退出']"}}# 假如 有 class LoginPage(BasePage),  那么 self.__class__.__name__ = 'LoginPage'self.locators = xxxx[self.__class__.__name__]# 有了这个方法后,就可以实现,让 self.locators 这个字典, 本来是self.locators['username'] 获取到 '#username', 可以直接用self['username'] 获取。def __getitem__(self, item):return self.locators[item]def get_page(self):return self.pageif __name__ == '__main__':class LoginPage(BasePage):passlp1= LoginPage()print(lp1.locators['百度首页输入框']) # 输出:#kwprint(lp1['百度首页输入框']) # 输出:#kwpage =  lp1.get_page()page.goto("https://www.baidu.com/")page.fill(lp1['百度首页输入框'],'admin')       # 是哪个元素,可以做到见名知意input("---卡住--")

统一管理的元素yaml文件(后面再提供完整代码)

结构要和上面的xxxx一样。这个是一个大字典,一个页面一个key

每个key的值,又是一个字典,里面每一项是一个元素,名称 : 元素定位

元素定位直接写 CSS的定位,或xpath的定位即可

需注意  LoginPage  的命名 要和接下来的pages包中 定义  LoginPage类名称一致

3. 新建pages包,新建LoginPage.py  表示登录页

要继承basePage.

from common.basePage import BasePageclass LoginPage(BasePage):# 假装百度是登录页面# 访问页面def open_login_page(self):self.page.goto("https://www.baidu.com")def login(self,msg):self.page.fill(self["百度首页输入框"], msg)self.page.click(self["百度搜索按钮"])self.page.wait_for_timeout(2000)self.page.screenshot(path="./screenshot/baidu.png")if __name__ == '__main__':lp1 = LoginPage()# 步骤1:打开页面lp1.open_login_page()# 步骤2:登录lp1.login("selenium")

4.页面与页面之间的连接

比如登录页面类中的登录方法中,返回了 首页对象

首页类中,实现了,打开添加商品页面的方法。 

同样的,首页的方法中,跳增加商品页,也有return 

4. 新建testCases包,里面用pytest(略)

写用例,获取到pages包中的页面类对象,调页面类的方法。(略)

结合allure (略)

框架实现(案例-保利商城)

登录用例(元素定位写在login_data.yaml 里面的,单独搞的)

页面类中,跳转到另一个页面方法返回另一个页面的对象,可以实现方法的链式调用

如下是登录页,到首页, 还有一个是首页中两个方法也有返回(也在下图)

是用的统一管理元素的方式。 然后下拉选项的元素设置有个小技巧,如下。

最后,项目搞了个日志。每次运行后,有日志文件

就可以实现,再关键地方记录日志,好追溯问题

最后代码,再gitee.需要联系,

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

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

相关文章

Python代码编程基础

字符串 str.[]实现根据下标定位实现对元素的截取 for 循环可以实现遍历 while 循环可以在实现遍历的同时实现对某一下标数值的修改 字符串前加 r 可以实现对字符串的完整内容输出 字符串前加 f 可以实现对字符串内{}中包裹内容的格式化输出,仅在 v3.6 之后可用…

5月9号.

v-for: v-bind: v-if&v-show: v-model: v-on: Ajax: Axios: async&await: Vue生命周期: Maven: Maven坐标:

Spring 必会之微服务篇(1)

目录 引入 单体架构 集群和分布式架构 微服务架构 挑战 Spring Cloud 介绍 实现方案 Spring Cloud Alibaba 引入 单体架构 当我们刚开始学开发的时候,基本都是单体架构,就是把一个项目的所有业务的实现功能都打包在一个 war 包或者 Jar 包中。…

计算机的基本组成

#灵感# 记录下基础知识,此处专指计算机硬件方面,捎带记下芯片知识。 综述: 计算机硬件的基本组成包括运算器、控制器、存储器、输入设备和输出设备五大部分。其中,集成在一起的运算器和控制器称为 CPU(处理器&#x…

【Python 列表(List)】

Python 中的列表(List)是最常用、最灵活的有序数据集合,支持动态增删改查操作。以下是列表的核心知识点: 一、基础特性 有序性:元素按插入顺序存储可变性:支持增删改操作允许重复:可存储重复元…

Qt 的原理及使用(1)——qt的背景及安装

1. Qt 背景介绍 1.1 什么是 Qt Qt 是⼀个 跨平台的 C 图形⽤⼾界⾯应⽤程序框架 。它为应⽤程序开发者提供了建⽴艺术级图形 界⾯所需的所有功能。它是完全⾯向对象的,很容易扩展。Qt 为开发者提供了⼀种基于组件的开发模 式,开发者可以通过简单的拖拽…

多分类问题softmax传递函数+交叉熵损失

在多分类问题中,Softmax 函数通常与交叉熵损失函数结合使用。 Softmax 函数 Softmax 函数是一种常用的激活函数,主要用于多分类问题中。它将一个实数向量转换为概率分布,使得每个元素的值在 0 到 1 之间,且所有元素的和为 1。 …

数智读书笔记系列032《统一星型模型--一种敏捷灵活的数据仓库和分析设计方法》

引言 在当今数字化时代,数据仓库作为企业数据管理的核心基础设施,承担着整合、存储和提供企业数据的关键角色。随着商业环境的快速变化和业务需求的日益复杂,数据仓库的设计方法也在不断演进,以适应新的挑战和要求。 背景与意义 数据仓库领域长期存在着两种主流方法论之…

RT-Thread 深入系列 Part 1:RT-Thread 全景总览

摘要: 本文将从 RTOS 演进、RT-Thread 的版本分支、内核架构、核心特性、社区与生态、以及典型产品应用等多维度,全面呈现 RT-Thread 的全景图。 关键词:RT-Thread、RTOS、微内核、组件化、软件包管理、SMP 1. RTOS 演进与 RT-Thread 定位 2…

[docker基础一]docker简介

目录 一 消除恐惧 1) 什么是虚拟化,容器化 2)案例 3)为什么需要虚拟化,容器化 二 虚拟化实现方式 1)应用程序执行环境分层 2)虚拟化常见类别 3)常见虚拟化实现 一)主机虚拟化(虚拟机)实现 二)容器虚拟化实现 一 消除恐…

PostgreSQL 的 pg_advisory_lock 函数

PostgreSQL 的 pg_advisory_lock 函数 pg_advisory_lock 是 PostgreSQL 提供的一种应用级锁机制,它不锁定具体的数据库对象(如表或行),而是通过数字键值来协调应用间的并发控制。 锁的基本概念 PostgreSQL 提供两种咨询锁(advi…

SGLang 实战介绍 (张量并行 / Qwen3 30B MoE 架构部署)

一、技术背景 随着大语言模型(LLM)的飞速发展,如何更高效、更灵活地驾驭这些强大的模型生成我们期望的内容,成为了开发者们面临的重要课题。传统的通过拼接字符串、管理复杂的状态和调用 API 的方式,在处理复杂任务时…

微服务中 本地启动 springboot 无法找到nacos配置 启动报错

1. 此处的环境变量需要匹配nacos中yml配置文件名的后缀 对于粗心的小伙伴在切换【测试】【开发】环境的nacos使用时会因为这里导致项目总是无法启动成功

Lua从字符串动态构建函数

在 Lua 中,你可以通过 load 或 loadstring(Lua 5.1)函数从字符串动态构建函数。以下是一个示例: 示例 1:基本动态函数构建 -- 动态构建一个函数 local funcStr "return function(a, b) return a b end"-…

【Python】‌Python单元测试框架unittest总结

1. 本期主题:Python单元测试框架unittest详解 unittest是Python内置的单元测试框架,遵循Java JUnit的"测试驱动开发"(TDD)理念,通过继承TestCase类实现测试用例的模块化组织。本文聚焦于独立测试脚本的编写…

【Python 实战】---- 使用Python批量将 .ncm 格式的音频文件转换为 .mp3 格式

1. 前言 .ncm 格式是网易云音乐专属的加密音频格式,用于保护版权。这种格式无法直接播放,需要解密后才能转换为常见的音频格式。本文将介绍如何使用 Python 批量将 .ncm 格式的音频文件转换为 .mp3 格式。 2. 安装 ncmdump ncmdump 是一个专门用于解密 .ncm 文件的工具。它…

Linux 学习笔记2

Linux 学习笔记2 一、定时任务调度操作流程注意事项 二、磁盘分区与管理添加新硬盘流程磁盘管理命令 三、进程管理进程操作命令服务管理(Ubuntu) 四、注意事项 一、定时任务调度 操作流程 创建脚本 vim /path/to/script.sh # 编写脚本内容设置可执行权…

YOLO目标检测算法

文章目录 前言一、目标检测算法简介1、传统目标检测算法(1)R-CNN算法简介(2)Fast R-CNN算法简介(3)Faster R-CNN算法简介 2、目标检测中的算法设计范式(1)one-stage(2&am…

【软件设计师:软件】20.软件设计概述

一、软件设计基本原则 一、软件设计基本原则 1. 模块 是指执行某一特定任务的数据结构和程序代码。 将模块的接口和功能定义为其外部特性将模块的局部数据和实现该模块的程序代码称为内部特性。在模块设计时,最重要的原则就是实现信息隐蔽和模块独立。 2 . 信息隐蔽 将每…

软件工程之面向对象分析深度解析

前文基础: 1.软件工程学概述:软件工程学概述-CSDN博客 2.软件过程深度解析:软件过程深度解析-CSDN博客 3.软件工程之需求分析涉及的图与工具:软件工程之需求分析涉及的图与工具-CSDN博客 4.软件工程之形式化说明技术深度解…