尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
PO模式+数据驱动:打造低维护成本的UI自动化测试框架
写自动化测试的朋友应该都经历过这些用例写起来一时爽维护起来火葬场。前端页面刚改了个按钮的class测试脚本跟着红一片定位不到元素、用例全挂。这种情况多了以后团队里难免出现一种声音——自动化到底值不值得搞。我自己在几个项目里折腾过不少轮UI自动化从一个什么辅助手段都没有的裸脚本慢慢演进到一套自己用得顺手的框架核心其实就是两件事把页面结构从用例里抽离出去再把测试数据从代码里拆出去。这篇就聊聊我实际搭起来比较顺的一套东西基于PO模式做页面对象管理加上数据驱动来控制用例输入与预期结果。与其说这是一篇理论讲课不如说是把我实操过的项目框架摊开来看包括目录怎么组织、BasePage怎么设计、测试数据怎么优雅地喂给pytest以及真正落地过程中踩过的一些坑。适合刚把Selenium玩熟、想进入框架化阶段的开发者参考也适合在已有自动化脚本但感觉越来越难维护的团队做个对比自查。1. 整体设计思路为什么是PO模式数据驱动1.1 先从脚本为什么会“脆”说起UI自动化跑得稳不稳很大程度上取决于用例和页面之间的耦合程度。早期写脚本的时候很多人习惯直接在测试方法里塞driver.find_element(By.ID, user_login)。def test_login(driver): driver.find_element(By.ID, user_login).send_keys(admin)第一次跑起来很爽后面就出事了。产品改版把id换成了name或者登录按钮从button换成了div加click事件你得像排雷一样一个个去改用例里的每一条定位。拿到一个项目里可能有上百条用例每条里又有多个定位语句这种修法能让人崩溃。PO模式的核心思路是把页面的定位规则和操作方法封装到一个独立的类里测试用例不再关心这个页面长什么样只需要调用这个页面对外暴露的功能方法。就好比你用一台电视不需要管内部的电路怎么走只要按遥控器上的电源键。页面改了内部结构最多改对应的页面类用例代码一行不动。1.2 数据驱动解决的是另一层问题PO模式把页面隔离了但用例本身还有一层耦合就是测试数据写死在代码里。比如登录测试你写一组正确的账号密码再来一组错误的这组数据一换又得改代码。如果扩展到要测几十组边界数据一个用例要被复制粘贴出十几个方法就纯粹是给自己找罪受。数据驱动的想法是把测试输入和预期结果全都放到外部文件里代码里只写一套执行逻辑。pytest里正好有现成的parametrize装饰器配合yaml或json文件就能实现用一套用例代码去遍历多组数据。所以这套组合拳的关系是这样的PO模式把页面的变动隔离在页面类内部数据驱动把用例逻辑和数据分离两者兜住的是自动化测试里两个最大的维护成本点。有了这两层隔离哪怕前端频繁小迭代测试代码的改动面都能被压到最小。1.3 落地框架的目录设计我常用的一个目录结构长这样. ├── config/ │ ├── base.yaml # 环境地址、浏览器、超时时间等 │ └── conftest.py # 读取配置的fixture ├── data/ │ └── login_data.yaml # 登录模块的测试数据 ├── interfaces/ # 页面对象层 │ ├── base_page.py # BasePage公共类 │ ├── login_page.py # 登录页面类 │ └── home_page.py # 首页或功能页面类 ├── testcase/ │ ├── __init__.py │ └── test_login.py # 登录相关用例 ├── report/ # 测试报告输出 ├── utils/ │ ├── driver_factory.py # 浏览器驱动工厂 │ ├── yaml_loader.py # 读取yaml的工具 │ └── log_util.py # 日志封装 └── reqs.txtinterfaces层对应PO模式里的Page Objecttestcase层只负责业务和数据编排普通情况下不出现driver.findElement这种语句。2. 页面对象层设计把页面封装成“控件盒子”2.1 base_page里应该沉淀哪些公共操作页面类写多了以后会发现自己写了很多重复代码——查找元素、点击、输入、等待、截图。这些操作每写一个页面类都要重复一遍的话就没有意义了所以base_page这个基类要提前设计好。class BasePage: def __init__(self, driver, timeout10): self.driver driver self.timeout timeout def find(self, locator): return WebDriverWait(self.driver, self.timeout).until( lambda d: d.find_element(*locator) ) def finds(self, locator): return WebDriverWait(self.driver, self.timeout).until( lambda d: d.find_elements(*locator) ) def click(self, locator): # 先等待元素可见再点击减少“点击不到”的失败 self.find(locator).click() def input_text(self, locator, text): element self.find(locator) element.clear() element.send_keys(text) def get_text(self, locator): return self.find(locator).text def wait_visible(self, locator, timeout10): return WebDriverWait(self.driver, timeout).until( EC.visibility_of_element_located(locator) )这些都是最底层的方法看起来很简单但设计的时候有几个值得注意的细节第一click和input_text返回self这样调用链能串起来。比如page.input_text(用户名).input_text(密码).click(登录)一行就能表达完一个操作序列用例看起来会清透很多。第二所有定位都走find方法不在页面类里直接出现with单独一行driver.find_element。这是后面排查超时问题时最省力的一招——你能把日志和定位统一起来。第三base_page里不放业务动作只放面向元素的基础操作。把“点击登录按钮”写在LoginPage里而不是写在BasePage里这是职责边界问题。2.2 页面类里的元素定位怎么写更省心页面类的实现重点在于元素定位策略。我见过不少项目把xpath写得又长又硬页面一改就废。稳定性的优先级上我自己习惯这样排data-testid id name css选择器 xpath。现在的项目很多会加data-testid这些自动化专用属性这是最推荐优先使用的。没有的话退而求其次用id再不行用css。xpath能用但尽量不要写长链尤其是那种包含大量层级关系的绝对路径。一个登录页面类的标准长这样class LoginPage(BasePage): # 定位器统一放在页面类顶部改起来一目了然 username_input (By.CSS_SELECTOR, input[nameusername]) password_input (By.CSS_SELECTOR, input[typepassword]) login_button (By.CSS_SELECTOR, button[typesubmit]) error_tip (By.CSS_SELECTOR, .login-error) def login(self, username, password): self.input_text(self.username_input, username) self.input_text(self.password_input, password) self.click(self.login_button) return HomePage(self.driver) def get_error_message(self): return self.get_text(self.error_tip)定位器放在类顶部相当于把这个页面所有需要用到的元素规则集中陈列在一个地方。页面什么元素改了到这个区域里面搜就行不用右键顺着调用链往前翻用例。这也是PO模式在实际维护时最能直接感受到的好处之一。2.3 页面跳转的处理逻辑登录完成后返回首页这个“页面过渡”在PO模式里通常有两种处理方式。第一种像上面代码那样让登录方法返回HomePage对象。用例里写起来就是home_page login_page.login(username, password)。这一行不仅完成了登录操作还把上下文从登录页切换到了首页后续调用首页的方法就很自然。第二种是在用例层显式完成跳转也就是登录操作后在测试里再创建一个HomePage实例。两种方案都能跑我更倾向于第一种。前者让页面跳转的语义更贴合实际操作流程而且从调用方写起来也更省事。3. 测试用例层与控制流设计pytest中的“骨架与血液”3.1 fixture怎么用才能简化用例编写pytest的fixture是这套框架里连接驱动、页面对象和实际用例的桥梁。conftest.py里写driver的session级或function级fixture再写几个直接返回页面对象实例的fixture。一个我实际项目中常用的conftest写法pytest.fixture(scopefunction) def driver(): _driver DriverFactory.get_driver() _driver.get(Config.BASE_URL) yield _driver _driver.quit() pytest.fixture def login_page(driver): return LoginPage(driver)function级别的driver保证了每条用例在独立干净的浏览器环境里跑用例之间互不干扰。但代价是每条用例都要重新打开浏览器跑完整套用例的时间会非常可观。我后来在回归场景里切换到session级driver配合用例内部的状态重置方法来提速这一块要根据项目的实际用例量权衡。fixture命名上刻意保持简单用例签名里需要什么就直接写。例如def test_login_success(login_page):这样写用例读起来就像在陈述一句话。不需要在用例内部去手动实例化页面了。3.2 一条登录用例怎么把上面几层串起来拿登录模块来举例完整的一条用例是这样的class TestLogin: def test_login_success(self, login_page): home_page login_page.login(admin, admin123) assert home_page.is_username_displayed(admin) def test_login_wrong_password(self, login_page): login_page.login(admin, wrongpass) assert login_page.get_error_message() 用户名或密码错误用例里没有一条定位语句没有调用Selenium的底层API没有任何数据字面量。这些全被塞到了页面类和外部数据文件里。看到这个用例的人不需要了解页面长什么样只要理解业务逻辑就行。这其实就是框架化的意义所在——测试用例回归到了“描述业务行为”的本位而不再是一堆浏览器操作的流水账。3.3 pytest和unittest怎么选现在写UI自动化我基本都是pytest不用unittest。pytest的fixture机制天然适合做依赖注入parametrize做数据驱动是一等公民级别的支持还有插件生态比如pytest-ordering控制执行顺序、pytest-rerunfailures做失败重试、pytest-html出报告。unittest做这些事要么写起来很费劲要么缺现成的能力。如果你接手的项目里已经是unittest写的也不一定要推翻重来。unittest的setUp和tearDown本质上也能做类似的初始化清理工作数据驱动可以用ddt库配合data装饰器实现。但如果是从零开始搭选pytest会让自己少走很多弯路。4. 数据驱动的实现细节yaml文件怎么变成用例参数4.1 测试数据装进yaml后长什么样我用得最顺的数据文件格式是yaml。原因无他yaml支持嵌套结构可读性比json强还能写注释。一份登录模块的数据文件看起来大概这样login_data: - case_name: 正常登录 username: admin password: admin123 expected: 首页用户名显示admin - case_name: 密码错误 username: admin password: wrongpass expected: 用户名或密码错误 - case_name: 用户名为空 username: password: admin123 expected: 用户名不能为空每组数据就是一个用例场景。数据文件是测试人员和产品经理也能打开修改的东西这就把测试数据的管理从代码层面下沉到了配置层面很多不需要懂代码的人也能参与维护。4.2 pytest的parametrize怎么接上yaml读取yaml和parametrize结合起来的完整写法如下。import pytest import yaml with open(data/login_data.yaml, r, encodingutf-8) as f: login_data yaml.safe_load(f)[login_data] class TestLogin: pytest.mark.parametrize( data, login_data, ids[case[case_name] for case in login_data] ) def test_login_with_data(self, login_page, data): login_page.login(data[username], data[password]) actual login_page.get_error_message() assert data[expected] in actual or login_page.is_login_success()ids参数非常关键它决定了用例在报告里显示的名字。如果不加idspytest里显示的就是data-0、data-1这种排查失败用例时你得去对列表下标极其痛苦。加了ids之后报告和日志里能直接看到“正常登录”“密码错误”这些场景名谁挂了、什么场景挂了一眼就知道。4.3 数据驱动和参数化的边界问题写用例写多了以后会慢慢意识到数据驱动不能走火入魔。有些东西适合放数据文件有些东西不适合。适合放数据的提交内容的输入值、预期结果文案、异常提示信息、期望的状态码或页面标题。不适合放数据的元素定位、执行流程控制比如先打开a页面还是先操作b区域、浏览器类型。这些属于代码层面的逻辑和配置放进数据文件反而会让用例的流程变得不可控。我在一个项目里遇到过把元素定位也写成数据的情况那基本是把代码复杂度原封不动搬进了配置文件。改起来反而更绕而且配置文件没有语法检查一个缩进错了整组用例直接报错。所以把握一个原则——数据文件里只放“输入”和“预期结果”不含任何“怎么做”。4.4 数据量大了以后怎么办用例数据少的时候就一个简单的yaml文件很清爽。当数据达到几十组甚至上百组的时候yaml文件会变得庞大起来。这时候可以做两件事。第一件事是按模块拆分数据文件一个业务模块一个yaml保持文件的单一职责。第二件事是如果数据来源于接口返回或者数据库查出来的动态数据那就不适合静态yaml了。可以在fixture里动态生成一组列表再通过pytest_generate_tests钩子来传参。这种方式更灵活适合数据量大且需要动态刷新的场景但编写门槛也确实更高。5. 实操过程与核心环节实现搭一个登录场景练手5.1 配置文件的装载与全局参数管理框架里的配置文件我习惯用yamlbase.yaml中放全局性内容。base: url: http://demo.example.com browser: chrome implicit_wait: 5 element_timeout: 10 headless: false读取的时候直接用工具函数在代码里装载然后把它暴露成一个全局配置对象。# utils/config.py class Config: def __init__(self): with open(config/base.yaml, r, encodingutf-8) as f: data yaml.safe_load(f)[base] self.url data[url] self.browser data[browser] self.element_timeout data[element_timeout] self.headless data[headless] config Config()使用全局config对象的好处是代码里任何地方import config拿来即用不需要一层层往下传参数。对测试框架这种没有复杂依赖关系的项目来说全局配置对象比依赖注入更实用。5.2 driver工厂怎么写driver需要支持Chrome、Firefox、Edge等主流浏览器切换同时要支持headless模式工厂类每次都通过keyboard命令新建浏览器实例会显得很笨拙实际要做一个简单的路由分发。class DriverFactory: staticmethod def get_driver(browserNone): browser browser or config.browser options None if browser.lower() chrome: options webdriver.ChromeOptions() if config.headless: options.add_argument(--headless) options.add_argument(--window-size1920,1080) return webdriver.Chrome(optionsoptions) elif browser.lower() firefox: options webdriver.FirefoxOptions() if config.headless: options.add_argument(-headless) return webdriver.Firefox(optionsoptions) elif browser.lower() edge: options webdriver.EdgeOptions() if config.headless: options.add_argument(--headless) return webdriver.Edge(optionsoptions) raise ValueError(funsupported browser: {browser})这里有个容易被忽略的点headless模式下如果不设置window-size默认窗口尺寸可能不是你想要的大小那些依赖窗口尺寸的响应式布局元素可能会判定为不可见导致点击失败。所以headless模式下设置一个固定尺寸是实测中减少奇怪报错的一个有效手段。5.3 登录流程在页面对象中的完整表达假设要测试的系统是一个带登录的后台管理平台登录成功后应该显示用户名称和退出按钮。那么HomePage里面需要有判断某个用户名是否出现的操作。class HomePage(BasePage): username_label (By.CSS_SELECTOR, .navbar-user-name) logout_button (By.CSS_SELECTOR, .navbar-logout) def get_logined_user(self): return self.get_text(self.username_label) def is_login_success(self, expected_user): return expected_user self.get_logined_user()然后回到LoginPage的login方法。def login(self, username, password): self.input_text(self.username_input, username) self.input_text(self.password_input, password) self.click(self.login_button) return HomePage(self.driver)这里点击登录按钮之后理论上页面会跳转到首页。但如果登录失败停留在登录页则登录按钮的点击不会导致URL变化。处理方式是登录后等待首页的特征元素出现。更稳一点的写法是点击登录后先判断是成功还是失败再进行分支处理。实际项目中我在login方法里加了一个状态判别。def login(self, username, password): self.input_text(self.username_input, username) self.input_text(self.password_input, password) self.click(self.login_button) # 等待首页特征元素或者错误提示出现的二选一情况 WebDriverWait(self.driver, 10).until( lambda d: self.driver.current_url.endswith(/home) or self.find(self.error_tip).is_displayed() )这样做的好处是登录方法本身的返回时机是确定的用例层不需要额外加等待。在很多有异步请求的页面里点击登录后也许会有两三秒的延迟如果不等待就断言大概率会撞上元素尚未更新的场景。5.4 从测试数据到报告展示的完整链路跑一遍数据驱动用例后pytest会把每条数据当做一条独立用例输出到报告里。如果用的是pytest-html报告里展示的标题来自ids参数。如果接入allure报告还可以给每条数据动态添加描述allure的dynamic模块能在运行时附加上模块、功能、场景等标签这样数据驱动用例在报告上的展示效果又会更丰富一层。我实际的项目中会把case_name直接映射成allure场景名失败时附加上读取的yaml源数据帮助快速定位是哪一组数据触发的。这些信息的组织方式虽然不是框架核心但对排查问题的效率影响非常大。6. 常见问题与排查技巧实录6.1 为什么点击按钮总是报element not interactable这个报错是UI自动化里出现频率极高的一种。原因一般有两个。第一个原因是元素虽然存在但处于不可交互状态比如被遮罩层遮挡、按钮还未加载完成。解决办法是在click方法里先等待元素可见再等待可点击。我实际项目里的click方法比基本版多了一步。def click(self, locator): element WebDriverWait(self.driver, self.timeout).until( EC.element_to_be_clickable(locator) ) element.click()第二个原因是页面上有其他浮层挡住了按钮比如弹窗提示、cookies提醒条。这种情况纯粹等待是无解的需要先关闭浮层再点击。所以在页面类的方法里如果有比较常见的浮层元素可以加一个自动关闭浮层的动作防患于未然。6.2 浏览器越跑越慢最后卡死跑大量用例的过程中浏览器经常会出现内存不断走高的现象尤其是chrome加载了不少页面资源之后。这种情况在长期运行的回归里特别明显。我遇到的解决办法有两个方向。一是把driver的scope粒度缩到function级每条用例结束都quit一次缺点是速度慢。二是session级driver跑完一组模块之后强制清理一次缓存和cookie用driver.delete_all_cookies()加上反复刷新空页面来释放内存。具体取舍要看你的用例总时长和稳定性需求。在session级driver下还有一个致命点需要留意一旦某条用例把页面带到了一个异常状态后续用例都会跟着遭殃。所以session级方案的用例编写规范性要求更高每条用例结束必须确保页面回到一个基准状态。6.3 yaml数据文件缩进错了报错信息看不懂数据驱动里最让人抓狂的问题之一就是yaml解析出错。缩进一不对yaml.safe_load直接抛异常而且报错信息往往只是大致定位到某一行并不能清晰地告诉你应该怎么改。这个问题的解法听起来有点原始但特别有效先在本地用脚本单独跑一次yaml加载把加载出来的结构打印出来人工检查。很多加载问题在解析脚本里暴露得更清楚而不会和其他用例执行逻辑混在一起。另外我强烈建议在数据文件里打开编辑器的yaml校验插件大多数语法错误在写的时候就会被标红轮不到运行时去报错。6.4 用例相互影响单独跑通过、一起跑失败这是UI自动化最毒的坑。单独执行某条用例时一切正常整套跑的时候失败了这种问题十有八九和用例间的状态残留有关比如上一个用例登录过、弹窗没关、localStorage里存了不该有的东西。我的排查套路是先拿到失败用例单独跑跑通了再找它前面顺序相邻的用例。在失败用例的driver实例里打印当前页面URL、cookie、localStorage内容对比单独跑和连着跑的状态差异很快就能定位到是哪一条用例污染了环境。根治的办法通常都指向同一个方向用例之间保持彻底隔离。要么function级driver要么每条用例登录动作都在开始前重置本地存储。6.5 元素加载速度不稳定时快时慢Selenium定位元素时默认有一个隐式等待但隐式等待和显式等待同时使用的时候会引发意想不到的时间叠加效应。举个例子find_element用了3秒失败后隐式等待又追加了3秒最后定位总耗时变成双方等待时间的总和。我现在的做法是关闭隐式等待全部用显式等待。这样每个元素的等待时间都是白纸黑字可控制的日志里能清晰地看到“等了多久”“等到没有”。排查起来比那种“不知道时间去哪了”的状态要靠谱得多。self.driver.implicitly_wait(0)在base_page里我完全不依赖implicitly_wait只靠WebDriverWait去控制每一步操作的节奏。这套设计在项目里实测下来脚本的失败率肉眼可见地下降了一截。6.6 断言失败以后怎么快速定位是断言逻辑错还是页面真错了在数据驱动场景中尤其如此——一条用例挂了说不清是代码写错、数据给错还是页面真的渲染失败。我在框架里加了两个辅助手段。第一用例失败时自动截图并保存到report目录文件名里带上用例名和时间戳。不管是不是页面问题一张当时的截图能省去大部分猜测时间。第二断言之前把页面上相关的关键文本都打印到日志里。登录失败时日志里记录“当前错误提示为用户名或密码错误”这样对比预期数据就能一眼看出偏差在哪。7. 维护这套框架的一点心得把PO模式和数据驱动这套框架真正用起来之后我最大的感受是自动化测试的维护成本大头其实不在写用例上而在后续每一次页面迭代后的调整速度上。PO模式能保证前端改了某个按钮的位置或样式你只需要去对应的页面类里改一下定位器而不会波及到任何一条用例。数据驱动则让你新增一个测试场景时不用复制一整个用例方法只往yaml里加一组数据就够了。但框架本身不是万能的。如果团队里没有人愿意维护页面类如果用例之间状态隔离做得不彻底如果报表和日志体系没有搭建起来那再好的设计也扛不住时间带来的腐化。我的经验是框架是骨架真正让自动化测试活下来的是每一次随手就把页面类整理干净、把数据文件归类的习惯。一个几十行的小改动当时不觉得有什么积累几百次之后你会突然发现这套框架已经可以稳稳地承担起每周回归的职责了。
RELATED

