您的位置:首页 > 其它

loadrunner入门篇-Vuser发生器

2017-03-04 22:11 357 查看
Vuser 发生器(Visual User Generator,VuGen),主要通过捕获客户端向服务器发送的HTTP请求,将这些请求录制成脚本,在回放时将捕获的HTTP请求再次发送,以达到模拟客户行为的目的。

下面具体介绍一下如何使用,篇幅比较长,请耐心看下去:

 

脚本录制                                                        

 

    这里以HP自带的在线订票网站进行录制,依次打开:开始|所有程序|HP Loadrunner|Samples|web|HP Tours Web Application(注:打开网站前,要先启动同目录下的Web Server 服务,否则进不了)。

 

    首先得注册一个账号,才能进行后续的操作。

    注册完后,打开VuGen应用,依次打开:开始|所有程序|HP Loadrunner|Applications|Visual User Generator(如图1所示)。



图1(注:在打开应用过程中如果遇到问题,可以尝试右键‘以管理员身份运行’)
 

    点击界面中的New Script图标按钮,即创建新脚本,会弹出一个对话框(如图2所示),可以看到loadrunner提供3种选择协议的方式:单协议脚本、多协议脚本和最近使用过的协议。



图2
    在录制脚本过程中,涉及的关键点是如何选择录制协议,选择的协议将直接影响录制后的脚本是否理想。那到底怎么知道被测对象用的是什么通信协议呢?常用方法有几种:
1)直接跟开发确认数据通信所采用的协议;
2)通过概要设计或详细设计手册获知所使用的协议;
3)使用协议分析工具捕获通信时的数据包并进行分析(注:一定要摒除底层协议,不要被底层协议所迷惑);
4)根据以往测试经验来判断被测对象所采用的协议,这种方法有时不一定准确。
    那这里顺便贴上常用的协议分类表,仅供参考(如图3所示)。



图3
    这里呢就以比较常用的Web(HTTP/HTML)协议为例进行录制。
    VuGen 录制浏览器主要是通过代理的方式来实现的,即以VuGen 作为代理来访问目标服务器,这样就可以捕获客户端与服务器之间通过的数据包(默认是使用自带的IE浏览器,使用其他浏览器录制容易出现HTTP请求被丢失的现象,所以尽量使用IE浏览器进行录制)。

    录制时系统弹出一个录制窗口(如图4所示),具体说明如下:



 

图4
    1)application type:应用类型(比如BS或CS);                    

    2)program to record:录制的程序(即用什么浏览器来录制,前面也提到过,最好默认使用自带的IE);

    3)URL:被录制应用的地址;                                              

    4)working directory:存放路径;

    5)record into action:表示将录制的代码放到哪个部分;(注:loadrunner生成的代码由三部分组成,vuser_int,aciton和vuser_end。一般情况下都是将生成的代码放在action部分,因为vuser_int和               vuser_end两部分的代码只会执行一次,这样会出现这种问题,客户的并发虚拟用户只执行一次,执行完成一次后再也不执行,这样就没有HTTP请求给服务器,也即服务器没有压力)

    6)Record the application startup:表示应用程序一启动,就立即开始录制;如果不选,则应用程序启动后,VuGen会弹出一个对话框,并且暂时不会进行录制,当用户操作应用程序到需要录制的地方时,单          击record按钮,才开始录制。

    开始录制后,会出现一个工具栏(如图5所示),从左到右依次代表开始录制、停止录制、暂停录制、新建action、在脚本与录制界面之间切换、添加开始事务标识、添加结束事务标识、设置集合点和添加注释。



图5
  录制完后,单击'停止录制'按钮结束录制,这时VuGen会自动生成一个脚本。可以看出LR生成的脚本都是由函数组成的(如图6所示)。  



图6
 

recording options 设置                                 

 

  在进行录制时,首先要对录制的一些一些参数进行设置,只有将这些参数设置好,才能录制并生成需要的脚本。

  首先是recordin
