尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

F´ DpManager 组件单元测试设计解析:基于 STest 规则引擎的抽象状态、规则组与随机场景测试

发布时间:2026/9/24 19:52:55

资讯中心
01
ARTICLE

F´ DpManager 组件单元测试设计解析:基于 STest 规则引擎的抽象状态、规则组与随机场景测试

F´ DpManager 组件单元测试设计解析:基于 STest 规则引擎的抽象状态、规则组与随机场景测试
嵌入式系统编程【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址https://gitcode.com/gh_mirrors/fp/fprime点击查看免费下载导读Svc::DpManager是 F´ 飞行软件框架中的活动组件active component负责为各客户组件统一管理数据产品data product缓冲区既支持同步取缓冲区也支持异步请求缓冲区并将填充完成的数据产品转发给下游处理组件。本文以 Svc/DpManager/test/ut/README.md 为骨架完整解析该组件单元测试的设计从抽象状态Abstract State与规则组Rule Groups的建模到DpManagerTester/TestState的类层次、规则类的宏生成机制再到 10000 步随机场景测试的编排方式。读完本文你将掌握 F´ 官方组件测试中基于STest规则引擎的规则-状态-断言测试方法论并能将同一套模式复用于自己组件的单元测试开发。1. 测试对象与文档定位本文所述的单元测试位于 Svc/DpManager/test/ut/其目录结构如下Svc/DpManager/test/ut/ ├── README.md # 本文主体测试设计文档 ├── AbstractState.hpp # 抽象状态定义 ├── DpManagerTestMain.cpp # gtest 测试入口 ├── DpManagerTester.cpp # 测试装置harness实现 ├── DpManagerTester.hpp # 测试装置头文件 ├── Rules/ # 各规则组及测试器 ├── Scenarios/ # 随机场景测试器 └── TestState/ # 测试状态规则前置条件与动作实现被测试组件Svc::DpManager的端口与接口定义在 Svc/DpManager/DpManager.fpp 中实现在 Svc/DpManager/DpManager.cpp 中。整个测试体系建立在 F´ 的STestScenario Testing框架之上测试文档的定位不是罗列断言而是用抽象状态 规则 场景三层模型来描述被测系统行为——这与传统的写死输入-期望输出式单元测试有本质区别规则带有前置条件precondition场景由测试器Tester随机挑选可执行的规则逐步推进最终以状态机的方式覆盖系统全部合法行为路径。2. 抽象状态Abstract State2.1 状态类型BufferGetStatus测试装置中用于模拟缓冲区获取结果的枚举类型BufferGetStatus测试装置中bufferGet响应状态取值为VALID缓冲区可用或INVALID缓冲区不可用。该枚举定义于 Svc/DpManager/test/ut/AbstractState.hppenum class BufferGetStatus { VALID, //! Valid INVALID //! Invalid };它模拟的是上游 Buffer Manager缓冲区管理器对bufferGetOut端口调用的两种可能响应是驱动被测组件成功/失败两条分支的关键开关。2.2 状态变量抽象状态的全部变量及初始值如下表与文档一致并标注源码出处变量类型描述初始值bufferGetStatusBufferGetStatus缓冲区获取状态VALIDbufferSizeOptionFwSizeType当前缓冲区大小none缺省时由随机数生成器决定NumSuccessfulAllocationsOnChangeChannelU32成功分配缓冲区次数0NumFailedAllocationsOnChangeChannelU32失败分配缓冲区次数0NumDataProductsOnChangeChannelU32已处理的数据产品数量0NumBytesOnChangeChannelU64已处理的字节数0bufferGetOutPortNumOptOptionFwIndexType最近一次使用bufferGetOut的端口号在from_bufferGetOut端口处理函数中更新noneproductResponseOutPortNumOptOptionFwIndexType最近一次使用productResponseOut的端口号在from_productResponseOut处理函数中更新noneproductSendOutPortNumOptOptionFwIndexType最近一次使用productSendOut的端口号在from_productSendOut处理函数中更新nonebufferAllocationFailedEventCountFwSizeType自上次节流throttle清除以来缓冲区分配失败事件的发生次数0几个值得注意的设计细节均可从源码印证OptionT语义bufferSize、三个*PortNumOpt均使用TestUtils::OptionT见 TestUtils/Option.hpp。bufferSize为none时规则动作中会通过STest::Pick::lowerUpper(MIN_BUFFER_SIZE, MAX_BUFFER_SIZE)在最小/最大边界之间随机取值端口号记录为none意味着尚未发生过该端口的调用用作断言前置条件。边界常量AbstractState.hpp 中定义了MIN_BUFFER_SIZE 1与MAX_BUFFER_SIZE 1024每个测试用例都会在最小与最大缓冲区大小两个边界上各执行一遍规则覆盖缓冲区尺寸的极端情况。OnChangeChannel语义NumSuccessfulAllocations等四个变量是TestUtils::OnChangeChannelU32/U64类型。DpManagerTester::checkTelemetry()DpManagerTester.cpp在schedIn::OK规则动作中被调用它会比较通道值的变化前后状态仅当值发生变化时才断言遥测telemetry更新一次否则断言无遥测发出。这精确对应 DpManager.fpp 中四条遥测通道的update on change语义。缓冲数据区AbstractState还内置了U8 bufferData[MAX_BUFFER_SIZE]数组见 AbstractState.hpp当bufferGetStatus VALID时from_bufferGetOut_handler会将该数组作为缓冲区数据指针返回并校验size MAX_BUFFER_SIZE见 DpManagerTester.cpp构造出一个合法的Fw::Buffer当状态为INVALID时则原样返回一个无效缓冲区从而模拟分配失败。3. 规则组Rule Groups测试文档将全部测试行为划分为 6 个规则组每个规则组负责驱动被测组件的一个端口或命令。下面逐一展开并结合 DpManager.cpp 的实现说明断言背后的业务逻辑。3.1 BufferGetStatus辅助规则组该规则组管理测试装置中的缓冲区获取状态是两个辅助规则helper rule不直接验证需求而是为其他规则组构造前置条件。3.1.1 Valid将缓冲区获取状态置为VALID模拟缓冲区可用的系统状态。前置条件bufferGetStatus ! VALID动作bufferGetStatus VALID测试序列① 应用规则BufferGetStatus::Invalid② 应用规则BufferGetStatus::Valid验证的需求无辅助规则3.1.2 Invalid将缓冲区获取状态置为INVALID模拟无缓冲区可用的系统状态。前置条件bufferGetStatus ! INVALID动作bufferGetStatus INVALID测试序列应用规则BufferGetStatus::Invalid验证的需求无辅助规则两条规则互相把对方设为前置条件保证状态迁移只能严格交替进行符合状态机建模的规范。3.2 ProductGetIn同步取缓冲区该规则组向productGetIn端口发送测试输入。productGetIn是同步输入端口类型为[DpManagerNumPorts] Fw.DpGet见 DpManager.fpp。被测组件在 DpManager.cpp 中的处理逻辑为Fw::Success DpManager::productGetIn_handler(const NATIVE_INT_TYPE portNum, FwDpIdType id, FwSizeType size, Fw::Buffer buffer) { return this-getBuffer(portNum, id, size, buffer); }即直接委托私有辅助函数getBuffer同步返回Fw::Success状态并把缓冲区填入调用方传入的引用。getBuffer的核心逻辑DpManager.cppFw::Success DpManager::getBuffer(FwIndexType portNum, FwDpIdType id, FwSizeType size, Fw::Buffer buffer) { Fw::Success status(Fw::Success::FAILURE); buffer this-bufferGetOut_out(portNum, static_castU32(size)); if (buffer.isValid()) { this-numSuccessfulAllocations; status Fw::Success::SUCCESS; } else { this-numFailedAllocations; this-log_WARNING_HI_BufferAllocationFailed(id); } return status; }3.2.1 BufferInvalid分配失败路径在测试装置返回无效缓冲区的状态下调用productGetIn。前置条件bufferGetStatus INVALID动作逐条对照实现清空历史记录令S为bufferSize若为none则取随机值以随机端口号N、随机容器 IDI、大小S调用productGetIn断言调用返回状态为FAILURE事件节流断言若bufferAllocationFailedEventCount DpManagerComponentBase::EVENTID_BUFFERALLOCATIONFAILED_THROTTLE则断言事件历史仅含 1 条、BufferAllocationFailed事件历史仅含 1 条且索引 0 处携带容器 IDI然后将计数加 1否则断言事件历史为空——这正是throttle 10节流语义见 DpManager.fpp的测试体现事件只在前 10 次失败时上报递增NumFailedAllocations断言 from 端口历史含 1 项、bufferGetOut历史含 1 项且索引 0 处大小为S断言bufferGetOutPortNumOpt为N即被调用的是测试所选的端口号。测试序列① 应用BufferGetStatus::Invalid② 设置bufferSize MIN_BUFFER_SIZE③ 应用ProductGetIn::BufferInvalid④ 应用SchedIn::OK⑤ 设置bufferSize MAX_BUFFER_SIZE⑥ 应用ProductGetIn::BufferInvalid⑦ 应用SchedIn::OK验证的需求SVC-DPMANAGER-001、SVC-DPMANAGER-004注意SchedIn::OK在每次规则执行后被应用目的是触发遥测输出并校验计数通道的更新。3.2.2 BufferValid成功分配路径在测试装置返回有效缓冲区的状态下调用productRequestIn文档原文如此端口语义为productGetIn的成功分支。前置条件bufferGetStatus VALID动作清空历史以随机端口号N、随机 IDI、大小S调用productRequestIn断言返回状态为SUCCESS断言事件历史为空成功路径不产生事件递增NumSuccessfulAllocations断言 from 端口历史含 1 项、bufferGetOut历史含 1 项且索引 0 处大小为S断言bufferGetOutPortNumOpt为N。测试序列① 设置bufferSize MIN_BUFFER_SIZE② 应用ProductGetIn::BufferValid③ 应用SchedIn::OK④ 设置bufferSize MAX_BUFFER_SIZE⑤ 应用ProductGetIn::BufferValid⑥ 应用SchedIn::OK验证的需求SVC-DPMANAGER-001、SVC-DP-MANAGER-0043.3 ProductRequestIn异步请求缓冲区该规则组向异步输入端口productRequestIn类型[DpManagerNumPorts] Fw.DpRequest发送请求。与同步路径不同异步路径不直接返回缓冲区而是通过productResponseOut输出端口把(id, buffer, status)回送给客户组件见 DpManager.cppvoid DpManager::productRequestIn_handler(const NATIVE_INT_TYPE portNum, FwDpIdType id, FwSizeType size) { Fw::Buffer buffer; const Fw::Success status this-getBuffer(portNum, id, size, buffer); this-productResponseOut_out(portNum, id, buffer, status); }因此该规则组的断言数量比ProductGetIn多除了bufferGetOut历史外还必须验证productResponseOut历史及对应端口号。3.3.1 BufferInvalid分配失败路径前置条件bufferGetStatus INVALID动作清空历史以随机端口号N、随机 IDI、大小S调用productRequestIn事件节流断言若bufferAllocationFailedEventCount EVENTID_BUFFERALLOCATIONFAILED_THROTTLE则断言事件历史为 1 条且BufferAllocationFailed历史索引 0 处含I并递增计数否则断言事件历史为空递增NumFailedAllocations断言 from 端口历史含2 项bufferGetOut与productResponseOut各一项断言bufferGetOut历史含 1 项、索引 0 处大小为S且bufferGetOutPortNumOpt为N断言productResponseOut历史含 1 项索引 0 处为预期的无效缓冲区及状态FAILURE且productResponseOutPortNumOpt为N。测试序列① 应用BufferGetStatus::Invalid② 设置bufferSize MIN_BUFFER_SIZE③ 应用ProductRequestIn::BufferInvalid④ 应用SchedIn::OK⑤ 设置bufferSize MAX_BUFFER_SIZE⑥ 应用ProductRequestIn::BufferInvalid⑦ 应用SchedIn::OK验证的需求SVC-DPMANAGER-002、SVC-DPMANAGER-0043.3.2 BufferValid成功分配路径前置条件bufferGetStatus VALID动作清空历史以随机端口号N、随机 IDI、大小S调用productRequestIn断言事件历史为空递增NumSuccessfulAllocations断言 from 端口历史含 2 项断言bufferGetOut历史含 1 项、索引 0 处大小为SbufferGetOutPortNumOpt为N断言productResponseOut历史含 1 项、索引 0 处为预期的有效缓冲区及状态SUCCESSproductResponseOutPortNumOpt为N。测试序列① 设置bufferSize MIN_BUFFER_SIZE② 应用ProductRequestIn::BufferValid③ 应用SchedIn::OK④ 设置bufferSize MAX_BUFFER_SIZE⑤ 应用ProductRequestIn::BufferValid⑥ 应用SchedIn::OK验证的需求SVC-DPMANAGER-002、SVC-DP-MANAGER-0043.4 ProductSendIn转发数据产品该规则组向异步输入端口productSendIn类型[DpManagerNumPorts] Fw.DpSend发送已填充的数据产品缓冲区。组件逻辑DpManager.cpp只做透传更新统计变量后将缓冲区原样转发到productSendOut类型Fw.BufferSend供下游如Svc::BufferAccumulator、Svc::DpWriter等处理。3.4.1 OK前置条件true无状态限制动作清空历史令S为bufferSize或随机值以随机端口号N、随机 IDI、大小为S的缓冲区B调用productSendIn断言事件历史为空转发是静默操作递增NumDataProducts文档原文写作NumDataBroducts实际对应 DpManager.fpp 中的遥测通道NumDataProducts按B的大小增加NumBytes断言 from 端口历史含 1 项、productSendOut历史含 1 项且索引 0 处为B断言productSendOutPortNumOpt为N。测试序列① 设置bufferSize MIN_BUFFER_SIZE② 应用ProductSendIn::OK③ 应用SchedIn::OK④ 设置bufferSize MAX_BUFFER_SIZE⑤ 应用ProductSendIn::OK⑥ 应用SchedIn::OK验证的需求SVC-DPMANAGER-003、SVC-DPMANAGER-0043.5 SchedIn调度与遥测该规则组向schedIn端口Svc.Sched发送调度输入。组件实现DpManager.cpp将四个内部计数通过tlmWrite_*逐条写为遥测——这是唯一产生遥测的时机因此被作为遥测检查点插入到几乎所有其他规则之后。3.5.1 OK前置条件true动作清空历史以随机 context 调用schedIn检查遥测即前文所述checkTelemetry()基于OnChangeChannel的前后值比较断言变化即上报、不变即静默。测试序列应用规则SchedIn::OK验证的需求SVC-DPMANAGER-0043.6 CLEAR_EVENT_THROTTLE清除事件节流该规则组测试CLEAR_EVENT_THROTTLE命令opcode 0x00见 DpManager.fpp。组件实现DpManager.cpp调用自动生成的节流清除函数并回送命令成功响应void DpManager::CLEAR_EVENT_THROTTLE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { this-log_WARNING_HI_BufferAllocationFailed_ThrottleClear(); this-cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); }3.6.1 OK前置条件true动作清空历史发送命令CLEAR_EVENT_THROTTLE检查命令响应应为OK断言DpManagerComponentBase::m_BufferAllocationFailedThrottle 0节流计数被清零将抽象状态中的bufferAllocationFailedEventCount置 0与组件内部计数保持同步。测试序列验证节流与清除的完整闭环应用BufferGetStatus::Invalid应用ProductRequestIn::BufferInvalid共EVENTID_BUFFERALLOCATIONFAILED_THROTTLE 1次此时第 11 次失败起事件已被抑制应用CLEAR_EVENT_THROTTLE::OK节流计数归零再次应用ProductRequestIn::BufferInvalid节流解除后事件应重新开始上报。验证的需求SVC-DPMANAGER-0064. 测试实现架构Implementation文档第三部分揭示了这套测试的工程组织方式被测组件、抽象状态、规则逻辑与测试器分别封装在不同类中大量模板代码通过宏展开生成手写代码只保留规则语义本身。4.1 DpManagerTester 与 TestStateDpManagerTester派生自自动生成的DpManagerGTestBase持有抽象状态abstractState与被测组件component见 DpManagerTester.hpp。关键配置常量DpManagerTester.hppMAX_HISTORY_SIZE 10事件/遥测/端口输出历史的最大容量TEST_INSTANCE_ID 0被测组件实例 IDTEST_INSTANCE_QUEUE_DEPTH 10活动组件消息队列深度。TestState是DpManagerTester的派生类规则的前置条件与动作都在TestState中实现以便使用DpManagerGTestBase提供的函数与断言宏。其头文件是样板代码通过宏TEST_STATE_DEF_RULE(GROUP_NAME, RULE_NAME)批量声明规则函数见 TestState/TestState.hpp#define TEST_STATE_DEF_RULE(GROUP_NAME, RULE_NAME) \ bool precondition__##GROUP_NAME##__##RULE_NAME() const; \ void action__##GROUP_NAME##__##RULE_NAME();而TestState的函数定义则完全手写逐条编码第 3 节描述的规则前置条件与动作。上图为BufferGetStatus规则组的前置条件与动作示意继承链为DpManagerGTestBase → DpManagerTester → TestState。4.2 Rules 类每个规则是一个派生自STest::RuleTestState的类同样是宏展开生成的样板代码其precondition(state)与action(state)只是转调TestState中对应的precondition__GROUP__RULE()与action__GROUP__RULE()手写函数。规则类头文件示例见 Rules/BufferGetStatus.hppRules.hpp汇总全部规则。4.3 规则组测试器Rule Group Testers每个规则组对应一个 Tester见 Rules/Testers.hpp内含BufferGetStatus、CLEAR_EVENT_THROTTLE、ProductGetIn、ProductRequestIn、ProductSendIn、SchedIn六个 Tester。每个 Tester 定义本组规则、创建一个TestState并为每条规则提供一个测试场景test scenario。以BufferGetStatus组为例4.4 随机场景测试器Random Scenario Tester随机场景测试器将所有规则实例化并组合成随机场景Scenarios/Random.cppRules::BufferGetStatus::Invalid bufferGetStatusInvalid; Rules::BufferGetStatus::Valid bufferGetStatusValid; Rules::CLEAR_EVENT_THROTTLE::OK clearEventThrottleOK; Rules::ProductRequestIn::BufferInvalid productRequestInBufferInvalid; Rules::ProductRequestIn::BufferValid productRequestInBufferValid; Rules::ProductSendIn::OK productSendInOK; Rules::SchedIn::OK schedInOK; void Tester::run(FwSizeType maxNumSteps) { STest::RuleTestState* rules[] { ... }; // 上述 7 条规则 STest::RandomScenarioTestState scenario(RandomScenario, rules, ...); STest::BoundedScenarioTestState boundedScenario(BoundedRandomScenario, scenario, maxNumSteps); const U32 numSteps boundedScenario.run(this-testState); printf(Ran %u steps.\n, numSteps); }它使用STest::RandomScenario与STest::BoundedScenario的组合每一步从全部规则中挑选前置条件为真的规则随机执行直至达到步数上限。Scenarios::Random::Tester只暴露run(maxNumSteps)接口私有持有TestState。5. 测试用例清单与需求追溯测试入口 DpManagerTestMain.cpp 将上述规则映射为 9 个 gtest 用例并通过REQUIREMENT(SVC-DPMANAGER-XXX)宏建立需求追溯gtest 用例场景追溯需求BufferGetStatus.Invalid/.Valid辅助规则单独验证无ProductGetIn.BufferInvalid/.BufferValid同步取缓冲区失败/成功SVC-DPMANAGER-001、SVC-DPMANAGER-004ProductRequestIn.BufferInvalid/.BufferValid异步请求缓冲区失败/成功SVC-DPMANAGER-002、SVC-DPMANAGER-004ProductSendIn.OK转发数据产品SVC-DPMANAGER-003、SVC-DPMANAGER-004SchedIn.OK调度与遥测SVC-DPMANAGER-004CLEAR_EVENT_THROTTLE.OK清除事件节流SVC-DPMANAGER-006Scenarios.Random7 条规则随机组合10000 步SVC-DPMANAGER-002/003/004对应组件的正式需求详见 Svc/DpManager/docs/sdd.md需求编号描述验证方法SVC-DPMANAGER-001提供同步请求并接收数据产品缓冲区的端口数组单元测试SVC-DPMANAGER-002提供接收并异步响应数据产品缓冲区请求的端口数组单元测试SVC-DPMANAGER-003接收数据产品缓冲区并转发供进一步处理单元测试SVC-DPMANAGER-004提供成功分配数、失败分配数与处理数据量的遥测单元测试SVC-DPMANAGER-006经测试追溯清除事件节流的能力单元测试main函数在初始化 gtest 后调用STest::Random::seed()为随机场景设置随机种子DpManagerTestMain.cpp使随机场景在每次运行时有不同的执行轨迹。该测试可通过 F´ 标准的单元测试构建流程运行如fprime-util build --ut后执行生成的测试二进制也可以直接以 gtest 入口编译运行。6. 从测试反观组件设计端口连通与状态机闭环最后将测试装置与组件实现对照可以清晰还原 DpManager 的运行时行为这也是理解该测试文档价值的关键三端口联动from_bufferGetOut_handler、from_productResponseOut_handler、from_productSendOut_handler三个 from 端口处理函数DpManagerTester.cpp分别模拟上游 Buffer Manager 与下游消费者的响应并把实际调用的端口号记录到抽象状态供断言核对——这保证测试不仅验证数据正确还验证调用发生在预期的端口上。节流机制的完整闭环BufferAllocationFailed事件在 DpManager.fpp 中声明为severity warning high且throttle 10即每 10 次失败才上报 1 次。测试通过失败 11 次 → 断言事件被抑制 → 发送CLEAR_EVENT_THROTTLE→ 再失败 1 次 → 断言事件恢复的序列验证了组件级节流与命令级清除的联动正确性。端口数组规模所有业务端口均为[DpManagerNumPorts]数组该常量在 config/AcConstants.fpp 中配置为 5即单个DpManager实例可同时支持 5 组客户-缓冲区连接测试用随机端口号N验证了端口选择逻辑见 Svc/DpManager/docs/sdd.md 第 5.1 节的拓扑示例。7. 总结Svc::DpManager的单元测试是 F´ 框架内基于 STest 规则引擎的组件级测试的典型范本其设计可以概括为四个层次抽象状态层AbstractState用枚举 Option/OnChangeChannel精确建模被测系统的可观状态规则层RulesTestState前置条件约束规则的合法触发时机动作编码调用 断言历史/事件/遥测 更新计数的完整验证逻辑场景层Tester每个规则组提供确定性测试场景Random::Tester则把所有规则组合为可复现的随机漫步追溯层REQUIREMENT宏把每个用例映射到SVC-DPMANAGER-00X需求形成可审计的验证闭环。这套模式不仅覆盖了 DpManager 全部端口productGetIn、productRequestIn、productSendIn、schedIn、命令CLEAR_EVENT_THROTTLE、事件BufferAllocationFailed与遥测四条通道的成败路径与边界条件最小/最大缓冲区也为读者在自己的 F´ 组件上编写同等质量的测试提供了可直接照搬的工程模板。赞分享嵌入式系统编程【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址https://gitcode.com/gh_mirrors/fp/fprime点击查看免费下载相关推荐F´ (F Prime) DpManager 组件基于规则的单元测试Abstract State、规则组与随机场景解析F´ F Prime DpManager 组件基于规则的单元测试Abstract State、规则组与随机场景解析 导读 本文以 Svc/DpManager/嵌入式系统编程F´ 组件测试框架 STest 完全指南用规则与场景驱动的结构化单元测试F´ 组件测试框架 STest 完全指南用规则与场景驱动的结构化单元测试 导读 STest 是 F´F Prime飞行软件与嵌入式系统框架内置的测试框架嵌入式系统编程Svc::DpWriter 组件单元测试深度解析基于 STest 规则引擎的数据产品落盘验证Svc::DpWriter 组件单元测试深度解析基于 STest 规则引擎的数据产品落盘验证 Svc::DpWriter 是 F´F Prime飞行软件框嵌入式系统编程上一篇CMake优化编译选项完全指南-O0到-O3的性能与调试权衡下一篇awesome-prometheus-alerts项目结构解析代码组织与实现原理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。