相关推荐

用LangChain和Playwright构建大模型驱动的测试智能体

用LangChain和Playwright构建大模型驱动的测试智能体

把大模型接进自动化测试这件事,我之前一直持保留态度。直到用LangChain把Playwright包成一个能自己看页面、自己点按钮、自己写断言的测试智能体之后,我才意识到传统的UI自动化写法确实到了该升级的时候。这篇文章就把我搭建这个测试智能体的完整思路、核…

📅 2026/10/11 19:26:59
西门子Teamcenter仿真体系建设:构建自动驾驶研发数字主线

西门子Teamcenter仿真体系建设:构建自动驾驶研发数字主线

简介:本资源系统阐述西门子在自动驾驶与智能网联汽车领域的仿真开发方法论与平台化体系建设路径,面向汽车电子工程师、ADAS/自动驾驶算法开发者、仿真流程架构师及PLM系统实施人员,解决多学科协同难、仿真数据分散、V模型全流程工具链割裂、C…

📅 2026/10/11 19:21:59
4-report-assembler 报告组装器完全指南:zeroize-audit 审计结果汇聚、置信门控与双模式报告生成

4-report-assembler 报告组装器完全指南:zeroize-audit 审计结果汇聚、置信门控与双模式报告生成

AI 技能AI 插件应用安全网络安全AI 评测 【免费下载链接】skills Trail of Bits Claude Code skills for security research, vulnerability detection, and audit workflows 项目地址: https://gitcode.com/gh_mirrors/skills8/skills 点击查看 免费下载 4-report…