19b64
g options设置(如图7所示),需要注意的设置项有recording选项卡,advanced选项卡和correlation选项卡。



 

图7 

1、recording选项卡

    recording level包含两种录制模式:HTML-based script和URL-based script。(如图8所示)



图8

    关于HTML-based script脚本方式又有两种:

1)模拟用户录制行为,即GUI录制,把用户每一步的操作显示出来,最后生成的脚本非常直观,并且会将上下文的一些敏感信息记录下来。它创建URL(web_url)、link(web_link)、image(web_image)和提交表单(web_submit_form)。代码如下:

    在录制时只做了两个操作,生成的代码也只有两个函数,也即这种录制模式只录制用户的操作,其他的内容不会被录制,使用的提交信息函数为web_submit_form(),该函数运行时,首先在页面上去查找表单,再提交数据

    友情吐槽一下:用这个模式录制,本来使用的函数应该是web_submit_form()的,但不知道为什么,我的机子录制了很多遍,最后生成的都是使用的web_submit_data()函数,所以下面的这段代码是我录制后参照书本所描述的修改了一下得来的。





web_url("WebTours",
"URL=http://127.0.0.1:1080/WebTours/",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTML",
LAST);

web_submit_form("login.pl", "Snapshot=t5.inf",
ITEMDATA, "Name=username", "Value=test1", ENDITEM,
"Name=password", "Value=test1", ENDITEM, "Name=login.x", "Value=0", ENDITEM,
"Name=login.y", "Value=0", ENDITEM,
LAST);

return 0;


A script describing user actions 
2)录制所有links、images和URL,但不创建web_link、web_image和提交表单(web_submit_form)。这种录制方式生成的脚本不直观。如下代码:

    同样的提交登录的信息,但使用的是web_submit_data(),该函数是直接向服务器发送要提交的数据的。





web_url("WebTours",
"URL=http://127.0.0.1:1080/WebTours/",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTML",
LAST);

lr_think_time(10);

web_submit_data("login.pl",
"Action=http://127.0.0.1:1080/WebTours/login.pl",
"Method=POST",
"TargetFrame=",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?in=home",
"Snapshot=t5.inf",
"Mode=HTML",
ITEMDATA,
"Name=userSession", "Value=120572.007128392zcAVQccpzVzzzzzHDHcVVpfDfH", ENDITEM,
"Name=username", "Value=test1", ENDITEM,
"Name=password", "Value=test1", ENDITEM,
"Name=JSFormSubmit", "Value=off", ENDITEM,
"Name=login.x", "Value=0", ENDITEM,
"Name=login.y", "Value=0", ENDITEM,
LAST);

return 0;


A script containing explicit URLs only
   

    在录制过程中可能会录制到一些非HTML的元素(如XML、JS),主要用于包含去获取自己的一些资源,如JS文件用于调用加载多个图片。对于这类非HTML的元素,录制时有3种方式:

 1)录制时对于非HTML资源并不会生成一个新的功能。它列出所有相关资源的参数,如web_url、web_link和web_submit_data。这些功能的参数使用EXTRARES表示,如下代码:





web_url("WebTours",
"URL=http://127.0.0.1:1080/WebTours/",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTML",
EXTRARES,
"Url=http://act.cmcmcdn.com/upload/201507/8afc2fe48db9060fe1bdda2089e1d950.png", ENDITEM,
"Url=http://act.cmcmcdn.com/upload/201507/3b491068507d8f85ea7b35d756da7215.png", ENDITEM,
LAST);


record within the current script step
2)在一个组中记录这些单独的步骤,为每个非HTML资源创建一个新的功能,如下代码:





web_url("WebTours",
"URL=http://127.0.0.1:1080/WebTours/",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTML",
LAST);

web_concurrent_start(NULL);

