selenium 是thoughtWorks公司的一个强大的开源Web功能测试工具系列,采用javascript来管理整个测试过程,包括读入测试套件,执行测试和记录测试结果。它采用javascript单元测试工具JSUnit为核心,模拟正式用户操作,包括浏览页面,点击链接,输入文字,提交表单,触发鼠标事件等。并且能够对页面结果进行种种验证。也就是说,只要在测试用例中把预期的用户行为与结果都描述出来,我们就得到了一个可以自动化运行的功能测试套件。Selenium的核心是用javascript写的,他和浏览器进行通行,把测试用例的信息发送给浏览器执行,从而达到自动化测试的目的
通过研究selenium-webdriver的源码,笔者发现其实webdriver的实现原理并不高深莫测无法揣度。在这里以webdriverruby binding的firefox-webdriver实现为例,简单介绍一下webdriver的工作原理。
∙ 当测试脚本启动firefox的时候,selenium-webdriver会首先在新线程中启动firefox浏览器。如果测试脚本指定了firefox的profile,那么就以该profile启动,否则的话就新启1个profile,并启动firefox;
∙ firefox一般是以-no-remote的方法启动,启动后selenium-webdriver会将firefox绑定到特定的端口,绑定完成后该firefox实例便作为webdriver的remote server存在;
∙ 客户端(也就是测试脚本)创建1个session,在该session中通过http请求向remote server发送restful的请求,remote server解析请求,完成相应操作并返回response;
∙ 客户端接受response,并分析其返回值以决定是转到第3步还是结束脚本;
这就是webdriver的工作流程,看起来很复杂实际上当了解了webdriver的实现原理后,理解上述问题应该比较简单。
webdriver是按照server – client的经典设计模式设计的。
server端就是remote server,可以是任意的浏览器。当我们的脚本启动浏览器后,该浏览器就是remote server,它的职责就是等待client发送请求并做出相应;
client端简单说来就是我们的测试代码,我们测试代码中的一些行为,比如打开浏览器,转跳到特定的url等操作是以http请求的方式发送给被测试浏览器,也就是remote server;remote server接受请求,并执行相应操作,并在response中返回执行状态、返回值等信息;
举个实际的例子,下面代码的作用是”命令”firefox转跳到google主页:
driver = Selenium::WebDriver.for :firefox
driver.navigate.to "http://google.com"
在执行driver.navigate.to “http://google.com” 这句代码时,client,也就是我们的测试代码向remote server发送了如下的请求:
POST session/285b12e4-2b8a-4fe6-90e1-c35cba245956/url
post_data {"url":"http://google.com"}
通过post的方式请求localhost:port/hub/session/session_id/url地址,请求浏览器完成跳转url的操作。
如果上述请求是可接受的,或者说remote server是实现了这个接口,那么remote server会跳转到该post data包含的url,并返回如下的response
{"name":"get","sessionId":"285b12e4-2b8a-4fe6-90e1-c35cba245956","status":0,"value":""}
该response中包含如下信息
∙ name:remote server端的实现的方法的名称,这里是get,表示跳转到指定url;
∙ sessionId:当前session的id;
∙ status:请求执行的状态码,非0表示未正确执行,这里是0,表示一切ok不许担心;
∙ value:请求的返回值,这里返回值为空,如果client调用title接口,则该值应该是当前页面的title;
如果client发送的请求是定位某个特定的页面元素,则response的返回值可能是这样的:
{"name":"findElement","sessionId":"285b12e4-2b8a-4fe6-90e1-c35cba245956","status":0,"value":{"ELEMENT":"{2192893e-f260-44c4-bdf6-7aad3c919739}"}}
name,sessionId,status跟上面的例子是差不多的,区别是该请求的返回值是ELEMENT:{2192893e- f260-44c4-bdf6-7aad3c919739},表示定位到元素的id,通过该id,client可以发送如click之类的请求与 server端进行交互。
那么remote server端的这些功能是如何实现的呢?答案是浏览器实现了webdriver的统一接口,这样client就可以通过统一的restful的接口去进行浏览器的自动化操作。目前webdriver支持ie, chrome, firefox, opera等主流浏览器,其主要原因是这些浏览器实现了webdriver约定的各种接口
webdriver原理:
1. WebDriver启动目标浏览器,并绑定到指定端口。该启动的浏览器实例,做为web driver的remote server。
2. Client 端通过CommandExcuter发送HTTPRequest给remote server 的侦听端口(通信协议: the webriver wire protocol)
3. Remote server 需要依赖原生的浏览器组件(如:IEDriver.dll,chromedriver.exe),来转化转化浏览器的native调用。
查看命令提示符下的运行日志:
咋一看很乱,慢慢分析一下就发现很有意思!结合上面的脚本分析
word/media/image1.gif
---------------------------------------------------------------------------------------
启动代理进入监听状态
C:\selenium>java -jar selenium-server-standalone-2.33.0.jar
八月22, 201310:19:48上午org.openqa.grid.selenium.GridLauncher main
INFO: Launching a standalone server
10:19:48.734 INFO - Java: Oracle Corporation 23.21-b01
10:19:48.734 INFO - OS: Windows XP 5.1 x86
10:19:48.734 INFO - v2.33.0, with Core v2.33.0. Built from revision 4e90c97
10:19:48.843 INFO -RemoteWebDriver instances should connect to: http://127.0.0.
1:4444/wd/hub
10:19:48.843 INFO - Version Jetty/5.1.x
10:19:48.843 INFO - Started HttpContext[/selenium-server/driver,/selenium-server
/driver]
10:19:48.843 INFO - Started HttpContext[/selenium-server,/selenium-server]
10:19:48.843 INFO - Started HttpContext[/,/]
10:19:48.890 INFO - Started org.openqa.jetty.jetty.servlet.ServletHandler@176343
e
10:19:48.890 INFO - Started HttpContext[/wd,/wd]
10:19:48.906 INFO - Started SocketListener on 0.0.0.0:4444
10:19:48.906 INFO - Started org.openqa.jetty.jetty.Server@388c74
--------------------------------------------------------------------------------------
创建新session
10:20:38.593 INFO - Executing: [new session: {platform=ANY, javascriptEnabled=tr
ue, browserName=chrome, version=}] at URL: /session)
10:20:38.593 INFO - Creating a new session for Capabilities [{platform=ANY, java
scriptEnabled=true, browserName=chrome, version=}]
webdrivr通过GET方式发送请求
[0.921][INFO]: received Webriver request: GET /status
向webdrver返回响应,返回码200表示成功
[0.921][INFO]: sending Webriver response: 200 {
"sessionId": "",
"status": 0,
"value": {
"build": {
"version": "alpha"
},
"os": {
"arch": "x86",
"name": "Windows NT",
"version": "5.1 SP3"
}
}
}
webdriver再次以POST方式发送请求,并启动浏览器相关信息
[0.984][INFO]: received Webriver request: POST /session {
"desiredCapabilities": {
"browserName": "chrome",
"javascriptEnabled": true,
"platform": "ANY",
"version": ""
}
}
[0.984][INFO]: Launching chrome: "C:\ocuments and Settings\Administrator\Local S
ettings\Application ata\Google\Chrome\Application\chrome.exe" --remote-debugging
-port=4223 --no-first-run --enable-logging --logging-level=1 --user-data-dir="C:
\OCUME~1\AMINI~1\LOCALS~1\Temp\scoped_dir1808_7550" --load-extension="C:\OCUME~1
\AMINI~1\LOCALS~1\Temp\scoped_dir1808_26821\internal" --ignore-certificate-error
sdata:text/html;charset=utf-8,
[1.773][INFO]: sending Webriver response: 303
webdriver再次以GET方法请求,这附加上了session的信息
[1.778][INFO]: received Webriver request: GET /session/32b33aa585ccbbf7ba7853588
2852af3
服务器先对sesssionID进行解析,确认是selenium调用的以及要访问的网址,
[1.779][INFO]: sending Webriver response: 200 {
"sessionId": "32b33aa585ccbbf7ba78535882852af3",
"status": 0,
"value": {
"acceptSslCerts": true,
"applicationCacheEnabled": false,
"browserConnectionEnabled": false,
"browserName": "chrome",
"chrome": {
"chromedriverVersion": "2.0"
},
"cssSelectorsEnabled": true,
"databaseEnabled": true,
"handlesAlerts": true,
"javascriptEnabled": true,
"locationContextEnabled": true,
"nativeEvents": true,
"platform": "Windows NT",
"rotatable": false,
"takesScreenshot": true,
"version": "27.0.1453.116",
"webStorageEnabled": true
}
}
10:20:40.640 INFO - Done: /session
10:20:40.640 INFO - Executing: org.openqa.selenium.remote.server.handler.GetSess
ionCapabilities@14cf7a1 at URL: /session/ac5b2c71-5b1a-469e-814c-fdd09a2061fc)
10:20:40.640 INFO - Done: /session/ac5b2c71-5b1a-469e-814c-fdd09a2061fc
10:20:40.656 INFO - Executing: [get: http://www.youdao.com] at URL: /session/ac5
b2c71-5b1a-469e-814c-fdd09a2061fc/url)
webdriver正试向服务器请求youdao网站
[1.820][INFO]: received Webriver request: POST /session/32b33aa585ccbbf7ba785358
82852af3/url {
"url": "http://www.youdao.com"
}
[1.822][INFO]: waiting for pending navigations...
[1.829][INFO]: done waiting for pending navigations
[2.073][INFO]: waiting for pending navigations...
[2.900][INFO]: done waiting for pending navigations
获得服务器数据的应答
[2.900][INFO]: sending Webriver response: 200 {
"sessionId": "32b33aa585ccbbf7ba78535882852af3",
"status": 0,
"value": null
}
10:20:41.734 INFO - Done: /session/ac5b2c71-5b1a-469e-814c-fdd09a2061fc/url
--------------------------------------------------------------------------------------
下面接着发送定位输入框的信息
10:20:41.734 INFO - Executing: [find element: By.name: q] at URL: /session/ac5b2
c71-5b1a-469e-814c-fdd09a2061fc/element)
[2.905][INFO]: received Webriver request: POST /session/32b33aa585ccbbf7ba785358
82852af3/element {
"using": "name",
"value": "q"
}
[2.905][INFO]: waiting for pending navigations...
[2.905][INFO]: done waiting for pending navigations
[2.922][INFO]: waiting for pending navigations...
[2.922][INFO]: done waiting for pending navigations
得到服务器应答
[2.922][INFO]: sending Webriver response: 200 {
"sessionId": "32b33aa585ccbbf7ba78535882852af3",
"status": 0,
"value": {
"ELEMENT": "0.19427558477036655:1"
}
}
10:20:41.765 INFO - Done: /session/ac5b2c71-5b1a-469e-814c-fdd09a2061fc/element
10:20:41.765 INFO - Executing: [send keys: 0org.openqa.selenium.support.events.
EventFiringWebDriver$EventFiringWebElement@a8215ba9, [h, e, l, l, o]] at URL: /s
ession/ac5b2c71-5b1a-469e-814c-fdd09a2061fc/element/0/value)
向定位到的输入框写入hello
[2.936][INFO]: received Webriver request: POST /session/32b33aa585ccbbf7ba785358
82852af3/element/0.19427558477036655:1/value {
"id": "0.19427558477036655:1",
"value": [ "h", "e", "l", "l", "o" ]
}
[2.936][INFO]: waiting for pending navigations...
[2.936][INFO]: done waiting for pending navigations
[3.002][INFO]: waiting for pending navigations...
[3.002][INFO]: done waiting for pending navigations
[3.002][INFO]: sending Webriver response: 200 {
"sessionId": "32b33aa585ccbbf7ba78535882852af3",
"status": 0,
"value": null
}
10:20:41.843 INFO - Done: /session/ac5b2c71-5b1a-469e-814c-fdd09a2061fc/element/
0/value
再次发送定位输入框的请求
10:20:41.843 INFO - Executing: [find element: By.name: q] at URL: /session/ac5b2
c71-5b1a-469e-814c-fdd09a2061fc/element)
[3.006][INFO]: received Webriver request: POST /session/32b33aa585ccbbf7ba785358
82852af3/element {
"using": "name",
"value": "q"
}
[3.006][INFO]: waiting for pending navigations...
[3.006][INFO]: done waiting for pending navigations
[3.016][INFO]: waiting for pending navigations...
[3.016][INFO]: done waiting for pending navigations
[3.016][INFO]: sending Webriver response: 200 {
"sessionId": "32b33aa585ccbbf7ba78535882852af3",
"status": 0,
"value": {
"ELEMENT": "0.19427558477036655:1"
}
}
10:20:41.859 INFO - Done: /session/ac5b2c71-5b1a-469e-814c-fdd09a2061fc/element
10:20:41.859 INFO - Executing: [send keys: 0org.openqa.selenium.support.events.
EventFiringWebDriver$EventFiringWebElement@a8215ba9, [k, e, y, ., E, N, T, E, R]
] at URL: /session/ac5b2c71-5b1a-469e-814c-fdd09a2061fc/element/0/value)
对定位的到的输入框发送回车(ENTER)事件请求
[3.021][INFO]: received Webriver request: POST /session/32b33aa585ccbbf7ba785358
82852af3/element/0.19427558477036655:1/value {
"id": "0.19427558477036655:1",
"value": [ "k", "e", "y", ".", "E", "N", "T", "E", "R" ]
}
[3.021][INFO]: waiting for pending navigations...
[3.021][INFO]: done waiting for pending navigations
[3.064][INFO]: waiting for pending navigations...
[3.064][INFO]: done waiting for pending navigations
[3.064][INFO]: sending Webriver response: 200 {
"sessionId": "32b33aa585ccbbf7ba78535882852af3",
"status": 0,
"value": null
}
10:20:41.906 INFO - Done: /session/ac5b2c71-5b1a-469e-814c-fdd09a2061fc/element/
0/value
10:20:41.906 INFO - Executing: [close window] at URL: /session/ac5b2c71-5b1a-469
e-814c-fdd09a2061fc/window)
[3.068][INFO]: received Webriver request: ELETE /session/32b33aa585ccbbf7ba78535
882852af3/window
[WARNING:chrome_desktop_impl.cc(88)] chrome detaches, user should take care of d
irectory:C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\scoped_dir1808_7550 and C:\DOCUME~1\
ADMINI~1\LOCALS~1\Temp\scoped_dir1808_26821
[5.318][INFO]: sending Webriver response: 200 {
"sessionId": "32b33aa585ccbbf7ba78535882852af3",
"status": 0,
"value": null
}
10:20:44.156 INFO - Done: /session/ac5b2c71-5b1a-469e-814c-fdd09a2061fc/window
word/media/image1.gif
request请求 / response应答
一次请求会对应一次应答
POST/GET是请求(request)两种类型;关于两种请求方式的类别参考其它资料
200 、203是 HTTP请求返回的状态码,200表示成功;
sessionid:每一个访问服务器的客户端,都要先得到服务器端分配的一个sessionid,就像通行证一样,只有得到sessionid的客户端才能向服务器请求想要的数据。
其它还包括操作系统版本,浏览器类型、URL、字符类型等非常详细的记录。熟悉HTTP ,了解TCP 的三次握手四次挥手,相信你对浏览器的交互与webdriver原理会有更深入的认识。
最近比较空闲就仔细看了一下Selenium的源码,因为主要是使用WebDriver所以重点关注了一下WebDriver的工作原理。在前一篇blog里已经解释过了WebDriver与之前Selenium的JS注入实现不同,直接利用了浏览器native support来操作浏览器。所以对于不同平台,不同的浏览器,必须依赖一个特定的浏览器的native component来实现把WebDriver API的调用转化为浏览器的native invoke。
t(pB;k!Z4K+g] Ch0
mEnmp051Testing软件测试网;na\ f o|{X51Testing软件测试网pLS'as'o e$K
51Testing软件测试网2jV'nj.r#s4M
在我们new一个WebDriver的过程中,Selenium首先会确认浏览器的native component是否存在可用而且版本匹配。接着就在目标浏览器里启动一整套Web Service,这套Web Service使用了Selenium自己设计定义的协议,名字叫做The WebDriver Wire Protocol。这套协议非常之强大,几乎可以操作浏览器做任何事情,包括打开、关闭、最大化、最小化、元素定位、元素点击、上传文件等等等等。
c_fQ4S?!IBU051Testing软件测试网ia5HZ)Wo2[
51Testing软件测试网^$@X$AE%ia,y6X
Y4\#R m w*f*C2L051Testing软件测试网+`Y&Px5S-@&CM
WebDriver Wire协议是通用的,也就是说不管是FirefoxDriver还是ChromeDriver,启动之后都会在某一个端口启动基于这套协议的Web Service。例如FirefoxDriver初始化成功之后,默认会从http://localhost:7055开始,而ChromeDriver则大概是http://localhost:46350之类的。接下来,我们调用WebDriver的任何API,都需要借助一个ComandExecutor发送一个命令,实际上是一个HTTP request给监听端口上的Web Service。在我们的HTTP request的body中,会以WebDriver Wire协议规定的JSON格式的字符串来告诉Selenium我们希望浏览器接下来做社么事情。51Testing软件测试网8c+@M6J9|+K\
51Testing软件测试网2L(F7?-Q i
51Testing软件测试网XOVfP {5m5v[
AV/_k+xcn051Testing软件测试网5H1KI.E6tgz
这里笔者初步画了一个图来表示各种WebDriver的工作原理:
8q/GJn+?j*Pl:_2a/Ai051Testing软件测试网'lg+Y(DE|
e/`C`K3s,~uu0
rV#|1u)Q0
_CO0Zdzv;h!_&F0+WL&RpR,^051Testing软件测试网N;K|U4^ D+_2YXi(|
t`W T"k05FN sM*h)YJ4h0
1@3[/OSJs0
P!^u1GM6JA WN\0从上图中我们可以看出,不同浏览器的WebDriver子类,都需要依赖特定的浏览器原生组件,例如Firefox就需要一个add-on名字叫webdriver.xpi。而IE的话就需要用到一个dll文件来转化Web Service的命令为浏览器native的调用。另外,图中还标明了WebDriver Wire协议是一套基于RESTful的web service。如果不明白什么是RESTful的,可以参见笔者之前另外一篇介绍REST的blog(http://blog.csdn.net/ant_yan/article/details/7963517)51Testing软件测试网.g+id6^h~h.e2b"l
51Testing软件测试网jWb$XI&u}5qoz
d2V2Od3vu0
(\Y(N*k:e0
3S:\}'}.d&_ vV%C0关于WebDriver Wire协议的细节,比如希望了解这套Web Service能够做哪些事情,可以阅读Selenium官方的协议文档, 在Selenium的源码中,我们可以找到一个HttpCommandExecutor这个类,里面维护了一个Map
xC f3|k)M0J051Testing软件测试网x1Ev6{.P
}V!B(j\#v[&E/h0
[java]view plaincopyprint?
1. nameToUrl = ImmutableMap.
2. .put(NEW_SESSION, post("/session"))
3. .put(QUIT, delete("/session/:sessionId"))
4. .put(GET_CURRENT_WINDOW_HANDLE, get("/session/:sessionId/window_handle"))
5. .put(GET_WINDOW_HANDLES, get("/session/:sessionId/window_handles"))
6. .put(GET, post("/session/:sessionId/url"))
7.
8. // The Alert API is still experimental and should not be used.
9. .put(GET_ALERT, get("/session/:sessionId/alert"))
10. .put(DISMISS_ALERT, post("/session/:sessionId/dismiss_alert"))
11. .put(ACCEPT_ALERT, post("/session/:sessionId/accept_alert"))
12. .put(GET_ALERT_TEXT, get("/session/:sessionId/alert_text"))
13. .put(SET_ALERT_VALUE, post("/session/:sessionId/alert_text"))
nameToUrl = ImmutableMap.
.put(NEW_SESSION, post("/session"))
.put(QUIT, delete("/session/:sessionId"))
.put(GET_CURRENT_WINDOW_HANDLE, get("/session/:sessionId/window_handle"))
.put(GET_WINDOW_HANDLES, get("/session/:sessionId/window_handles"))
.put(GET, post("/session/:sessionId/url"))
// The Alert API is still experimental and should not be used.
.put(GET_ALERT, get("/session/:sessionId/alert"))
.put(DISMISS_ALERT, post("/session/:sessionId/dismiss_alert"))
.put(ACCEPT_ALERT, post("/session/:sessionId/accept_alert"))
.put(GET_ALERT_TEXT, get("/session/:sessionId/alert_text"))
.put(SET_ALERT_VALUE, post("/session/:sessionId/alert_text"))
51Testing软件测试网#zcYj1x^
*i`.X7X4UN#`'J \0可以看到实际发送的URL都是相对路径,后缀多以/session/:sessionId开头,这也意味着WebDriver每次启动浏览器都会分配一个独立的sessionId,多线程并行的时候彼此之间不会有冲突和干扰。例如我们最常用的一个WebDriver的API,getWebElement在这里就会转化为/session/:sessionId/element这个URL,然后在发出的HTTP request body内再附上具体的参数比如by ID还是CSS还是Xpath,各自的值又是什么。收到并执行了这个操作之后,也会回复一个HTTP response。内容也是JSON,会返回找到的WebElement的各种细节,比如text、CSS selector、tag name、class name等等。以下是解析我们说的HTTP response的代码片段:51Testing软件测试网4YI,T8O{o Q&j
[x|`7YRx*B051Testing软件测试网5~n@PUI4A/o(V
t6X+xH;l1|w L$]0
_2\/VU!C/tZ$V051Testing软件测试网 Wv|Mk&O:~X
[java]view plaincopyprint?
1. try {
2. response = new JsonToBeanConverter().convert(Response.class, responseAsText);
3. } catch (ClassCastException e) {
4. if (responseAsText != null && "".equals(responseAsText)) {
5. // The remote server has died, but has already set some headers.
6. // Normally this occurs when the final window of the firefox driver
7. // is closed on OS X. Return null, as the return value _should_ be
8. // being ignored. This is not an elegant solution.
9. return null;
10. }
11. throw new WebDriverException("Cannot convert text to response: " + responseAsText, e);
12. } //...
try {
response = new JsonToBeanConverter().convert(Response.class, responseAsText);
} catch (ClassCastException e) {
if (responseAsText != null && "".equals(responseAsText)) {
// The remote server has died, but has already set some headers.
// Normally this occurs when the final window of the firefox driver
// is closed on OS X. Return null, as the return value _should_ be
// being ignored. This is not an elegant solution.
return null;
}
throw new WebDriverException("Cannot convert text to response: " + responseAsText, e);
} //...
51Testing软件测试网1o._4Q[[F%Z Y相信总结道这里,应该对WebDriver的运行原理应该清楚了!其实挺佩服这一套RESTful web service的设计。感觉封装WebDriver暴露出来的public API还可以更加友好跟强大一点,这次就先总结道这里,会继续分析Selenium源码,继续分享的!
对于lr的原理,我想应该和其它性能测试工具的原理是一样的。一般来说,性能测试工具都有一个虚拟用户脚本产生器(vugen),压力产生器和用户代理、压力调度和监控系统、压力结果分析工具对于虚拟用户产生器来讲,主要的功能就是通过代理接收从客户端发送的数据包,记录并转发给服务器,接收服务器端的数据包,记录并转发给客户端。除此之外,虚拟用户脚本生成器在截获数据之后,根据录制时使用的协议对数据包分析,用脚本函数记录下来。并提供了编译和调试的环境,用于对脚本的优化和修改压力产生器用于根据压力调度系统的命令,产生实际的负载压力调度和监控系统:压力调度工具可以根据用户的场景要求,设置不同的Vu数量等监控系统主要是用于对数据库、服务器的性能计数器等的监控压力结果分析工具主要是将获取的性能计数器的信息,生成相应的分析图
一、LoadRunner工具组成1、虚拟用户脚本生成器:捕获最终用户业务流程和创建自动性能测试脚本,即我们在以后说的产生测试脚本;2、压力产生器:通过运行虚拟用户产生实际的负载;3、用户代理:协调不同负载机上虚拟用户,产生步调一致的虚拟用户;4、压力调度:根据用户对场景的设置,设置不同脚本的虚拟用户数量;5、监视系统:监控主要的性能计数器;6、压力结果分析工具:本身不能代替分析人员,但是可以辅助测试结果的分析。
二、LoadRunner工具原理
代理(Proxy)是客户端和服务器端之间的中介人,LoadRunner就是通过代理方式截获客户端和服务器之间交互的数据流。
1、虚拟用户脚本生成器通过代理方式接收客户端发送的数据包,记录并将其转发给服务器端;接收到从服务器端返回的数据流,记录并返回给客户端。
这样服务器端和客户端都以为在一个真实运行环境中,虚拟脚本生成器能通过这种方式截获数据流;虚拟用户脚本生成器在截获数据流后对其进行了协议层上的处理,最终用脚本函数将数据流交互过程体现为我们容易看懂的脚本语句。
2、压力生成器则是根据脚本内容,产生实际的负载,扮演产生负载的角色。
3、用户代理是运行在负载机上的进程,该进程与产生负载压力的进程或是线程协作,接受调度系统的命令,调度产生负载压力的进程或线程。
4、压力调度是根据用户的场景要求,设置各种不同脚本的虚拟用户数量,设置同步点等。
5、监控系统则可以对数据库、应用服务器、服务器的主要性能计数器进行监控。
6、压力结果分析工具是辅助测试结果分析。
LoadRunner由以下部分组成:
虚拟用户发生器: Vuser Generator
压力调度和监控中心: Controller
压力产生器:Load Generator
压力结果分析工具: AnalysisVuser Generator是一个集成开发环境,用于录制回放修改Vuser脚本.Controller是一个框架程序和监控程序,负责将Vuser脚本以多进程/多线程方式在Load Generator机器上运行.Analysis是一个数据分析工具,可以安装在任何Windows平台机器上.Loadrunner进行测试的一般步骤:1.用户确定进行测试的业务或者交易,录制并生成脚本2.手工修改脚本,确定脚本能回放成功3.在Controller中对场景进行配置,启动测试,Controller控制Load Generator对被测系统的加压方式和行为4.Controller负责搜集被测系统各个环节的性能数据.各个Load Generator会记录最终用户相应时间和脚本执行的日志5.压力测试运行结束以后, Load Generator将数据传送到Controller中,由Controller对测试数据进行汇总6.用Analysis对数据进行分析7.对系统进行调优,重复进行压力测试,确定性能是否有所提高
1.录制的时候,QTP将我们操作过的所有对象都记录下来,保存在对象库object repository中,记录的形式是一个逻辑名加上若干识别属性
2.运行脚本时,QTP分析该脚本要执行那个对象的操作,然后根据该语句中的逻辑名,在对象库中查找该对象的详细记录,然后在运行的真实披头散发软件中按图索骥,找到需要操作的对象,把语句规定的操作施加在该对象上.施加的主要动作就是把操作的相关消息时间放入到该对象窗口的消息循环队列中
3.所以,一个完整的脚本测试应该包括两部分,一个是测试脚本的代码,一个是对象库
另外:
描述性编程和传统的QTP脚本的区别是:他把需要识别的对象的属性从对象库中转移到了脚本里面,通过在脚本里面的特殊语法格式.来告诉QTP识别对象的方法.描述性编程更加灵活,因为他不需要经过录制这个步骤,可以直接通过编程的方式操作任何一个你想操作的对象,只要你在测试脚本代码里提供给QTP识别该对象的足够信息。
描述性编程没有严格的要求,只要遵循一个原则:在代码中告诉QTP足够他识别该对象的属性,QTP就可以不通过对象库,而是通过描述性编程的方式完成对指定对象的操作.所以描述性编程没有什么神秘的,他无非是把原来存储在对象库中的信息提到代码中来了而已.
工作原理简单点说就是通过把QTP安装到电脑上,然后用生成脚本,回放来回归测试。 对象识别原理获取hwnd,然后判断ui属性,逐个判断,然后逐层递归,最后获取每个对象的所有层面的属性,跟对象库里的属性进行比较,匹配则应用,不匹配则智能识别(重新启动识别过程,过滤出一些符合条件的,进行判断后找到唯一匹配项) 复杂点说:把对象从被测软件ui中读取出主要特征,存入对象库,回放时在被测试软件中寻找指定对象,赋予对象一些方法,方法为windows win32或者web上的一些activex控件的通用方法(或者javascrīpt应用于一些未支持的事件,比如link.click),微软控件对外的接口,把其中一些方法进行封装,成为qtp自己的方法,比如getroproperty=对象。object.value,然后运用这些方法属性驱动被测试对象完成一些指定的动作。对于任何一个add-in都是先找到人家的对外接口,然后拿过来封装,需要的时候去调用接口事件,也就成为了QTP的动作。 此外还有数据驱动 xx驱动的,每种驱动方式都大同小异,瞎JB侃呗……怕啥的阿,反正谁都不明白。面试的人肯定也不明白啥,就是随便问问
一直认为学习一个工具一定要学会它的本质和原理才能真正的有所收获,不然工具换掉或者工具被淘汰就会很尴尬,又要从头学习。当你学会了本质,其他的工具学习起来也会很快的,并且从开发的角度去思考问题,更容易学习好一个工具。最近学习了几天的QTP,对QTP的原理有一个简单的认识,和大家分享下。
个人认为QTP的脚本运行其实就是一组对象有组织的执行自己的方法,最终完成一个流程的过程。当打开一个web时,想要脚本能够模拟人来操作整个流程,那多就要求这个脚本能够识别人的每一个操作,而人的操作实际上是对web页面上控件的操作,所以只要QTP的脚本能够识别人操作过的控件就可以模拟人的操作流程,而web页面上的控件都是QTP脚本中的对象,也就是说只有QTP脚本中的对象能够被唯一的识别出来,就可以模拟人的整个操作流程。而QTP又是如何识别对象的呢?
对象识别原理就是获取hwnd,然后判断ui属性,逐个判断,然后逐层递归,最后获取每个对象的所有层面的属性,跟对象库里的属性进行比较,匹配则应用。也就是说在你添加一个对象到对象仓库中的时候,该对象的主要属性都被保存到对象库中了,回放QTP脚本的时候实际上就是在被测试软件中寻找指定的对象,然后按照这些对象指定的方法去完成一个动作,而这些方法就是把windows win32中、web上的一些activex控件中的方法和微软控件对外的接口中的一些方法进行封装,成为qtp自己的方法。对于任何一个add-in都是先找到人家的对外接口,然后拿过来封装,需要的时候去调用接口事件,也就成为了QTP的动作。
所以QTP脚本回放实际上就是要做两个步骤:1)识别出要操作的对象控件。2)识别出对象控件后来完成该对象控件指定的方法。
在QTP识别对象的时候是按照对象的唯一属性来区分的,有时候QTP对象仓库保存的对象属性是不完全的,导致两个很相似的对象不能够识别出来,这样脚本就会报错,或者说对象仓库中对象的属性每次都是变化的,那么每次回放脚本也会和对象仓库中保存的不一致导致脚本报错。这里介绍一个很好用的web对象的属性——object属性。
QTP支持直接访问DOM,可以通过DOM来访问HTML标签。在QTP中,访问DOM是通过使用page测试对象的object属性来进一步访问的,这样就可以访问到很底层的对象属性,可以用底层的对象属性来唯一区分web页面上的对象控件,这样就能够解决一些关于对象识别的错误。
用page页中的Link对象举例说明object属性:
Browser(”网易”).Page(”网易”).Link(”VIP收费邮箱”).CheckProperty “URL”,”http://vip.163.com/”
其中使用了CheckProperty方法来对比Link对象的URL属性是否等于指定的地址(http://vip.163.com/),也可以用Link对象的object属性中的herf属性来对比,代码如下:
herf = Browser(”网易”).Page(”网易”).Link(”VIP收费邮箱”).Object.href
If not herf = “http://vip.163.com/” Then
reporter.ReportEventmicFail,http://vip.163.com/, herf
End If
窗体顶端
利用开发语言的反编译原理,将控件的方法和属性预先封装。如此,在录制的时候,只要打开相应的插件,QTP就能够准确识别控件类型及其对应的属性等内容(特殊控件除外)。
Quick Test Professional
一、简答题:
1
QTP有哪几种录制模式?
答:正常录制模式、低级录制模式、模拟录制模式
2LoadRunne和QTP的工作原理有何不同?
LoadRunner:基于协议的性能测试
录制原理:捕获数据包
QTP:基于UI(用户界面)对象的功能测试
录制原理:识别对象
测试计算器时,如何加入所有控件?怎样将加入的控件导出,以便下次再用?
4、QTP中global 和local 的区别?两者的迭代次数分别在哪里设置?
5、用
QTP录制脚本后如何修改脚本能够增加其灵活性?
答:参数化、封装、ACTION复用
6、QTP中Object Spy的作用?能否用它来添加对象,如果可以说明步骤,如果不可以请说明如何添加。
答:查看对象,包括属性和方法
不能添加对象
7、如何修改共享对象库中的值并使之生效?
8、Action1中参数传递到Action2中有哪几种方式,请分别加以说明。Datetable
共享、通过环境变量共享、通过
Action parameters
的参数传递
9、正则表达式:\(?0\d{2}[) -]?\d{8}表示什么内容?
答:表示国内电话号码
10、 [QTP]请将下面操作用另外一种描述性编程实现(通过Description对象):
Window("Error").WinButton("text:=OK", "index:=1").Click
二、脚本开发题
1、录制
QTP自带软件
Flight4a
系统的订票功能,
要求:
对所订机票的机舱类型进行参数化,
即实现
定购如下图所示
3张不同机舱类型(如图)的飞机票,如何实现?
2、如果运行
Fight4a的登录脚本前(脚本中需要QTP自动打开登录框)被测对象已经存在多个了
(如图)
这将导致后续步骤出错,如何能够先将已经打开的登录框关闭再执行登录呢,请将脚
本中关闭多余登录框功能补充完整。
3
、如果将DataTable中原本写在Global的参数改写在Action中,则需要加上怎样的代码达到原来的迭代效果?注:Action1迭代设为一次
4、用QTP打开Word并录入内容。
5、用QTP打开记事本写入汉字。
三、性能测试工具使用题:
1. [LoadRunner]web
系统中username参数表为file类型,表中有12个值,分别A、B、C、D、E、F、G、H、I、J、K、L。测试场景中虚拟并发用户数设为
4,迭代次数设为
3,参数中Select next row与Update value on分别为(Unique, Each Iteration)与(Sequential, Once)时,写出迭代3次的取值情况。
本文来源:https://www.2haoxitong.net/k/doc/96de65c2915f804d2a16c103.html
文档为doc格式