📅 2026/10/11 19:21:59
MORE NEWS

更多资讯

📰

Flink/PyFlink CSV读写实战:Schema声明与参数配置避坑

先说个我上个月接手的真实任务:一批传感器历史数据以 CSV 文件存在对象存储里,需要灌进 Flink 流作业做实时指标计算。文件不大,三十来个分区,每分区几万行,字段也就四五个。我当时觉得这是最没技术含量的一步&#xf…

📰

LingBot-World 2.0能商用吗?CC BY-NC-SA 4.0许可证解读:14B权重的使用边界与风险清单

【免费下载链接】lingbot-world-v2 Infinite Worlds with Versatile Interactions 项目地址: https://gitcode.com/gh_mirrors/li/lingbot-world-v2 点击查看 免费下载 LingBot-World 2.0(LingBot-World-Infinity)是一个"以多样化交互生…

📰

响应式实时数据处理:从概念到落地的完整技术链路

1. 从“rea”这个模糊词根说起:它到底指向什么第一次看到“rea”这个标题的时候,我盯着屏幕愣了几秒。没有正文,没有关键词,没有摘要,就孤零零三个字母。这种输入条件放在任何一个技术社区里,都像是有人扔了…

📰

基于YOLOv8的路面裂缝检测系统:中英文双版实战

1. 路面裂缝检测这个方向,为什么值得用YOLOv8重做一遍道路养护这个行当里,裂缝检测一直是个绕不开的活。早些年靠老师傅拿粉笔在路面上画框、拿本子记桩号,后来有了半自动的图像处理工具,但真正让一线养护队头疼的问题始终没变&am…

📰

Portabase数据库恢复教程:如何从备份快照快速找回丢失的数据

【免费下载链接】portabase Portabase - Database backup & restore tool for PostgreSQL, MySQL, MsSQL, MariaDB, Firebird SQL, SQLite, MongoDB, Redis and Docker Volume 项目地址: https://gitcode.com/gh_mirrors/por/portabase 点击查看 免费下载 Por…

📰

cosmos-sdk 系统测试入门:从查询、JSON 断言到 Genesis 与交易驱动的状态测试

区块链 【免费下载链接】cosmos-sdk Framework for building performant, customizable blockchains with native interoperability 项目地址: https://gitcode.com/gh_mirrors/co/cosmos-sdk 点击查看 免费下载 本指南基于 cosmos-sdk 仓库中的 tools/systemtests…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