web_url("8afc2fe48db9060fe1bdda2089e1d950.png",
"URL=http://act.cmcmcdn.com/upload/201507/8afc2fe48db9060fe1bdda2089e1d950.png",
"TargetFrame=",
"Resource=1",
"RecContentType=image/png",
"Referer=http://127.0.0.1:1080/WebTours/",
"Snapshot=t5.inf",
LAST);

web_url("3b491068507d8f85ea7b35d756da7215.png",
"URL=http://act.cmcmcdn.com/upload/201507/3b491068507d8f85ea7b35d756da7215.png",
"TargetFrame=",
"Resource=1",
"RecContentType=image/png",
"Referer=http://127.0.0.1:1080/WebTours/",
"Snapshot=t6.inf",
LAST);

web_concurrent_end(NULL);


record in separate steps and use concurrent groups
3)不记录,对于非HTML元素不记录,如下代码:





web_url("WebTours",
"URL=http://127.0.0.1:1080/WebTours/",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTML",
LAST);


do not record

    URL-based script方式

    将每条客户端发出的请求录制成一条语句,对LR来说,在该模式下,一条语句只能建立一个到服务器的连接,并将通信过程中的很多隐藏信息都录制出来(如session、cookie)。LR提供了web_concurrent_start()和web_concurrent_end()函数模拟URL-based scriptr的工作方式。如下代码:





web_url("WebTours",
"URL=http://127.0.0.1:1080/WebTours/",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTTP",
LAST);

web_url("header.html",
"URL=http://127.0.0.1:1080/WebTours/header.html",
"Resource=0",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/",
"Snapshot=t2.inf",
"Mode=HTTP",
LAST);

web_url("www.baidu.com",
"URL=http://www.baidu.com/",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t3.inf",
"Mode=HTTP",
LAST);

web_url("webtours.png",
"URL=http://127.0.0.1:1080/WebTours/images/webtours.png",
"Resource=1",
"RecContentType=image/png",
"Referer=http://127.0.0.1:1080/WebTours/header.html",
"Snapshot=t4.inf",
LAST);

web_url("hp_logo.png",
"URL=http://127.0.0.1:1080/WebTours/images/hp_logo.png",
"Resource=1",
"RecContentType=image/png",
"Referer=http://127.0.0.1:1080/WebTours/header.html",
"Snapshot=t5.inf",
LAST);

web_url("welcome.pl",
"URL=http://127.0.0.1:1080/WebTours/welcome.pl?signOff=true",
"Resource=0",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/",
"Snapshot=t6.inf",
"Mode=HTTP",
LAST);

web_concurrent_start(NULL);

web_url("home.html",
"URL=http://127.0.0.1:1080/WebTours/home.html",
"Resource=0",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/welcome.pl?signOff=true",
"Snapshot=t7.inf",
"Mode=HTTP",
LAST);

web_url("nav.pl",
"URL=http://127.0.0.1:1080/WebTours/nav.pl?in=home",
"Resource=0",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/welcome.pl?signOff=true",
"Snapshot=t8.inf",
"Mode=HTTP",
LAST);

web_concurrent_end(NULL);

web_url("mer_login.gif",
"URL=http://127.0.0.1:1080/WebTours/images/mer_login.gif",
"Resource=1",
"RecContentType=image/gif",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?in=home",
"Snapshot=t9.inf",
LAST);

web_concurrent_start(NULL);

web_url("8afc2fe48db9060fe1bdda2089e1d950.png",
"URL=http://act.cmcmcdn.com/upload/201507/8afc2fe48db9060fe1bdda2089e1d950.png",
"Resource=1",
"RecContentType=image/png",
"Referer=http://127.0.0.1:1080/WebTours/",
"Snapshot=t10.inf",
LAST);

web_url("3b491068507d8f85ea7b35d756da7215.png",
"URL=http://act.cmcmcdn.com/upload/201507/3b491068507d8f85ea7b35d756da7215.png",
"Resource=1",
"RecContentType=image/png",
"Referer=http://127.0.0.1:1080/WebTours/",
"Snapshot=t11.inf",
LAST);

