• 【原创】Jmeter实战 实用手册


    编写此文,主要想通过这种方式,记录自己研究Jmeter的过程和实用方法技巧。2022.01.20

    第一步:要使用Jmeter,先从官方下载最新版本,下载链接Apache JMeter - Download Apache JMeter

    第二步:解压压缩包后,运行bin文件夹下的jmeter.bat或jmeter.sh,根据所使用的电脑平台决定;这里得提前安装JDK/JRE,具体的方法就不说了,太过于基础了;

    第三步:下载安装Jmeter插件管理器Install :: JMeter-Plugins.org,将jar包放入Jmeter的lib/ext文件夹目录下,重启Jmeter;(Download plugins-manager.jar and put it into lib/ext directory, then restart JMeter.)

    第四步:用Jmeter插件管理器,

    第五步:安装自己所需的插件,常用插件列表参考如下:

    第六步:根据自己所需做的测试对象类型,选择不同的协议(插件)进行脚本开发(录制);这里以HTTP协议脚本录制为例,测试计划(TestPlan)中需要的基础组件参考如下:

      1 <?xml version="1.0" encoding="UTF-8"?>
      2 <jmeterTestPlan version="1.2" properties="5.0" jmeter="5.4.1">
      3   <hashTree>
      4     <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
      5       <stringProp name="TestPlan.comments"></stringProp>
      6       <boolProp name="TestPlan.functional_mode">false</boolProp>
      7       <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
      8       <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
      9       <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
     10         <collectionProp name="Arguments.arguments"/>
     11       </elementProp>
     12       <stringProp name="TestPlan.user_define_classpath"></stringProp>
     13     </TestPlan>
     14     <hashTree>
     15       <ProxyControl guiclass="ProxyControlGui" testclass="ProxyControl" testname="HTTP(S) Test Script Recorder" enabled="true">
     16         <stringProp name="ProxyControlGui.port">8888</stringProp>
     17         <collectionProp name="ProxyControlGui.exclude_list"/>
     18         <collectionProp name="ProxyControlGui.include_list"/>
     19         <boolProp name="ProxyControlGui.capture_http_headers">true</boolProp>
     20         <intProp name="ProxyControlGui.grouping_mode">0</intProp>
     21         <boolProp name="ProxyControlGui.add_assertion">false</boolProp>
     22         <stringProp name="ProxyControlGui.sampler_type_name"></stringProp>
     23         <boolProp name="ProxyControlGui.sampler_redirect_automatically">false</boolProp>
     24         <boolProp name="ProxyControlGui.sampler_follow_redirects">true</boolProp>
     25         <boolProp name="ProxyControlGui.use_keepalive">true</boolProp>
     26         <boolProp name="ProxyControlGui.detect_graphql_request">true</boolProp>
     27         <boolProp name="ProxyControlGui.sampler_download_images">false</boolProp>
     28         <intProp name="ProxyControlGui.proxy_http_sampler_naming_mode">0</intProp>
     29         <stringProp name="ProxyControlGui.default_encoding"></stringProp>
     30         <stringProp name="ProxyControlGui.proxy_prefix_http_sampler_name"></stringProp>
     31         <stringProp name="ProxyControlGui.proxy_pause_http_sampler"></stringProp>
     32         <boolProp name="ProxyControlGui.notify_child_sl_filtered">false</boolProp>
     33         <boolProp name="ProxyControlGui.regex_match">false</boolProp>
     34         <stringProp name="ProxyControlGui.content_type_include"></stringProp>
     35         <stringProp name="ProxyControlGui.content_type_exclude"></stringProp>
     36       </ProxyControl>
     37       <hashTree/>
     38       <com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup guiclass="com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroupGui" testclass="com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup" testname="bzm - Concurrency Thread Group" enabled="true">
     39         <elementProp name="ThreadGroup.main_controller" elementType="com.blazemeter.jmeter.control.VirtualUserController"/>
     40         <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
     41         <stringProp name="TargetLevel"></stringProp>
     42         <stringProp name="RampUp"></stringProp>
     43         <stringProp name="Steps"></stringProp>
     44         <stringProp name="Hold"></stringProp>
     45         <stringProp name="LogFilename"></stringProp>
     46         <stringProp name="Iterations"></stringProp>
     47         <stringProp name="Unit">M</stringProp>
     48       </com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup>
     49       <hashTree>
     50         <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
     51           <collectionProp name="HeaderManager.headers"/>
     52         </HeaderManager>
     53         <hashTree/>
     54         <CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager" enabled="true">
     55           <collectionProp name="CookieManager.cookies"/>
     56           <boolProp name="CookieManager.clearEachIteration">false</boolProp>
     57           <boolProp name="CookieManager.controlledByThreadGroup">false</boolProp>
     58         </CookieManager>
     59         <hashTree/>
     60         <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="001_Login" enabled="true">
     61           <boolProp name="TransactionController.includeTimers">false</boolProp>
     62           <boolProp name="TransactionController.parent">false</boolProp>
     63         </TransactionController>
     64         <hashTree>
     65           <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true">
     66             <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
     67               <collectionProp name="Arguments.arguments"/>
     68             </elementProp>
     69             <stringProp name="HTTPSampler.domain"></stringProp>
     70             <stringProp name="HTTPSampler.port"></stringProp>
     71             <stringProp name="HTTPSampler.protocol"></stringProp>
     72             <stringProp name="HTTPSampler.contentEncoding"></stringProp>
     73             <stringProp name="HTTPSampler.path"></stringProp>
     74             <stringProp name="HTTPSampler.method">GET</stringProp>
     75             <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
     76             <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
     77             <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
     78             <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
     79             <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
     80             <stringProp name="HTTPSampler.connect_timeout"></stringProp>
     81             <stringProp name="HTTPSampler.response_timeout"></stringProp>
     82           </HTTPSamplerProxy>
     83           <hashTree>
     84             <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="JSON Extractor" enabled="true">
     85               <stringProp name="JSONPostProcessor.referenceNames"></stringProp>
     86               <stringProp name="JSONPostProcessor.jsonPathExprs"></stringProp>
     87               <stringProp name="JSONPostProcessor.match_numbers"></stringProp>
     88             </JSONPostProcessor>
     89             <hashTree/>
     90             <BoundaryExtractor guiclass="BoundaryExtractorGui" testclass="BoundaryExtractor" testname="Boundary Extractor" enabled="true">
     91               <stringProp name="BoundaryExtractor.useHeaders">false</stringProp>
     92               <stringProp name="BoundaryExtractor.refname"></stringProp>
     93               <stringProp name="BoundaryExtractor.lboundary"></stringProp>
     94               <stringProp name="BoundaryExtractor.rboundary"></stringProp>
     95               <stringProp name="BoundaryExtractor.default"></stringProp>
     96               <boolProp name="BoundaryExtractor.default_empty_value">false</boolProp>
     97               <stringProp name="BoundaryExtractor.match_number"></stringProp>
     98             </BoundaryExtractor>
     99             <hashTree/>
    100             <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor" enabled="true">
    101               <stringProp name="RegexExtractor.useHeaders">false</stringProp>
    102               <stringProp name="RegexExtractor.refname"></stringProp>
    103               <stringProp name="RegexExtractor.regex"></stringProp>
    104               <stringProp name="RegexExtractor.template"></stringProp>
    105               <stringProp name="RegexExtractor.default"></stringProp>
    106               <stringProp name="RegexExtractor.match_number"></stringProp>
    107             </RegexExtractor>
    108             <hashTree/>
    109             <JSR223PostProcessor guiclass="TestBeanGUI" testclass="JSR223PostProcessor" testname="JSR223 PostProcessor" enabled="true">
    110               <stringProp name="cacheKey">true</stringProp>
    111               <stringProp name="filename"></stringProp>
    112               <stringProp name="parameters"></stringProp>
    113               <stringProp name="script"></stringProp>
    114               <stringProp name="scriptLanguage">groovy</stringProp>
    115             </JSR223PostProcessor>
    116             <hashTree/>
    117           </hashTree>
    118           <DebugSampler guiclass="TestBeanGUI" testclass="DebugSampler" testname="Debug Sampler" enabled="true">
    119             <boolProp name="displayJMeterProperties">false</boolProp>
    120             <boolProp name="displayJMeterVariables">true</boolProp>
    121             <boolProp name="displaySystemProperties">false</boolProp>
    122           </DebugSampler>
    123           <hashTree/>
    124         </hashTree>
    125         <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="002_TestCase_01" enabled="true">
    126           <boolProp name="TransactionController.includeTimers">false</boolProp>
    127           <boolProp name="TransactionController.parent">false</boolProp>
    128         </TransactionController>
    129         <hashTree/>
    130       </hashTree>
    131       <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
    132         <boolProp name="ResultCollector.error_logging">false</boolProp>
    133         <objProp>
    134           <name>saveConfig</name>
    135           <value class="SampleSaveConfiguration">
    136             <time>true</time>
    137             <latency>true</latency>
    138             <timestamp>true</timestamp>
    139             <success>true</success>
    140             <label>true</label>
    141             <code>true</code>
    142             <message>true</message>
    143             <threadName>true</threadName>
    144             <dataType>true</dataType>
    145             <encoding>false</encoding>
    146             <assertions>true</assertions>
    147             <subresults>true</subresults>
    148             <responseData>false</responseData>
    149             <samplerData>false</samplerData>
    150             <xml>false</xml>
    151             <fieldNames>true</fieldNames>
    152             <responseHeaders>false</responseHeaders>
    153             <requestHeaders>false</requestHeaders>
    154             <responseDataOnError>false</responseDataOnError>
    155             <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
    156             <assertionsResultsToSave>0</assertionsResultsToSave>
    157             <bytes>true</bytes>
    158             <sentBytes>true</sentBytes>
    159             <url>true</url>
    160             <threadCounts>true</threadCounts>
    161             <idleTime>true</idleTime>
    162             <connectTime>true</connectTime>
    163           </value>
    164         </objProp>
    165         <stringProp name="filename"></stringProp>
    166       </ResultCollector>
    167       <hashTree/>
    168       <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass="kg.apc.jmeter.vizualizers.TransactionsPerSecondGui" testclass="kg.apc.jmeter.vizualizers.CorrectedResultCollector" testname="jp@gc - Transactions per Second" enabled="true">
    169         <boolProp name="ResultCollector.error_logging">false</boolProp>
    170         <objProp>
    171           <name>saveConfig</name>
    172           <value class="SampleSaveConfiguration">
    173             <time>true</time>
    174             <latency>true</latency>
    175             <timestamp>true</timestamp>
    176             <success>true</success>
    177             <label>true</label>
    178             <code>true</code>
    179             <message>true</message>
    180             <threadName>true</threadName>
    181             <dataType>true</dataType>
    182             <encoding>false</encoding>
    183             <assertions>true</assertions>
    184             <subresults>true</subresults>
    185             <responseData>false</responseData>
    186             <samplerData>false</samplerData>
    187             <xml>false</xml>
    188             <fieldNames>true</fieldNames>
    189             <responseHeaders>false</responseHeaders>
    190             <requestHeaders>false</requestHeaders>
    191             <responseDataOnError>false</responseDataOnError>
    192             <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
    193             <assertionsResultsToSave>0</assertionsResultsToSave>
    194             <bytes>true</bytes>
    195             <sentBytes>true</sentBytes>
    196             <url>true</url>
    197             <threadCounts>true</threadCounts>
    198             <idleTime>true</idleTime>
    199             <connectTime>true</connectTime>
    200           </value>
    201         </objProp>
    202         <stringProp name="filename"></stringProp>
    203         <longProp name="interval_grouping">1000</longProp>
    204         <boolProp name="graph_aggregated">false</boolProp>
    205         <stringProp name="include_sample_labels"></stringProp>
    206         <stringProp name="exclude_sample_labels"></stringProp>
    207         <stringProp name="start_offset"></stringProp>
    208         <stringProp name="end_offset"></stringProp>
    209         <boolProp name="include_checkbox_state">false</boolProp>
    210         <boolProp name="exclude_checkbox_state">false</boolProp>
    211       </kg.apc.jmeter.vizualizers.CorrectedResultCollector>
    212       <hashTree/>
    213       <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass="kg.apc.jmeter.vizualizers.ResponseTimesOverTimeGui" testclass="kg.apc.jmeter.vizualizers.CorrectedResultCollector" testname="jp@gc - Response Times Over Time" enabled="true">
    214         <boolProp name="ResultCollector.error_logging">false</boolProp>
    215         <objProp>
    216           <name>saveConfig</name>
    217           <value class="SampleSaveConfiguration">
    218             <time>true</time>
    219             <latency>true</latency>
    220             <timestamp>true</timestamp>
    221             <success>true</success>
    222             <label>true</label>
    223             <code>true</code>
    224             <message>true</message>
    225             <threadName>true</threadName>
    226             <dataType>true</dataType>
    227             <encoding>false</encoding>
    228             <assertions>true</assertions>
    229             <subresults>true</subresults>
    230             <responseData>false</responseData>
    231             <samplerData>false</samplerData>
    232             <xml>false</xml>
    233             <fieldNames>true</fieldNames>
    234             <responseHeaders>false</responseHeaders>
    235             <requestHeaders>false</requestHeaders>
    236             <responseDataOnError>false</responseDataOnError>
    237             <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
    238             <assertionsResultsToSave>0</assertionsResultsToSave>
    239             <bytes>true</bytes>
    240             <sentBytes>true</sentBytes>
    241             <url>true</url>
    242             <threadCounts>true</threadCounts>
    243             <idleTime>true</idleTime>
    244             <connectTime>true</connectTime>
    245           </value>
    246         </objProp>
    247         <stringProp name="filename"></stringProp>
    248         <longProp name="interval_grouping">500</longProp>
    249         <boolProp name="graph_aggregated">false</boolProp>
    250         <stringProp name="include_sample_labels"></stringProp>
    251         <stringProp name="exclude_sample_labels"></stringProp>
    252         <stringProp name="start_offset"></stringProp>
    253         <stringProp name="end_offset"></stringProp>
    254         <boolProp name="include_checkbox_state">false</boolProp>
    255         <boolProp name="exclude_checkbox_state">false</boolProp>
    256       </kg.apc.jmeter.vizualizers.CorrectedResultCollector>
    257       <hashTree/>
    258       <ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="Aggregate Report" enabled="true">
    259         <boolProp name="ResultCollector.error_logging">false</boolProp>
    260         <objProp>
    261           <name>saveConfig</name>
    262           <value class="SampleSaveConfiguration">
    263             <time>true</time>
    264             <latency>true</latency>
    265             <timestamp>true</timestamp>
    266             <success>true</success>
    267             <label>true</label>
    268             <code>true</code>
    269             <message>true</message>
    270             <threadName>true</threadName>
    271             <dataType>true</dataType>
    272             <encoding>false</encoding>
    273             <assertions>true</assertions>
    274             <subresults>true</subresults>
    275             <responseData>false</responseData>
    276             <samplerData>false</samplerData>
    277             <xml>false</xml>
    278             <fieldNames>true</fieldNames>
    279             <responseHeaders>false</responseHeaders>
    280             <requestHeaders>false</requestHeaders>
    281             <responseDataOnError>false</responseDataOnError>
    282             <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
    283             <assertionsResultsToSave>0</assertionsResultsToSave>
    284             <bytes>true</bytes>
    285             <sentBytes>true</sentBytes>
    286             <url>true</url>
    287             <threadCounts>true</threadCounts>
    288             <idleTime>true</idleTime>
    289             <connectTime>true</connectTime>
    290           </value>
    291         </objProp>
    292         <stringProp name="filename"></stringProp>
    293       </ResultCollector>
    294       <hashTree/>
    295     </hashTree>
    296   </hashTree>
    297 </jmeterTestPlan>

    1. 为了录制脚本,需要添加HTTP(S) Test Script Recorder;

    2. 为了可以调整加压模型(加压曲线),需要添加 bzm - Concurrency Thread Group 并发线程组;

    3. 为了HTTP Header 和 Cookie统一管理,需要添加 HTTP Header Manager 及 HTTP Cookie Manager,添加后所有HTTP请求都会使用相同的Cookie值和Header参数,否则每个请求都会用不同的Cookie,进而出现登录失效的情况;

    4. 为了定义 事务(Transaction) 单元,需要添加 Transaction Logical 控制器(例如:001_Login)表示 登录 事务,其他的事务,(例如002_TestCase_01)需自己根据实际测试需要添加;

    5. 为了进行动态参数获取和替换,以达到参数化的效果,需要合理使用JSON、Boundary、Regular Expression、JSR223 PostProcessor等后置或前置处理步骤;

    6. 为了调试参数值和打印日志,需要Debug Sampler采样器,以配合上一个(5)中加入的后置、前置操作调试;

    7. 为了在GUI环境下调试方便,需要添加View Result Tree监听器,这样可以看到每个请求是否成功(绿色),失败(红色),并看到Request 和 Response的Header、Body信息,调试使用;

    8. 实际执行并发测试时,需要看 jp@gc - Transactions per Second 及 jp@gc - Response Times Over Time,即每秒事务数(TPS)和事务响应时间(RT);聚合报告(Aggregate Report)可以作为统计量分析事务成功、失败(Pass/Fail)比率;

    划重点:最开始使用Jmeter或使用中文界面时,经常会找不到所需的组件位置,这里给出以上组件的位置:

    1. 并发线程组:

     

    2.  察看结果树,在运行调试时使用:

    3. Header, Cookie, Cache管理器,在保证所有请求都使用同一Cookie信息时使用,可以解决一般的登录问题:

    4. 逻辑事务控制器,与Loadrunner的事务等效,可以理解为最终测试结果图里的事务名称,记录了这个逻辑事务开始时间戳到结束时间戳之间的运行时间;即Transaction ResponseTime

    6. Debug Sample,调试采样器,通常用来调试和打印我们定义的变量或从请求中获取的变量值;

    7. 常用的请求返回数据提取器,根据服务器返回数据的不同类型(文本?Json?HTML?XML等等),选择不同的提取器:

    第七步:开始脚本录制和事务划分,调试脚本;

        调试脚本常用到几个监听器和调试器,Result Tree View,Debug,详见上个章节;

    第八步:执行测试,想和Loadrunner一样进行场景测试,就得灵活配置执行线程组,常用的是并发线程组,介绍和用法参考https://cloud.tencent.com/developer/article/1640890

    第九步:命令行方式执行测试,并察看执行完成的报告结果数据;参考Jmeter系列(40)- 详解 Jmeter CLI 模式 - 小菠萝测试笔记 - 博客园 (cnblogs.com)

    第十步:需要分布式执行大批量测试,参考Jmeter系列(39)- Jmeter 分布式测试 - 小菠萝测试笔记 - 博客园 (cnblogs.com)

    第十一步:由了RT和TPS,还差CPU与MEM监控,

    Linux/Unix请参考nmon监控方法Jmeter系列(38)- 详解性能监控工具 nmon - 小菠萝测试笔记 - 博客园 (cnblogs.com)

    当然也可以用Jmeter提供的监控方法,参考Jmeter系列(35)- 使用 ServerAgent 监控服务器 - 小菠萝测试笔记 - 博客园 (cnblogs.com),如果遇到了监控问题,请参考接下来的 ※ 划重点 ※

    细节的参考,给两个地址,一个是官方文档Apache JMeter - User's Manual,一个是博客园小伙伴(一位阿里的测开工程师)整理的专题测试高级进阶技能系列 - Jmeter - 随笔分类(第3页) - 小菠萝测试笔记 - 博客园 (cnblogs.com)

    ※ 划重点 ※

    监控CPU/MEM遇到的问题解决方法:

     错误现象:安装了 PerfMon (Servers Performance Monitoring) 插件后,添加了服务器资源监控,点击开始后无响应,Jmeter日志控制台报错如下:

    ERROR o.a.j.JMeter: Uncaught exception in thread Thread[StandardJMeterEngine,6,main]
    java.lang.NoSuchMethodError: org.apache.jmeter.samplers.SampleSaveConfiguration.setFormatter(Ljava/text/DateFormat;)V
    at kg.apc.jmeter.JMeterPluginsUtils.doBestCSVSetup(JMeterPluginsUtils.java:272) ~[JMeterPlugins-Extras.jar:?]
    at kg.apc.jmeter.perfmon.PerfMonCollector.setupSaving(PerfMonCollector.java:136) ~[jmeter-plugins-perfmon-2.1.jar:?]
    at kg.apc.jmeter.perfmon.PerfMonCollector.testStarted(PerfMonCollector.java:113) ~[jmeter-plugins-perfmon-2.1.jar:?]
    at org.apache.jmeter.reporters.ResultCollector.testStarted(ResultCollector.java:350) ~[ApacheJMeter_core.jar:5.4.1]
    at kg.apc.jmeter.vizualizers.CorrectedResultCollector.testStarted(CorrectedResultCollector.java:28) ~[JMeterPlugins-Extras.jar:?]
    at org.apache.jmeter.engine.StandardJMeterEngine.notifyTestListenersOfStart(StandardJMeterEngine.java:205) ~[ApacheJMeter_core.jar:5.4.1]
    at org.apache.jmeter.engine.StandardJMeterEngine.run(StandardJMeterEngine.java:382) ~[ApacheJMeter_core.jar:5.4.1]
    at java.lang.Thread.run(Thread.java:748) [?:1.8.0_181]

    这是因为缺少了一个jar包所致,请下载jmeter-plugins-cmn-jmeter-0.x.jar,拷贝到jmeter的lib/ext文件夹下,然后重启jmeter再试即可看到优美的CPU/MEM监控曲线;

    jmeter-plugins-cmn-jmeter-0.x.jar下载地址参考:https://mvnrepository.com/artifact/kg.apc/jmeter-plugins-cmn-jmeter 

    https://maven.aliyun.com/repository/central/kg/apc/jmeter-plugins-cmn-jmeter/0.7/jmeter-plugins-cmn-jmeter-0.7.jar

    https://maven.aliyun.com/repository/central/kg/apc/jmeter-plugins-cmn-jmeter/0.6/jmeter-plugins-cmn-jmeter-0.6.jar

  • 相关阅读:
    个人项目 源程序特征统计程序(C++)
    自我介绍+软工五问
    团队作业3——需求改进&系统设计
    Four Fundamental Operations(JS) --结对项目
    WordCount of Software Engineering
    代码开发、测试发布
    需求改进---系统设计
    综合系统开发---需求分析
    读书笔记---软件设计原则、设计模式
    自我介绍+课程六问
  • 原文地址:https://www.cnblogs.com/xiaoTT/p/15828230.html
Copyright © 2020-2023  润新知