web_concurrent_end(NULL);

lr_think_time(12);

web_submit_data("login.pl",
"Action=http://127.0.0.1:1080/WebTours/login.pl",
"Method=POST",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?in=home",
"Snapshot=t12.inf",
"Mode=HTTP",
ITEMDATA,
"Name=userSession", "Value=120572.172620494zcAVQDVpVcQVzzzHDHcVVpfizHHf", ENDITEM,
"Name=username", "Value=test1", ENDITEM,
"Name=password", "Value=test1", ENDITEM,
"Name=JSFormSubmit", "Value=off", ENDITEM,
"Name=login.x", "Value=0", ENDITEM,
"Name=login.y", "Value=0", ENDITEM,
LAST);

web_concurrent_start(NULL);

web_url("nav.pl_2",
"URL=http://127.0.0.1:1080/WebTours/nav.pl?page=menu&in=home",
"Resource=0",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/login.pl",
"Snapshot=t13.inf",
"Mode=HTTP",
LAST);

web_url("login.pl_2",
"URL=http://127.0.0.1:1080/WebTours/login.pl?intro=true",
"Resource=0",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/login.pl",
"Snapshot=t14.inf",
"Mode=HTTP",
LAST);

web_concurrent_end(NULL);

web_concurrent_start(NULL);

web_url("flights.gif",
"URL=http://127.0.0.1:1080/WebTours/images/flights.gif",
"Resource=1",
"RecContentType=image/gif",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?page=menu&in=home",
"Snapshot=t15.inf",
LAST);

web_url("signoff.gif",
"URL=http://127.0.0.1:1080/WebTours/images/signoff.gif",
"Resource=1",
"RecContentType=image/gif",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?page=menu&in=home",
"Snapshot=t16.inf",
LAST);

web_url("itinerary.gif",
"URL=http://127.0.0.1:1080/WebTours/images/itinerary.gif",
"Resource=1",
"RecContentType=image/gif",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?page=menu&in=home",
"Snapshot=t17.inf",
LAST);

web_url("in_home.gif",
"URL=http://127.0.0.1:1080/WebTours/images/in_home.gif",
"Resource=1",
"RecContentType=image/gif",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?page=menu&in=home",
"Snapshot=t18.inf",
LAST);

web_concurrent_end(NULL);

return 0;


URL-based script
    关于URL高级设置有两种方式:

1)create concurrent groups for resources their source HTML page。将捕获所有HTML页面的资源并保存在并发组中(并发组使用web_concurrent_start和web_concurrent_end satements 两个函数表示,如上面的代码,就是使用了这种方式,故不再重复贴代码了),如果不选中该选项,HTML页面资源将会分成独立的、单独的web_url步骤,但不放入并行组中。

2)use web_custom_request only。如果录制的是非浏览器的应用程序,可以设置VuGen自定义HTTP请求。在LR中使用web_custom_request函数来实现,代码如下:





web_custom_request("WebTours",
"URL=http://127.0.0.1:1080/WebTours/",
"Method=GET",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTTP",
LAST);


use web_custom_request only
 

    相对比两种录制模式,可参考下面的原则:

a.基于浏览器的应用程序推荐用HTML;反之,则用URL

b.如果基于浏览器的应用程序包含了JS,并且该脚本向服务器发送了请求,比如DataGrid的分布按钮,推荐用URL

c.基于浏览器的应用程序中使用了HTTPS安全协议,建议使用URL。

如果使用HTML模式录制后不能成功回话,可以考虑改用URL模式来录制,因为这种情况多是同上面所列举的原因所引起的。

 

2、advanced选项卡

    是设置脚本回话的高级选项。(如图9所示)



图9 
1)save snapshot resources locally:表示运行结果中保存一个快照 

2)add comments to script for HTTP errors while recording:表示出现错误时会自动添加注释。在Heades可以选择需要录制的heades,以便服务器能够正确处理编辑信息。需要注意的是accept-language选项,像websphere这类服务器会根据HTTP请求中的header来确定编码。

3、correlation选项卡

    用来对脚本中的关联属性进行设置(如图10所示)。LR包括两种规则:内建规则和自定义规则;LR会默认自带一些内建规则。在录制时选中需要的关联规则,录制脚本过程中LR会自动匹配需要关联的规则,并生成关联函数。如果当前这些关联规则无法满足录制的需求,那么可以单击new application按钮来新建一个关联,再单击new rule按钮为该关联新建一个规则。



 图10
 

run-time settings 设置                                 

 

  主要用于设置在脚本运行过程中脚本运行的策略,需要注意的设置项有run logic选项卡、pacing选项卡、think time选项卡和miscellaneous选项卡,具体解释可查看我的另一篇博客,都有详细的描述。在这里就不再赘述了。(附上博客链接:http://www.cnblogs.com/Chilam007/p/6114626.html)

 

脚本完善                                                         

 

    脚本录制完成并不代表这些脚本很接近用户的真实使用情况,为了使这些脚本更接近用户的真实使用情况,需要对脚本进行完善。

    首先,为了衡量服务器对某个动作处理的响应时间,需要定义事务(transaction)。例如一个登录动作,为了衡量多用户同时执行登录服务器的响应时间,可以将此操作定义为一个事务。其次,为了衡量加重负载的情况下服务器的性能情况,需要插入集合点。例如为了模拟50个用户同时登录的情况,要做到真正的并发,就必须添加一个集合点,用来衡量服务器的性能。

    最后,在录制脚本的过程中添加注释,以增强脚本的可读性 。

1、插入事务

    事务即客户要实现的业务,事务响应时间的原理是将业务操作结束时的时间点与业务开始时的时间点的差值,反应了业务的响应时间,也即是事务由两部分组成:开始事务函数和结束事务函数。

    插入事务有两种方法:一种是在录制过程中插入开始和结束事务点;另一种是在编辑脚本时插入。一般情况下是在录制过程中插入。

    录制过程中需要插入事务点时,单击录制工具栏中的‘插入开始事务点’按钮,输入开始事务名称(要遵守脚本编辑命名规则)。事务的开始点与事务的结束点具有一一对应的特点,即在脚本中插入了一个事务开始点后一定要在接下来的过程中插入结束事务点,插入结束事务点的操作与插入开始事务点的操作一致,当该业务流程执行完毕后需要添加结束事务点,在工具栏中点击‘插入结束事务点’即可。

    如完成后才插入的话,在生成的脚本中找到要插入开始事务点的地方,选择insert->start transaction,在弹出的对话框中输入开始事务的名称;找到要插入结束事务点的地方。(注:同样的操作,但在‘结束事务’对话框中,比在脚本录制过程中插入结束事务点时的对话框中多出了一项transaction status,如图11所示)。

1)事务的状态被自动设置,如果事务执行成功,状态设置为PASS;如果事务执行失败,状态设置为FAIL;如果事务异常中断,状态设置为STOP。

2)事务执行成功,代码返回的状态是PASS。

3)事务执行失败,代码返回的是FAIL。

4)事务异常中断,代码返回的状态是STOP。

为什么需要事务状态?因为在测试过程中不单需要获取事务的响应时间,还需要确定事务是否成功,如果响应时间很短,但事务都失败了,那也是无法满足客户需求的。





图11
    回放脚本后,事务结束状态为PASS并不一定代表业务一定做成功了(比如将登录脚本的账户密码改为错误的,可看下面贴的代码),因为LR在自动判断事务结束状态时是以结束函数是否运行为标准,即只要结束事务函数运行,那么就将事务的结束状态置为PASS,反之将事务的结束状态置为FAIL。正常情况应该是登录后,先检查登录账户名是否正确,如果检查到正确后才将事务的结束状态置为PASS,这样才能保证业务成功了,否则做性能测试根本毫无意义。所以一般情况下都需要先设置检查点后,再根据检查点来判断事务是否成功,这样才比较合理。





web_url("WebTours",
"URL=http://127.0.0.1:1080/WebTours/",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTML",
EXTRARES,
"Url=http://act.cmcmcdn.com/upload/201507/8afc2fe48db9060fe1bdda2089e1d950.png", ENDITEM,
"Url=http://act.cmcmcdn.com/upload/201507/3b491068507d8f85ea7b35d756da7215.png", ENDITEM,
LAST);

lr_start_transaction("登录");

lr_think_time(16);

web_submit_data("login.pl",
"Action=http://127.0.0.1:1080/WebTours/login.pl",
"Method=POST",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?in=home",
"Snapshot=t3.inf",
"Mode=HTML",
ITEMDATA,
"Name=userSession", "Value=120579.391332523zcAcDAcpttVzzzzHDHcVDpiVtzf", ENDITEM,
"Name=username", "Value=test1", ENDITEM,
"Name=password", "Value=test1", ENDITEM,
"Name=JSFormSubmit", "Value=off", ENDITEM,
"Name=login.x", "Value=45", ENDITEM,
"Name=login.y", "Value=12", ENDITEM,
LAST);

lr_end_transaction("登录",LR_AUTO);

return 0;

注:回放这个脚本时,事务的结束状态为PASS,这是正确的,因为这个脚本是可以正确的登录的。


正常的脚本(LR_AUTO)





web_submit_data("login.pl",
"Action=http://127.0.0.1:1080/WebTours/login.pl",
"Method=POST",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?in=home",
"Snapshot=t3.inf",
"Mode=HTML",
ITEMDATA,
"Name=userSession", "Value=120579.391332523zcAcDAcpttVzzzzHDHcVDpiVtzf", ENDITEM,
"Name=username", "Value=error1", ENDITEM,
"Name=password", "Value=test1", ENDITEM,
"Name=JSFormSubmit", "Value=off", ENDITEM,
"Name=login.x", "Value=45", ENDITEM,
"Name=login.y", "Value=12", ENDITEM,
LAST);

注:下面是回放日志,可以看到虽然是错误的账号,但仍被置为PASS状态,这是不正确的。
Action.c(4): web_url("WebTours") was successful, 9467 body bytes, 2222 header bytes      [MsgId: MMSG-26386]
Action.c(16): Notify: Transaction "登录" started.
Action.c(20): web_submit_data("login.pl") was successful, 795 body bytes, 225 header bytes      [MsgId: MMSG-26386]
Action.c(36): Notify: Transaction "登录" ended with "Pass" status (Duration: 0.3466 Wasted Time: 0.0031).
Ending action Action.


修改成错误账号的脚本(LR_AUTO)

2、插入集合点

    集合点是指在脚本中插入集合点函数(lr_rendezvous),当脚本运行到集合点函数时,将停止运行并等待其允许运行的条件(其允许运行条件即为集合点策略)达到后才释放集合点开始运行。也即在性能测试过程中如果需要保证虚拟用户真正并发,即必须添加集合点。

    集合点可以在录制过程中插入,也可以在录制完成后插入,但只能插入action部分的脚本中,不能插入vuser_int和vuser_end两部分脚本中。

    录制过程中需要插入集合点的位置时,单击录制工具栏中的‘插入集合点’按钮即可插入集合点;如完成后才插入的话,可选择insert->rendezvous,在弹出的‘插入集合点’对话框中输入要集合点名称。(注意命名时要遵守脚本编辑命名规则,使其具有代表性)。

3、插入注释

    注释可以在录制脚本过程中插入,也可以在脚本录制完成后插入。

    录制过程中需要对录制的脚本进行注释时,单击录制工具栏中的‘插入注释’按钮即可插入注释;如完成后才插入的话,可选择insert->comment,在弹出的‘插入注释’对话框中输入要注释的内容。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: