-
LoadRunner和HTTP协议
2010-08-20 10:44:01
最近在做性能测试,在开发web脚本的过程中遇到错误: Action.c(15): Error -26631: HTTP Status-Code=400 (Bad Request) for http://xxxxxx/onlinefront/s.do?tl=51&bk=null&optionId=244&p=110
问了很多人没有人知道问题的原因,最后只能自己潜心研究,首先从http status-code400的错误开始分析, 这个错误是说请求无法被处理,因为它含有缺失或无效的信息, 根据错误信息的描述应该是发送HTTP请求中语法格式不正确导致不被服务器接受,这很可能就是通过LoadRunner 发送HTTP请求是一个不完整。那么首先要确认的就是比较发出的请求和录制的时候请求看是否丢失了http信息来判断错误的原因。
首先选上runtimesettings中extended log的三个选项后运行此脚本,用录制发送相类似请求的日志和选择exection log的http send请求日志进行对比 。拷贝这些请求数据包到一个记事本中然后进行比较。在Recording Log(单协议)或Generation Log(多协议)中查找是否存在头数据包,我们发现在执行日志中头域失踪了。
这样解决问题的方法就是用web_add_header("xxxxx","yyyy")添加一个http头,在错误的请求前添加此函数然后回放。
如果你发现所有的HTTP send请求都缺少头数据包,在脚本中的开头添加web_add_auto_header(”XXXXX“,”yyyy“);随着web_add_auto_header的添加,你不需要为每个HTTP send请求都添加web_add_header了。
还有一个解决问题的方法是在Tools -> Recording Options -> Advanced tab中设置,点“headers”按钮,在列表中选择“Record Headers in the List”,然后选择“XXXXX”,因此它可确保在录制过程中录制自己。
-
QTP回放解决方案(学员翻译)
2010-06-16 20:50:42
2008年10月3号
大家好,
已经有一段时间没给大家回信了,对此我非常抱歉。由于公司的业务繁忙所以我一直没有给大家回信,当然我知道不应该拿这个当作借口。
这次,我将给大家讲解一下有关QuickTest Professional的使用。
多数时间,QTP可以自动录制你与应用系统交互的每一步,然而,尽管QTP功能强大,但是当应用界面对象不是标准的时候QTP也是无能为力的。
这是因为对于应用界面不标准的类型,QTP没有关于它的先前的资料,所以当遇到这种问题的时候就不知道如何去解决。正如一个孩子当在一些新的事情上犯错误的时候就不知道怎么去解决。
对于这类型的问题,我们有两种解决办法:
告诉QTP这个新事物的所有相关信息并且解释处理的方法。意思就是说当QTP下次遇到这个新事物的时候,就会自动识别并恰当地使用这个新事物(知道该记录什么和如何去回放这些记录)。让QTP执行这个,你需求使用一个叫做可延长性的功能。然而,我们都知道创建可延长性是有一定难度的而且它会限制QTP添加项的使用。
你创建一个回放的解决方案。意思是当你录制的时候QTP任然不知道你对这个应用的操作,所以不会恰当的生成脚本但是在录制的过程中QTP能够和应用交互。为了实现这个功能,你必须写自定义的函数并且详细的描述每一步的操作。这类型的解决方案往往使你的脚本更容易理解。下面我们会对这种解决方案进行详细的介绍。
因此,首先,我们使用QTP录制一段脚本:
Window(“Paint”).WinObject(“Colors”).Click 72,33
Window(“Paint”).WinObject(“Tools”).Click 21,148
Window(“Paint”).WinObject(“Afx:1000000:8″).Click 48,99
Window(“Paint”).WinObject(“Afx:1000000:8″).Drag 100,48
Window(“Paint”).WinObject(“Afx:1000000:8″).Drop 137,100
Window(“Paint”).WinObject(“Afx:1000000:8″).Drag 52,98
Window(“Paint”).WinObject(“Afx:1000000:8″).Drop 135,98
Window(“Paint”).WinObject(“Afx:1000000:8″).Drag 47,100
Window(“Paint”).WinObject(“Afx:1000000:8″).Drop 47,189
Window(“Paint”).WinObject(“Afx:1000000:8″).Drag 47,189
Window(“Paint”).WinObject(“Afx:1000000:8″).Drop 134,188
Window(“Paint”).WinObject(“Afx:1000000:8″).Drag 137,102
Window(“Paint”).WinObject(“Afx:1000000:8″).Drop 135,188
Window(“Paint”).WinObject(“Colors”).Click 134,30
Window(“Paint”).WinObject(“Tools”).Click 15,174
Window(“Paint”).WinObject(“Tools”).Click 31,261
Window(“Paint”).WinObject(“Afx:1000000:8″).Drag 82,144
Window(“Paint”).WinObject(“Afx:1000000:8″).Drop 102,187
如果你录制了这段脚本,你将看到QTP画了一个院长的图画,但是我们看这段脚本根本没有一点标示是画这个图画。同时你也注意到这个图画中丢失了一个元素,我没有忘记这个元素但是QTP却没有录制它。
现在,让我们看一下我们如何提高我们的脚本并且使我们的录制方案比QTP目前所能及的做的更好。
首先行为第一,你想让你的脚本看起来像什么呢?我个人的意见是,像下面的这段脚本就是好的:
SelectColor “red”
SelectTool “line”
DrawLine 100, 48, 137, 100
DrawLine 52, 98, 135, 98
…
SelectColor “blue”
SelectTool “rectangle”
DrawRectangle 82, 144, 102, 187
如果你像我,仅仅读了上面的一段脚本,你理解QTP做了什么,画了一根红色的线,一个蓝色的矩形等等[但是,你任然不会知道最后这个图画是什么样子]。你相信吗,这就是向前的方式?是的,的却如此,让我们出发。
首先,我们需要去了解QTP从应用中获悉什么。为了知道这个,我们使用间谍工具去监视QTP怎样识别你的对象。你将注意到所有的工具栏(工具和颜色)都作为一种对象被识别,同时QTP不能识别个别的按钮但是你能看到QTP识别了这种对象并作为WinObjects。
当你在应用中看到WinObjects的时候,你应该立刻意识到,这些是QTP不能识被的对象。事实如此,WinObjects是当QTP不了解这个对象时用来使用的一种类型。如果QTP了解对象的一些属性,QTP将把这些对象分类为一些其他的对象(WinEdit, WinButton, WinToolbar, 等等.)因此,对选项里的任一项,QTP有一种类型用来标示它不识别这个对象。WinObject为windows的对象,WebElement为Web的对象,JavaObject为Java对象,SwfObject为.Net对象等等。
因此,现在我们知道QTP只识别3个对象,这3个对象全是WinObjects:工具栏,颜色栏和画布(画图区域)。对于我们的回放,这些信息就已经足够了,因为我们总是通过使用点击工具栏里的按钮并使用相应的提示进行操作,只要这个按钮在工具栏里不移动就可以。
现在我们做一个简单的例子:
当我们选择了这个线条工具,QTP识别下面的状态:
Window(“Paint”).WinObject(“Tools”).Click 21,148
如果你试图努力,你可能得到一些和这些提示不同的值但是他们基本上一致,实际上,你可以点击任何地方只要他在工具栏的范围之内并且你可以选择即可。
现在,我们写一些和以下相似的事情:
SelectTool “Line”
如果你现在就这样执行脚本,QTP将提示它不理解关于选择工具栏的任何信息。而且QTP将通过“配错类型”错误提示显示出来。如果你同意的话,告诉QTP怎样去做。
我们需要创建一个和选择工具栏相同名字的程序,并且规定当执行程序时相应的做什么。
Sub SelectTool(ToolName)
If ToolName = “Line” Then
Window(“Paint”).WinObject(“Tools”).Click21,148
Else
MsgBox “Invalid tool name.”
End If
End Sub
SelectTool “Line”
如果你再次执行这段脚本,你将注意到QTPP不会验证关键字是否正确,反而直接调用Line 工具。
因此,现在你可以在你的函数里添加许多新的灵活的工具:
Sub SelectTool(ToolName)
Select Case ToolName
Case “Line”
Window(“Paint”).WinObject(”Tools”).Click16,142
Case “Curve”
Window(“Paint”).WinObject(”Tools”).Click 41,143
Case “Rectangle”
Window(“Paint”).WinObject(”Tools”).Click 16,165
Case “Polygon”
Window(“Paint”).WinObject(”Tools”).Click 41,166
Case Else
Reporter.ReportEvent micFail, “SelectTool”, “The tool ‘” & ToolName & “‘ does not exist.”
End Select
End Sub
SelectTool “Line”
SelectTool “Rectangle”
SelectTool “Line”
好了,现在你得到了图片并且你也知道如果新建SelectColor程序了。
但是,让我们看的远一点,因为我们写的程序并不完美。当我们写函数或程序时,我们可以使用变量让它更灵活而且我们可以参数化工具名称实现。然而,一个函数或是程序可以完全独立于外部数据除了通过参数传递数据。并且在当前情况下,SelectTool是依靠Window(”Paint”).WinObject(”Tools”)。所以,为了完善我们的函数,我们从这种方式重写:
Sub SelectTool(ToolObject, ToolName)
Select Case ToolName
Case “Line”
ToolObject.Click 16,142
Case “Curve”
ToolObject.Click 41,143
Case “Rectangle”
ToolObject.Click 16,165
Case “Polygon”
ToolObject.Click 41,166
Case Else
Reporter.ReportEvent micFail, “SelectTool”, “The tool ‘” & ToolName & “‘ does not exist.”
End Select
End Sub
SelectTool Window(“Paint”).WinObject(“Tools”), “Line”
SelectTool Window(“Paint”).WinObject(“Tools”), “Rectangle”
SelectTool Window(“Paint”).WinObject(“Tools”), “Line”
现在,我们通过添加一个新的参数从工具对象中移除附属,增加句法的结构复杂性。但是QTP有一个特性就是容许我们给QTP对象注册一个函数或者程序。通过做这些,我们告诉QTP对象现在是一个新的函数:
Sub SelectTool(ToolObject, ToolName)
Select Case ToolName
Case “Line”
ToolObject.Click 16,142
Case “Curve”
ToolObject.Click 41,143
Case “Rectangle”
ToolObject.Click 16,165
Case “Polygon”
ToolObject.Click 41,166
Case Else
Reporter.ReportEvent micFail, “SelectTool”, “The tool ‘” & ToolName & “‘ does not exist.”
End Select
End Sub
RegisterUserFunc “WinObject”, “SelectTool”, “SelectTool”
Window(“Paint”).WinObject(“Tools”).SelectTool “Line”
Window(“Paint”).WinObject(“Tools”).SelectTool “Rectangle”
Window(“Paint”).WinObject(“Tools”).SelectTool “Line”
现在,我们知道在我们的工具栏里如何去选择一个项目,让我们看一下在画布里画图。之前,我们规定和下面的代码相似是理想的:
SelectColor “red”
SelectTool “line
DrawLine 100, 48, 137, 100
So, now we need to draw a line:
Sub DrawLine(CanvasObject, StartX, StartY, EndX, EndY)
CanvasObject.Drag StartX, StartY
CanvasObject.Drop EndX, EndY
End Sub
RegisterUserFunc “WinObject”, “DrawLine”, “DrawLine”
Window(“Paint”).WinObject(“Afx:1000000:8″).DrawLine 44, 34, 128, 34
完整脚本如下:
Dim dicTools
Set dicTools = CreateObject(“Scripting.Dictionary”)
dicTools.Add “line”, Array(16, 142)
dicTools.Add “curve”, Array(41, 142)
dicTools.Add “rectangle”, Array(16, 165)
dicTools.Add “polygon”, Array(31, 165)
Dim dicColors
Set dicColors = CreateObject(“Scripting.Dictionary”)dicColors.Add “black”, Array(42, 14)
dicColors.Add “white”, Array(42, 30)
dicColors.Add “red”, Array(72, 33)
dicColors.Add “yellow”, Array(89, 33)
dicColors.Add “green”, Array(103, 33)
dicColors.Add “blue”, Array(135, 33)
Sub SelectTool(ByRef objTool, ByVal ToolName)
ToolName = LCase(ToolName)
If dicTools.Exists(ToolName) Then
objTool.Click dicTools(ToolName)(0), dicTools(ToolName)(1)
Reporter.ReportEvent micPass, “SelectTool”, “The tool ‘” & ToolName & “‘ has been selected.”
Else
Reporter.ReportEvent micFail, “SelectTool”, “The tool ‘” & ToolName & “‘ does not exist.”
End If
End Sub
Sub SelectColor(ByRef objColor, ByVal ColorName)
ColorName = LCase(ColorName)
If dicColors.Exists(ColorName) Then
objColor.Click dicColors(ColorName)(0), dicColors(ColorName)(1)
Reporter.ReportEvent micPass, “SelectTool”, “The color ‘” & ColorName & “‘ has been selected.”
Else
Reporter.ReportEvent micFail, “SelectTool”, “The color ‘” & ColorName & “‘ does not exist.”
End If
End Sub
Sub DrawLine(CanvasObject, StartX, StartY, EndX, EndY)
CanvasObject.Drag StartX, StartY
CanvasObject.Drop EndX, EndY
End Sub
Sub DrawRectangle(CanvasObject, StartX, StartY, EndX, EndY)
CanvasObject.Drag StartX, Start
-
如何获得一个ie已经存在的实例
2009-08-02 17:12:23
最近一个朋友在做自动化测试过程中想实现一个时时监控的功能,如只要前台有人打开网页,后台qtp就开始对页面上进行校验,这个功能很新颖第一次听同行有这样的思路。该工具的实现难点在于后台什么时候触发qtp执行自动化测试,这一点通过hook的技术可以实现,进入系统消息链,监控特定消息,然后触发qtp。hook的开发还需要一段时间,他暂时先不是同步进行校验,测试人员打开ie之后先手工出发校验。这里涉及到如何获得已经存在的ie的实例,也就是测试人员打开ie后,打开网页执行测试,同时点击执行qtp脚本,qtp开始在测试人员打开的那个页面上进行校验。这里主要用internet controls,ie application对象实现。
Dim objSW 'As SHDocVw.ShellWindows
Dim objIE 'As SHDocVw.InternetExplorer
Dim objDoc 'As ObjectSet objSW = New SHDocVw.ShellWindows
For Each objIE In objSW
If InStr(1, UCase(objIE.FullName), "IEXPLORE.EXE") <> 0 Then
If objIE.LocationURL = "目标链接地址" Then
Set objIE = objSW.Item
Exit For
End If
End If
Next 'objIE
'下面一行为控制代码,替换需要的操作代码
objIE.Navigate "www.sohu.com"Set objSW = Nothing
Set objIE = Nothing以上代码在vb中调试通过
-
如何用QTP处理dll中带有枚举类型返回值的函数
2009-05-16 23:15:31
收到一封学员关于用qtp处理dll中带有枚举型返回值函数的问题,这里是给他的一个答复。老师:
我用QTP 调用VC 编写的dll (extern c 编译)
MXDSGC_API MXD_RTN_CODE_E SGC_InitWinsock(void);
/*!
* This function is used to close windows socket port.
* \param
* void
* \return
* Return code by MXD_RTN_CODE_E enumeration.
*/
我在QTP中应该如何调用?
extern.Declare micUInteger
,"SGC_InitWinsock","D:\Share\MA\SiggenControl\sgc.dll","",micVoid
如果返回值是枚举型,在QTP中用什么返回呢?回答:关于qtp调用dll返回的是枚举型函数的问题,其实只要把返回值设置成MicInteger类型就可以,我写了一个例子请看下
在vc中编写了一个带有枚举型返回值函数的dll
lib.h中代码如下:
enum color
{red,yellow,blue};#ifndef LIB_H
#define LIB_H
extern "C" color __declspec(dllexport)getcolor();
#endif
lib.cpp中代码如下
#include "lib.h"
color getcolor()
{
color c_1;
c_1=red;
return c_1;
}
以上代码纯为了说明问题,vc6中编译通过qtp中调用代码
Extern.Declare micInteger,"getcolor","D:\VC\dll\Debug\dll.dll","getcolor"
Dim iColorResult
iColorResult=Extern.getcolor()
If iColorResult =0 Then
msgbox "red"
End IfIf iColorResult =1 Then
msgbox "yellow"
End IfIf iColorResult =1 Then
msgbox "blue"
End If以上代码在qtp10中调式通过。
这里关键是vbs是无类型的编程语言,所以不能和c语言中的类型进行匹配,如我们会在vb中声明同类型的变量更替它,vb中的调用代码如下:
Private Declare Function getcolor Lib "D:\VC\dll\Debug\dll.dll" () As colorPrivate Enum color
red
yellow
blue
End EnumPrivate Sub Command1_Click()
Dim iColorResult As color
iColorResult = GetColor()
MsgBox iColorResult
End Sub可以在vb中声明替代枚举型变量,与c中的枚举类型相匹配。
-
Qtp 问题代码收集
2008-12-25 22:09:31
1.环境变量处理数组
Dim MyArray(1) MyArray(0) = "10" MyArray(1) = "20" Environment.Value("Env") = MyArray MsgBox Environment.Value("Env")(0) 'Output = 10
2.How to specify the tolerance for a bitmap checkpoint
The user would like to specify the acceptable number of failing pixels between the expected and actual bitmap.
________________________________________
Solution: Set the relevant value for the key in the registry
1. Open the registry editor (Start -> Run -> regedit).
2. Navigate to HKEY_CURRENT_USER\SOFTWARE\Mercury Interactive\QuickTest Professional\MicTest\BitmapVerification.
3. Right-click on the ToleranceLevel value.
4. Select "Modify" from the pop-up menu.
5. Set the desired tolerance level. This value is the number of pixels that the checkpoint can allow to be different.
6. Close the registry editor.
You will need to restart QuickTest Professional for the changes to take effect.
Example:
If you set the tolerance level to be 10, then the checkpoint will pass as long as ten or less pixels are different between the expected and actual images3.db code function
'Example of How to use functions.
''************************************************************************************
' Example use DSN created for database of Flight sample application.
''************************************************************************************
SQL="SELECT * FROM ORDERS"
connection_string="QT_Flight32"
isConnected = db_connect ( curConnection ,connection_string )
If isConnected = 0 then
' execute the basic SQL statement
set myrs=db_execute_query( curConnection , SQL )
' report the query and the connection string
Reporter.ReportEvent micInfo ,"Executed query and created recordset ","Connection_string is ==> " & connection_string & " SQL query is ===> " & SQL
' show the number of rows in the table using a record set
msgBox " Quantity of rows in queried DB ( db_get_rows_count )==> " & db_get_rows_count( myrs )
' show the number of rows in the table using a new SQL statement
msgBox " Quantity of rows in queried DB (db_get_rows_count_SQL ) ==> " & db_get_rows_count_SQL( curConnection , "SELECT COUNT(*) FROM ORDERS" )
' change a value of a field in an existing row
rc = db_set_field_value (curConnection, "ORDERS" , "Agents_Name" , "test", "Agents_Name", "AGENT_TESTER")
' examples of how to retrieve values from the table
msgBox "val row 0 col 0: " & db_get_field_value( myrs , 0 , 0 )
msgBox "val row 0 col 1: " & db_get_field_value( myrs , 0 , 1 )
msgBox "val row 1 col Name: " & db_get_field_value( myrs , 1 , "Agents_Name" )
msgBox "val SQL row 1 col Name: " & db_get_field_value_SQL( curConnection , "ORDERS" , 1 , "Agents_Name" )
db_disconnect curConnection
End If
''****************************************************************************************
' Database Functions library
'******************************************************************************************
'db_connect
' ---------------
' The function creates a new connection session to a database.
' curSession - the session name (string)
' connection_string - a connection string
' for example the connection_string can be "DSN=SQLServer_Source;UID=SA;PWD=abc123"
'******************************************************************************************
Function db_connect( byRef curSession ,connection_string)
dim connection
on error Resume next
' Opening connection
set connection = CreateObject("ADODB.Connection")
If Err.Number <> 0 then
db_connect= "Error # " & CStr(Err.Number) & " " & Err.Descrīption
err.clear
Exit Function
End If
connection.Open connection_string
If Err.Number <> 0 then
db_connect= "Error # " & CStr(Err.Number) & " " & Err.Descrīption
err.clear
Exit Function
End If
set curSession=connection
db_connect=0
End Function
'********************************************************************************************
' db_disconnect
' ---------------------
' The function disconnects from the database and deletes the session.
' curSession - the session name (string)
'********************************************************************************************
Function db_disconnect( byRef curSession )
curSession.close
set curSession = Nothing
End Function
'*********************************************************************************************
' db_execute_query
' ---------------------------
' The function executes an SQL statement.
' Note that a db_connect for (arg1) must be called before this function
' curSession - the session name (string)
' SQL - an SQL statement
'**********************************************************************************************
Function db_execute_query ( byRef curSession , SQL)
set rs = curSession.Execute( SQL )
set db_execute_query = rs
End Function
''***********************************************************************************************
' db_get_rows_count
' ----------------------------
' The function returns the number of rows in the record set
' curRS - variable , contain record set , that contain all values that retrieved from DB by query execution
''***********************************************************************************************
Function db_get_rows_count( byRef curRS )
dim rows
rows = 0
curRS.MoveFirst
Do Until curRS.EOF
rows = rows+1
curRS.MoveNext
Loop
db_get_rows_count = rows
End Function
''************************************************************************************************
' db_get_rows_count_SQL
' ------------------------------------
' The function returns the number of rows that are the result of a given SQL statement
' curSession - the session name (string)
' CountSQL - SQL statement
''************************************************************************************************
Function db_get_rows_count_SQL( byRef curSession ,CountSQL )
dim cur_rs
set cur_rs = curSession.Execute( CountSQL )
db_get_rows_count_SQL = cur_rs.fields(0).value
End Function
''*************************************************************************************************
' db_get_field_value_SQL
' -----------------------------------
' curSession - variable denote current active connection
' tableName - name of the table , where value should be retrieved
' rowIndex - row number
' colName - the column name.
'*************************************************************************************************
Function db_get_field_value_SQL( curSession , tableName , rowIndex , colName )
dim rs
SQL = " select " & colName & " from " & tableName
set rs = curSession.Execute( SQL )
rs.move rowIndex
db_get_field_value_SQL = rs.fields(colName).value
End Function
'*************************************************************************************************
' db_get_field_value
' --------------------------
' The function returns the value of a single item of an executed query.
' Note that a db_execute_query for (arg1) must called before this function
' curRecordSet - variable , contain record set , that contain all values that retrieved from DB by query execution
' rowIndex - the row index number (zero based)
' colIndex - the column index number (zero based) or the column name.
' returned values
' -1 - requested field index more than exists in record set
'*************************************************************************************************
Function db_get_field_value( curRecordSet , rowIndex , colIndex )
dim curRow
curRecordSet.MoveFirst
count_fields = curRecordSet.fields.count-1
If ( TypeName(colIndex)<> "String" ) and ( count_fields < colIndex ) then
db_get_field_value = -1 'requested field index more than exists in recordset
Else
curRecordSet.Move rowIndex
db_get_field_value = curRecordSet.fields(colIndex).Value
End If
End Function
'*************************************************************************************************
' db_set_field_value
' ---------------------------
' The function changes the value of a field according to a search criteria.
' We search for a certain row according to a column name and the desired vale, then we change a value in that row according
' to a desired columns
' curConnection - the session name (string)
' tableName - name of the table , where value should be retrieved
' colFind - the column we search the criteria in
' colFindValue - the value we search in the column
' colChange - the column were we want to change the value
' colChangeValue - the new value
' returned values
' -1 - requested field index that doesn't exists in the recordset
'*************************************************************************************************
Function db_set_field_value(curConnection, tableName , colFind , colFindValue, colChange, colChangeValue)
dim curRow
dim updateSQL
dim checkSQL
checkSQL = "select * from Details"
set myrs1 = db_execute_query( curConnection , SQL )
myrs1.MoveFirst
count_fields = myrs1.fields.count
If ( TypeName(colFind)<> "String" ) or ( TypeName(colChange)<> "String" ) then
db_set_field_value = -1 'requested field index that doesn't exists in the record set
Else
updateSQL = "UPDATE " & tableName & " SET " & colChange & "='" & colChangeValue & "' WHERE " & colFind & "='" & colFindValue & "'"
set myrs1 = db_execute_query( curConnection , updateSQL )
db_set_field_value = 1 'operation suceeded
End If
End Function
'*************************************************************************************************
' db_add_row
' -----------------
' The function adds a new row to the desired table
' curConnection - variable , contains a recordset , that contains all the values to be retrieved from DB by query execution
' tableName - name of the table , where value should be retrieved from
' values - array that contains values to be entered in a new row to the table.
' Note: the function must receive values for all the columns in the table!
' returned values
' -1 - the number of values to be entered to the table doesn't fit the number of columns
' 1 - execution of the query succeed and the data was entered to the table
'*************************************************************************************************
Function db_add_row(curConnection, tableName , byRef values)
dim i
dim updateSQL
dim myrs1
updateSQL = "INSERT INTO " & tableName & " VALUES ("
arrLen = UBound (values) - LBound (values) + 1
set myrs1=db_execute_query( curConnection , SQL )
myrs1.MoveFirst
count_fields = myrs1.fields.count
' check if numbers of values fit the numbers of columns
If arrLen <> count_fields then
db_add_row = -1
Else
For i = 0 to arrLen-1
updateSQL = updateSQL & values (i)
If i <> arrLen-1 then
updateSQL = updateSQL & ","
End If
Next
updateSQL = updateSQL & ")"
set myrs1 = db_execute_query( curConnection , updateSQL )
db_add_row = 1
End If
End Function
'*************************************************************************************************
' represent_values_of_RecordSet
' ---------------------------------------------
' the function reports all the values on fields in a record set
' curRS - variable , contains the recordset , that contains all the values that were retrieved from the DB by the query execution
'*************************************************************************************************
Function represent_values_of_RecordSet( myrs)
dim curRowString
myrs.MoveFirst
reporter.ReportEvent 4,"Fields quantity" , myrs.fields.count
count_fields = myrs.fields.count-1
curRow=0
Do Until myrs.EOF
curRowString= ""
curRow = curRow+1
For ii=0 to count_fields
curRowString = curRowString& "Field " &"==> " & myrs.fields(ii).Name &" : Value ==>" & myrs.fields(ii).Value & vbCrLf
Next
myrs.MoveNext
reporter.ReportEvent 4,"Current row"& curRow , curRowString
Loop
End Function -
QTP FrameWork 子文件夹
2008-12-02 14:36:48
例:1.scrīpt files (.mts)2.OR files (.mtr-> per action , .tsr-> shared)3.Library files( .vbs)4.Recovery files (.qrs)5.Test Data files .xls6.Output/Result files (.xml)7.Functions (.txt)8. Documents (.xls or xlsx)例:1.SoftWare2.Config3.Log4.Report5.Test Data6.lib7.Documents -
hp winrunner 百问 三
2008-10-16 22:29:04
winRunner api 调用,例子中实现如何获得功能执行时间
load_dll ("C:\\WINNT\\SYSTEM32\\kernel32.dll");
extern int GetTickCount();
<code code code>
start=GetTickCount();
<transaction to be timed code goes here>
end=GetTickCount();
# Note posting to a file
post_tick_count_time_lapse(start,end,"Transaction Title","C:\\temp.txt");
#-----------------------------------------------------------------------------------------------------------------
# FUNCTION CODE BELOW
public function post_tick_count_time_lapse (in starttime, in endtime, in transtitle, in filename)
{
# Declare variables
auto timelapse;
# Validate parameters
if (nargs() != 3 && nargs() != 4)
{
tl_step ("get_tick_count_time_lapse", FAIL, "Only 3 or 4 parameters are allowed.");
return(E_ILLEGAL_NUM_OF_PARAMS);
}
# Determine how long the transaction took and round
timelapse = int((endtime - starttime) + .5)/1000;
# Post timelapse
if (nargs() == 3) # Was no ouput file specified
report_msg (transtitle & " : " & timelapse & " seconds"); # Send output to the test results
else # There was an ouput file specified
{
print (transtitle & " : " & timelapse & " seconds |" & get_time()) >> filename; # Append output to specified file
# Note "|" is a file delimiter above
close(filename);
}
return(E_OK);
}
-
hp-WinRunner 百问 二
2008-10-05 16:26:15
WinRunner使用TSL脚本,主要是类c的语法,wr调用com对象难度比较大,这里利用另类办法调用底层com对象,主要利用wr中file的操作以及dos_system的调用方法,实现,通过这种方法可以加强wr的脚本扩展能力的缺陷。例子如下:
public function send_outlook_mail(in to, in cc, in bcc, in subject, in body, in attach)
{
auto i, scrīpt;
scrīpt = getvar("exp") & "\\send_outlook_mail.vbs";
file_open(scrīpt,FO_MODE_WRITE);
file_printf(scrīpt,"Dim objOutlook\r\n");
file_printf(scrīpt,"Dim objOutlookMsg\r\n");
file_printf(scrīpt,"Set ōbjOutlook = CreateObject(\"Outlook.Application\")\r\n");
file_printf(scrīpt,"Set ōbjOutlookMsg = objOutlook.CreateItem(olMailItem)\r\n");
file_printf(scrīpt,"objOutlookMsg.To = \"%s\"\r\n", to);
file_printf(scrīpt,"objOutlookMsg.CC = \"%s\"\r\n", cc);
file_printf(scrīpt,"objOutlookMsg.BCC = \"%s\"\r\n", bcc);
file_printf(scrīpt,"objOutlookMsg.Subject = \"%s\"\r\n", subject);
file_printf(scrīpt,"objOutlookMsg.Body = \"%s\"\r\n", body);
file_printf(scrīpt,"objOutlookMsg.attachments.add \"%s\"\r\n", attach);
file_printf(scrīpt,"objOutlookMsg.Send\r\n");
file_close(scrīpt);
dos_system("wscrīpt \"" & scrīpt & "\"");
#dos_system("del \"" & scrīpt & "\"");
return(E_OK);
}
Example:
send_outlook_mail("pcl@51testing.com.com","","","Daily reports","Winrunner scrīpt to check the status of Daily reports is Completed.Check the results attached.Thanks.",file); -
hp - WinRunner 百问 一
2008-09-21 14:03:34
How to Close all Browsers that were opened before WinRunner?
1.The close_browsers function will close browsers that were opened
before WinRunner
function close_browsers()
{
auto count, count2;
count = 0;
while (win_exists("{class: window,MSW_class:browser_main_window,location:" & count & "}")==0)
count++;
for (count2 = 0; count2 < count; count2++)
win_close("{class: window,MSW_class:browser_main_window,location:"0"}");
# close IE-spawned browser windows
count = 0;
while (win_exists("{class: window,MSW_class: IEFrame,location:" &
count & "}")==0)
count++;
for (count2 = 0; count2 < count; count2++)
win_close("{class: window,MSW_class: IEFrame,location:"0"}");
# close browsers open before WinRunner
set_class_map("mic_unknown_class", "window");
set_record_attr("mic_unknown_class", "class", "MSW_class",
"location");
set_record_method("mic_unknown_class", RM_RECORD);
count = 0;
while (win_exists("{class: window,MSW_class:
mic_unknown_class,location: " & count & "}")==0)
count++;
for (count2 = 0; count2 < count; count2++)
win_close("{class: window,MSW_class: mic_unknown_class,location:" & count & "}");
}
2. This version of the function may execute a little faster when using
Netscape:public function close_all_browsers()
{
auto desc = "{class: window,MSW_class:
browser_main_window,NSTitle: \"Browser Main
Window\",location:",x,y,done;
auto desc2 = "{class: window,label: \"!.*Microsoft Internet
Explorer\",MSW_class: IEFrame,location:";
auto val = 50; # this line acutally sets the number of browser
windows to close
x = val;
done=0;
while(x >= 0)
{
y = desc&x--&"}";
if(E_OK == win_exists(y))
{
win_activate(y);
win_close(y);
}
}
x = val;
while(x>=0)
{
y = desc2&x--&"}";
if(E_OK == win_exists(y))
{
win_activate(y);
win_close(y);
}
}
return E_OK;
}3. You can use the undocumented _web_browser_close function to close
all browsers that were opened after WinRunner. Using empty quotes ""
will close all browsers opened after WinRunner. To close a specific
browser, pass the logical name of the browser to the function._web_browser_close("");
EXAMPLE:
# Close all open browser windows:
_web_browser_close("");
Here is a function which uses _web_browser_close to close the browsers
opened after WinRunner and will also close the browsers opened before
WinRunner.
public function close_all_browsers2()
{
# close browser opened after WinRunner
_web_browser_close("");
# close browsers open before WinRunner
set_class_map("mic_unknown_class", "window");
set_record_attr("mic_unknown_class", "class", "MSW_class",
"location");
set_record_method("mic_unknown_class", RM_RECORD);
while (win_exists("{class: window, MSW_class: mic_unknown_class,
location: 0}") == E_OK)
{
win_activate("{class: window, MSW_class: mic_unknown_class,
location: 0}");
win_close("{class: window, MSW_class: mic_unknown_class,
location: 0}");
}
}4. If the functions above have problems closing browsers opened before
WinRunner, you can try using the following code in one of the
functions. This code checks the items in the Shell Tray (the toolbar
at the bottom that lists the running applications) and selects the
ones that correspond with the browsers. This should bring the browser
to the foreground, at which point the type function can be used to
press Alt-F4 in order to close the window. This should work on Windows
95, 98, NT 4.0, and 2000. This will not work on Windows XP in its
current form, due to the way that Windows XP represents the items in
the Shell Tray as a toolbar, rather than a tab control. Similar
functionality should work, however, for Windows XP using toolbar
functions, instead of tab functions.
set_window("Shell_TrayWnd", 3);
tab_get_info("tab", "count", count);
for(i = count-1; i > 0; --i)
{
tab_get_item("tab", i, item);
if(match(item, ".*Microsoft Internet Explorer") > 0 || match(item,
".*Netscape") > 0)
{
tab_select_item("tab", "#" & i);
wait(1);
type("<kAlt_L-kF4>");
wait(1);
}
} -
Winrunner_Robot_数据驱动脚本代码
2008-09-17 13:04:56
WinRunner读取文件没有循环:# Flight Reservationwin_activate ("Flight Reservation");set_window ("Flight Reservation", 5);file_open("C:\\Win_execirses\\Dat2.txt",FO_MODE_READ);file_getline("C:\\Win_execirses\\Dat2.txt",line);split(line,array,";");obj_type ("MSMaskWndClass",array[1]);list_select_item ("Fly From:",array[2]); # Item Number 0;list_select_item ("Fly To:", array[3]); # Item Number 0;obj_mouse_click ("FLIGHT", 32, 45, LEFT);# Flights Tableset_window ("Flights Table", 2);button_press ("OK");# Flight Reservationset_window ("Flight Reservation", 8);edit_set ("Name:", array[4]);button_set ("First", ON);button_press ("Insert Order");set_window ("Flight Reservation", 8);obj_mouse_click ("Button", 7, 13, LEFT);#obj_mouse_click ("Button_4", 12, 11, LEFT);file_close("C:\\Win_execirses\\Dat2.txt");优化上面脚本,带有循环驱动:# Flight Reservationwin_activate ("Flight Reservation");set_window ("Flight Reservation", 5);file_open("C:\\Win_execirses\\Dat2.txt",FO_MODE_READ);while(file_getline("C:\\Win_execirses\\dat2.txt",line)==0){# file_getline("C:\\Win_execirses\\Dat2.txt",line);split(line,array,";");obj_type ("MSMaskWndClass",array[1]);list_select_item ("Fly From:",array[2]); # Item Number 0;list_select_item ("Fly To:", array[3]); # Item Number 0;obj_mouse_click ("FLIGHT", 32, 45, LEFT);# Flights Tableset_window ("Flights Table", 2);button_press ("OK");# Flight Reservationset_window ("Flight Reservation", 8);edit_set ("Name:", array[4]);button_set ("First", ON);edit_set ("Tickets:", array[5]);button_press ("Insert Order");set_window ("Flight Reservation", 8);# Flight Reservationset_window ("Flight Reservation", 4);obj_mouse_click ("Button", 18, 10, LEFT);# obj_mouse_click ("Button_4", 12, 11, LEFT);}file_close("C:\\Win_execirses\\Dat2.txt");Rational Robot脚本:数据驱动脚本-数据池技术:'$include "SQAutil.sbh"Sub MainDim dp_Result As Integerdim dp_id as long'dim dp_result as stringdim dp_Date as stringdim dp_Flyfrom as stringdim dp_Flyto as stringdim dp_Name as stringdp_id = SQAdatapoolopen("datapooltest1",FALSE, SQA_DP_SEQUENTIAL,FALSE)dp_result = SQAdatapoolfetch (dp_id)Window SetContext, "Caption=Flight Reservation", ""dp_result = SQAdatapoolvalue (dp_id,1,dp_Date)InputKeys dp_DateComboBox Click, "ObjectIndex=1", "Coords=120,13"dp_Result=SQADatapoolvalue(dp_id,2,dp_Flyfrom)ComboListBox Click, "ObjectIndex=1", "Text="&dp_FlyfromComboBox Click, "ObjectIndex=2", "Coords=117,8"dp_result=SQADatapoolvalue(dp_id,3,dp_Flyto)ComboListBox Click, "ObjectIndex=2", "Text="&dp_FlytoPushButton Click, "Text=FLIGHT"Window SetContext, "Caption=Flights Table", ""'ListBox Click, "ObjectIndex=1", "Text=16877 LAX 08:00 AM LON 08:45 AM SR $162.30;Coords=272,23"PushButton Click, "Text=OK"Window SetContext, "Caption=Flight Reservation", ""dp_result = SQAdatapoolvalue (dp_id,4,dp_Name)InputKeys dp_NamePushButton Click, "Text=Insert Order"PushButton Click, "ObjectIndex=9"dp_result=SQADatapoolClose (dp_id)End Sub数据驱动脚本-数据池技术 优化版本:'$include "SQAutil.sbh"Sub MainDim dp_Result As Integerdim dp_id as long'dim dp_result as stringdim dp_Date as stringdim dp_Flyfrom as stringdim dp_Flyto as stringdim dp_Name as stringdim i as integerdp_id = SQAdatapoolopen("datapooltest1",FALSE, SQA_DP_SEQUENTIAL,FALSE)for i = 1 to 3dp_result = SQAdatapoolfetch (dp_id)Window SetContext, "Caption=Flight Reservation", ""dp_result = SQAdatapoolvalue (dp_id,1,dp_Date)InputKeys dp_DateComboBox Click, "ObjectIndex=1", "Coords=120,13"dp_Result=SQADatapoolvalue(dp_id,2,dp_Flyfrom)ComboListBox Click, "ObjectIndex=1", "Text="&dp_FlyfromComboBox Click, "ObjectIndex=2", "Coords=117,8"dp_result=SQADatapoolvalue(dp_id,3,dp_Flyto)ComboListBox Click, "ObjectIndex=2", "Text="&dp_FlytoPushButton Click, "Text=FLIGHT"Window SetContext, "Caption=Flights Table", ""'ListBox Click, "ObjectIndex=1", "Text=16877 LAX 08:00 AM LON 08:45 AM SR $162.30;Coords=272,23"PushButton Click, "Text=OK"Window SetContext, "Caption=Flight Reservation", ""dp_result = SQAdatapoolvalue (dp_id,4,dp_Name)InputKeys dp_NamePushButton Click, "Text=Insert Order"PushButton Click, "ObjectIndex=9"next idp_result=SQADatapoolClose (dp_id)End Sub数据驱动 - 文件技术:Sub MainDim date1 as stringDim flyfrom as stringDim flyto as stringDim name1 as stringDim file as stringfile="C:\Rational_files\datafile1.txt"open file for input access read as #1while not eof(1)Input #1, date1,flyfrom,flyto,name1Window SetContext, "Caption=Flight Reservation", ""InputKeys date1ComboBox Click, "ObjectIndex=1", "Coords=108,15"ComboListBox Click, "ObjectIndex=1", "Text="&flyfromComboBox Click, "ObjectIndex=2", "Coords=118,11"ComboListBox Click, "ObjectIndex=2", "Text="&flytoPushButton Click, "Text=FLIGHT"Window SetContext, "Caption=Flights Table", ""PushButton Click, "Text=OK"Window SetContext, "Caption=Flight Reservation", ""InputKeys name1PushButton Click, "Text=Insert Order"PushButton Click, "ObjectIndex=9"wendclose #1End Sub -
HP 测试工具的支持环境
2008-09-14 19:17:04
到今天,hp性能测试工具LoadRunner已经支持多达60种协议,qtp提供25种插件环境,并且其他厂商提供了扩展插件2种,另外hp合作伙伴在测试管理工具qc基础上集成了13种插件,hp测试软件功能越来越强大同时软件包也变得越来越大
HP LoadRunner Protocols:
Action Message Format(AMF O)
c#.Net Template(Visual Studio add-in)
C++.net Template(Visual Studio add-in)
Citrix_ICA
COM/DCOM
CORBA-Java(tm)
C vuser
DB2 CLI
Domain Name Resolution(DNR)
Enterprise Java Protocol
HP WinRunner Software
HP QuickTestProfessional Software
i-mode
internet messaging(imap)
jacada
java Vuser
Javascrīpt Vuser
Lightweight Director Access Protocol(LDAP)
MediaPlayer(mms)
MQSeries-Client
MQSeries-Server
MS Exchange(MAPI)
MS Sequel Server Informix
Multimedia Messaging Service(MMS)
ODBC
Oracle(2-tier)
Oracle NCA
Oracle Web Application 11.i(c&s for Oracle)
Palm
PeopleSoft Tuxedo
Peoplesoft Enterprise(c&s for PeopleSoft)
Post office Protocol(POP3)
RealPlayer
Rmi-java
SAPGUI
SAPGUI/SAP-WEB Dual Protocol
SAP-WEB
Siebel-DB2 CLI
Siebel-Oracle
Siebel-MySql
Siebel-web
Simple Mail Protocol(smtp)
Sybase CTLib
Sybase DBLib
Terminal Emulation(RTE)
Tuxedo 6/7
vb vuser
vb scrīpt vuser
vbnet vuser
vbnet Template(visual studio add-in)
VoiceXML
WAP
Web(http/html)
web click and scrīpt
web services
windows socketsHP LoadRunner partner protocols:
Cognos(Genilogix)
JDBC(J2EE911)
SIP(Utopia)HP QuickTest Professional add-ins
Legacy 3270,5250 enulators,vt100
activex
citrix
delphi8.net winforms
htc/viewlink
internet explorer
java swt
jdedwards web client
jdk,javafoundation classes,awt
mozilla firefox 2.0
netscape
oracle 11i
peoplesoft 8.x
sap
siebel 7.x/8.x
stingray
visual basic
visualage smalltalk
vmware desktop support
web forms
web services
windows2000,windows2003,windowsxp ,windos vista
winforms
wpf from.net3.0HP QuickTest Professional partner add-ins
Flex(adobe)
TestAdvantage(infragistics)HP Quality Center partner integrations
AccuBridge(accurev)
CollabNet Conector(CollabNet)
NetProcess(Intellicorp)
Integrity Suite(MKS)
Personal Navigator(OnDemand Software)
SCM System(Perforce)
TestSmart(Sapphire infotech)
TeamTrack(Serena)
PVCS Version Manager(Serena)
Profesy(Sofea)
Solstice Integra Suite(Solstice Software)
QAInspect(SPI Dynamics)
Virual QA/Test Lab Management System(Surgient) -
自动化测试代码赏析
2008-09-11 11:43:37
如果你有兴趣,请把代码分析的结果写下来,因为看别人的代码,是提高自己自身能力的最快途径之一,当下公司招聘很多都需要测试人员懂工具,有的公司在面试过程中还拿出代码让测试人员来读,分析代码的结果是什么。古人说 "熟读唐诗三百首,不会做诗也会吟",希望你也成为一个自动化测试开发高手:
代码 一:
txtSearch = "T"
Set edtxt = CreateObject("Wscrīpt.Shell")
Browser(" ").Page(" ").WebEdit(" ").Click
edtxt.SendKeys(txtSearch)
Set edtxt = NothingrowCount = Browser(" ").Page(" ").WebTable(" ").GetROProperty("rows")
For j = 1 to rowCount
strTxt = Browser(" ").Page(" ").WebTable(" ").GetCellData(j,1)
strColor = Browser(" ").Page(" ").WebTable(" ").Object.rows(j-1).cells(1).currentStyle.backgroundColorIf strColor = "#fffde8" Then
reporter.ReportEvent micDone, "Text Highlighted", "The text """&strTxt&""" is highlighted."
strPos = left(strTxt,1)
If (Trim(txtSearch) = Trim(strPos)) Then
reporter.ReportEvent micPass, "Highlighted", "First Letter of highlighted text """&strTxt&""" starts with """&strPos&"""."
Else
reporter.ReportEvent micFail, "Highlighted", "Text """&strTxt&""" doesn't start with letter """&strPos&"""."
End If
End If
Next代码 二:
Set ōbjDesc = Descrīption.Create()
objDesc("micclass").Value = "WebEdit"
Set ōbjWE = Browser().Page().ChildObjects(objDesc)
objWECount = objWE.Count
For i = 0 to objWECount-1
Set ōbjCP = objWE(i).Object
ChkPointWE = objCP.IsContentEditable
Print objWE(i).GetROProperty("name")
Print ChkPointWE
Next代码 三:自动化测试框架代码,本框架基于SAFFRON(mercury)框架为基础实现的针对mercury自带的网上售票系统的关键字数据驱动其中SAFFRON(mercury)部分:
框架核心代码:
'************************************************************
' S.A.F.F.R.O.N. Prototype 1.1'
' Simple Automation Framework For Remarkably Obvious Notes
' Copyright © 2006 Mercury Interactive Corporation
'
' Notes:
'
' Requires QuickTest Professional 9.1
'
' Author : Adam Gensler
' Created : July 12, 2006
' Last Updated : September 11, 2006
'
' This prototype framework is provided AS IS, and is meant
' to be used for instructional purposes.
'
' This framework is a prototype, and is not supported
' by Mercury Interactive.
'
'************************************************************
initialized = false
thirdlevel = ""
level = ""
desc = ""
object = ""
objectDescrīption = ""levelsubdescrīptiondelimiter = ","
leveldescdelimiter = "|"
objectdelimiter = "|"
leveldelimiter = "|"
objectsDescrīptiondelimiter = "|"webLevels = "Browser|Page|Frame"
webLevelsDesc = "micclass:=Browser|micclass:=Page|micclass:=Frame|"
objects = "Link|WebButton|WebList|WebEdit"
objectsDescrīption = "micclass:=Link|micclass:=WebButton|micclass:=WebList|micclass:=WebEdit"' Generates a generic descrīption based up on the "level" viarable
' levelstr - will be one of the values that is in the level array
' returns - string representative of the object hierarchy
Public Function GenerateDescrīption (levelstr)
l = IndexOf(level, levelstr)
If l >=0 Then
fdesc = level(0) & "(" & Quote(desc(0)) & ")."
If l >= 1 Then
fdesc = fdesc + level(1) & "(" & Quote(desc(1)) & ")."
If 2 >= l Then
If thirdlevel <> "" Then
fdesc = fdesc + level(2) & "(" & Quote(desc(2)) & "," & Quote("name:=" & thirdlevel) & ")."
End If
End If
End If
End If
GenerateDescrīption = fdesc
End Function' Generates an object descrīption based upon the object, and objectDescrīption arrays
' obj - name of the object in the object array
' prop - additional property to help uniquely identify the object
' returns - a string representative of the object descrīption
Public Function GenerateObjectDescrīption (obj, prop)
i = IndexOf(object, obj)
ndesc = ""
If i <> -1 Then
ndesc = obj & "(" & Quote(objectDescrīption(i)) & "," & Quote(prop) & ")."
End If
GenerateobjectDescrīption = ndesc
End Function' given an array, returns the index of the value to search for
' ary - an array
' str - value to search for in an array
' returns - index in array
Public Function IndexOf (ary, str)
val = -1
For i = 0 to UBound(ary)
If ary(i) = str Then
val = i
End If
Next
IndexOf = val
End Function' configures framework to work within the context of a specific frame
' val - the Name of the frame to work within -- use Object Spy if you don't
' already know the frame name
Public Function WorkInFrame (val)
Report micPass, "Enter Frame", "Entered scope of frame " & Quote(val)
thirdlevel = val
End Function' configures the framework to work outside the context of a specific frame
Public Function StopWorkingInFrame
Report micPass, "Exit Frame", "Exited scope of frame " & Quote(thirdlevel)
thirdlevel = ""
End Function' generates a string with embedded/surrounding quotes
Public Function Quote (txt)
Quote = chr(34) & txt & chr(34)
End Function' navigate to a site if the browser is already opened, otherwise run initialization
Public Function BrowseTo (url)
thirdlevel = ""
Report micPass, "Navigate to URL", "Navigating to URL: " & Quote(url)
If initialized Then
Execute GenerateDescrīption("Browser") & "Navigate " & Quote(url)
Else
Launch "website", url
End If
Reporter.Filter = rfDisableAll
End Function' waits for the web page to finish loading
Public Function AutoSync
Execute GenerateDescrīption("Browser") & "Sync"
End Function' close all opened browsers
Public Function CloseBrowsers
If Browser("micclass:=Browser").Exist (0) Then
Browser("micclass:=Browser").Close
End If
While Browser("micclass:=Browser", "index:=1").Exist (0)
Browser("index:=1").Close
Wend
If Browser("micclass:=Browser").Exist (0) Then
Browser("micclass:=Browser").Close
End If
End Function' prepares the framework for usage, and configures all internal framework
' variables and structures
' apptype - used to launch different types of applications based
' upon different technologies -- currently there is only web
' val - string that represents what to launch
' returns - always returns true
Public Function Launch (apptype, val)
If "website" = apptype Then
thirdlevel = ""
Report micPass, "Initialize", "Initializing Framework"
level = split(webLevels, leveldelimiter, -1, 1)
desc = split(webLevelsDesc, leveldescdelimiter, -1, 1)
object = split(objects, objectdelimiter, -1, 1)
objectDescrīption = split(objectsDescrīption, objectsDescrīptiondelimiter, -1, 1)
CloseBrowsers
Set IE = CreateObject("InternetExplorer.Application")
IE.visible = true
IE.Navigate val
While IE.Busy
wait 1
Wend
End If
initialized = true
Launch = true
End Function' Verify the Existence of an object
' objtype - values should be limited to values in the object array
' text - multi-purpose argument that indicates what to verify
' - for a link, or button, it's the text of the control
' - for a list, it's the name of the control
' - for a frame, it's the name of the frame
Public Function Verify (objtype, text)
rval = false
localDesc = ""
estr = ""
If thirdlevel <> "" Then
localDesc = GenerateDescrīption(level(2))
Else
localDesc = GenerateDescrīption(level(1))
End IfAutoSync()
Select Case objtype
Case "Page"
Execute "rval = " & GenerateDescrīption(level(1)) & "Exist (0)"
If rval Then
Execute "title = " & GenerateDescrīption(level(1)) & "GetROProperty(" & Quote("title") & ")"
If title = text Then
rval = true
Else
rval = false
End If
End If
Case "CurrentFrame"
If thirdlevel <> "" Then
estr = "rval = " & localDesc
End If
Case "Link"
estr = "rval = " & localDesc & GenerateObjectDescrīption("Link", "innertext:=" & text)
Case "WebButton"
estr = "rval = " & localDesc & GenerateObjectDescrīption("WebButton", "value:=" & text)
Case "WebList"
estr = "rval = " & localDesc & GenerateObjectDescrīption("WebList", "name:=" & text)
Case "WebEdit"
estr = "rval = " & localDesc & GenerateObjectDescrīption("WebEdit", "name:=" & text)
End SelectIf estr <> "" Then
Execute estr + "Exist (0)"
End IfIf rval Then
Report micPass, objtype & " Verification", "The " & objtype & " " & Quote(text) & " was verified to exist"
Else
Report micFail, objtype & " Verification", "The " & objtype & " " & Quote(text) & " was not found"
End IfIf "True" = rval Then
rval = True
Else
rval = False
End IfVerify = rval
End Function' Activates an object based upon its object type
' objtype - the type of object should be limited to values in the object array
' text - identifying text for the control - for a link, it's the text of the link
Public Function Activate (objtype, text)
localDesc = ""
If thirdlevel <> "" Then
localDesc = GenerateDescrīption(level(2))
Else
localDesc = GenerateDescrīption(level(1))
End IfAutoSync()
Select Case objtype
Case "Link"
Execute localDesc & GenerateObjectDescrīption("Link","innertext:=" & text) & "Click"
Report micPass, "Link Activation", "The Link " & Quote(text) & " was clicked."
Case "WebButton"
Execute localDesc & GenerateObjectDescrīption("WebButton", "value:=" & text) & "Click"
Report micPass, "WebButton Activation", "The WebButton " & Quote(text) & " was clicked."
End Select
End Function' Selects a specific value from a listbox, or combobox
' objname - name of the control -- use Object Spy if you don't know the name property
' text - the item in the combobox to select
Public Function SelectFromList (objname, text)
localDesc = ""
rv = ""
rval = false
If thirdlevel <> "" Then
localDesc = GenerateDescrīption(level(2))
Else
localDesc = GenerateDescrīption(level(1))
End IfAutoSync()
localDesc = localdesc & GenerateObjectDescrīption("WebList", "name:=" & objname)
Execute "cnt = " & localDesc & "GetROProperty(" & Quote("items count") & ")"
For i = 1 to cnt
Execute "rv = " & localDesc & "GetItem (" & i & ")"
If rv = text Then
rval = true
End If
NextIf rval Then
Execute localDesc & "Select " & Quote(text)
End If
If rval Then
Report micPass, "WebList Selection", "The WebList item " & Quote(text) & " was selected."
Else
Report micFail, "WebList Selection", "The WebList item " & Quote(text) & " was NOT found."
End IfSelectFromList = rval
End Function' Enters text into an edit field
' objname - name of the control -- use Object Spy if you don't know what it is
' text - the text to enter into the control
Public Function EnterTextIn (objname, text)
localDesc = ""
rval = true
If thirdlevel <> "" Then
localDesc = GenerateDescrīption(level(2))
Else
localDesc = GenerateDescrīption(level(1))
End IfAutoSync()
localDesc = localdesc & GenerateObjectDescrīption("WebEdit", "name:=" & objname)
Execute localDesc & "Set (" & Quote(text) & ")"
Report micPass, "Enter Text", "Text: " & Quote(text) & " was entered into " & Quote(objname)
EnterTextIn = rval
End Function' Obtains text from a control
' objtype - is the type of control the get the text from
' objname - is the name of the control -- use Object Spy if you don't know the name
' returns - the text of the control
Public Function GetTextFrom (objtype, objname)
text = ""
localDesc = ""
If thirdlevel <> "" Then
localDesc = GenerateDescrīption(level(2))
Else
localDesc = GenerateDescrīption(level(1))
End IfAutoSync()
Select Case objtype
Case "WebEdit"
Execute "text = " & localDesc & GenerateObjectDescrīption("WebEdit", "name:=" & objname) & "GetROProperty (" & Quote("value") & ")"
Case "WebList"
Execute "text = " & localDesc & GenerateObjectDescrīption("WebList", "name:=" & objname) & "GetROProperty (" & Quote("value") & ")"
End Select
Report micPass, "Capture Text", "Text: " & Quote(text) & " was captured from the control " & Quote(objname)
GetTextFrom = text
End Function' Wrapper for the Reporter.Report Event method
' - could be used to create custom reports more easily
' See Reporter.ReportEvent documentation for usage
Public Function Report (status, objtype, text)
Reporter.Filter = rtEnableAll
Reporter.ReportEvent status, objtype, text
Reporter.Filter = rfDisableAll
End Function基于此框架的qtp网上售票的系统的框架代码例子想要的人请加: pcl2004_19@hotmail.com
-
解决RationalRobot获取htmlTableCell数据的技术
2008-09-09 12:53:45
昨天有位朋友通过msn加我,想解决RationalRobot如何获得HtmlTable中的Cell的数据问题,他在网上通过寻找以前的(2004-2005)年的帖子找到我,希望能解决这个问题,在论坛上大家给出的答案都是要扩展robot开发dll或者是解析网页,但是具体如何做没有给出代码,其实这个问题这么做就复杂了,robot我觉得是一个很不错的工具,2004年我写了很多关于robot心得的文章,记得第一篇在网络上发表的文章robot识别datagrid控件对象就是为了帮一个广州朋友解决问题得出的心得.
问题是这样如何用rational robot获得htmltablecell的内容,这个robot本身确实做的不是很好,获得cell的数据需要根据索引来进行定位,我们先看如何获得htmltable的内容,我写了一个html页面文件代码如下:<HTML><HEAD><TITLE>test</TITLE></HEAD><BODY><TABLE id=testaa><TR><TD>Jones</TD></TR><TR><TD>Smith</TD></TR><TR><TD>Harry</TD></TR></TABLE></BODY></HTML>保存为html文件后用ie打开,然后编写robot自动化测试代码如下:Sub MainDim Value as variantWindow SetContext, "Caption=test - Microsoft Internet Explorer",""Browser NewPage,"",""SQAGetProperty "Type=HTMLTable;HTMLId= testaa","InnerText",valueSQAConsoleWrite "Value : "& valueEnd Sub获得数据为 jones Smith Harry如何用robot获得其中cell中的单独数据呢,其实robot把html中的cell识别成htmltablecell对象,而获得其中的内容需要根据index来定位,我这个测试页面只有一个table,那么它的第一个cell的index就是1,第二个cell的index就是2所以修改自动化测试脚本代码看如何获得第一个 第二个 第三个cell的数据,修改代码如下:Sub MainDim Result As IntegerDim iCellCount as IntegerDim Value as variantfor iCellCount=1 to 3Window SetContext, "Caption=test - Microsoft Internet Explorer",""Browser NewPage,"",""SQAGetProperty "Type=HTMLTableCell;index="+ iCellCount,"outerText",valueSQAConsoleWrite Cstr(i iCellCount)+"Value : "& valuenext iCellCountEnd Sub如果网页中有3个table,每个table横三行竖三列table 1---------------------------1 | 2 | 3---------------------------4 | 5 | 6---------------------------table 2---------------------------1 | 2 | 3---------------------------4 | 5 | 6---------------------------table 3---------------------------1 | 2 | 3---------------------------4 | 5 | 6---------------------------如果要获得第二个table中的第一个cell中的内容,那么index就是7以上为利用robot自身解决获得cell表格内容的方法,当然还有其他网友提供的解析页面文件的方法,利用dom技术。 -
Qtp 9.5 温柔破解试验 一步破解成功
2008-09-08 11:10:39
周末在家休息,打开qtp想调试一些写的代码,但是发现qtp9.5过期,本人很懒实在厌烦重新安装系统,到网上看资料发现都是先破解qtp8.2或者9.2然后卸载8.2或者9.2版本然后在安装9.5版本,那有没有办法直接破解呢?思考:8.2到9.2的破解方式是把mgn-mqt82.exe文件拷贝到C:\Program Files\Mercury Interactive和qtp同级目录,然后执行该破解文件,但是到了9.5版本安装路径变成C:\Program Files\HP了,我想肯定是是否是文件夹路径改变后导致破解不成功,所以先在系统中创建了C:\Program Files\Mercury Interactive文件夹,然后把mgn-mqt82.exe拷贝到文件夹下,执行该破解文件,意想不到的事情发生,系统错误mgn-mqt82报错,不管三七二十执行QTP 9.5,发现破解没有完成。既然不行换个思路,以前我用mgn-mqt82.exe破解8.2的时候,经常发现破解不成功的问题,就到C:\Program Files\Common Files\Mercury Interactive\License Manager文件夹下去修改LSERVRC文件,删除里面的生成代码,再次执行mgn-mqt82.exe,反复几次就可以达到破解8.2的目的。既然这样我就到C:\Program Files\Common Files\Mercury Interactive\文件夹下,发现没License Manager文件夹,也没有生成LSERVRC文件,那我就手工创建该文件夹,做好该工作之后,然后再C:\Program Files\Mercury Interactive文件夹,执行mgn-mqt82.exe,执行成功,没有报错,既然没有报错,那就继续我把C:\Program Files\Common Files\Mercury Interactive\License Manager\LSERVRC中产生的生成的字符串拷贝出来,然后拷贝到qtp的license向导中,破解成功。呵呵,功夫不负有心人,试验成功破解步骤:1.安装qtp2.拷贝mgn-mqt82.exe到C:\Program Files\Mercury Interactive(创建)文件夹下3.创建C:\Program Files\Common Files\Mercury Interactive\License Manager文件夹4.执行mgn-mqt82.exe5.打开qtp9.5,然后安装license,copy文件C:\Program Files\Common Files\Mercury Interactive\License Manager\LSERVRC中#之前的字符串如:3QVWCPPOS5NGGFM6KPX64EQFSH6INFRJIVMC5WZ4XIIFIXX86UCPIP4M686DZKV9NANA9BUP# "QuickTestPro" version "6.0", no expiration date, exclusiveJZ7F79F6YQQFVUWNG2V7AW22K537DOELQYNX6VSCNCZ9J8M2QW9OXO5DSEQKUZA46X5BO# "FT-Unified" version "1.0", no expiration date, exclusive就拷贝#号前的3QVWCPPOS5NGGFM6KPX64EQFSH6INFRJIVMC5WZ4XIIFIXX86UCPIP4M686DZKV9NANA9BUP 然后paste到license向导中的license输入的地方,就可以了恭喜成功了 -
一个工作任务的解决方法
2008-09-07 23:21:58
有个网友接到工作任务产生测试数据,具体任务如下:
“我们老大给了个这个的任务给我:
质量:重复率<0.1%,相似率<10%
数据根据该字段的类型和长度生成, 涵盖各种情况,包括边界值,异常值....字符串要包含所有字符集,规定的所有长度,时间跨度取前后3年以上,枚举字段要随机取完所有值....
特殊要求:
固定在一个字段里放测试标识,如在作者字段的前两个字符为"测试",便于识别和删除...”
他的解决问题思路是通过lr把现有数据库中的数据导入到参数文件中,然后发送给服务器(不知道他为什么从数据库中读取数据,然后再发送),导入到file中遇到问题是 lr报错 “check file format”。经过交流之后我们把他的问题简单化,其实就是一个目标产生数据,但是中间遇到的是两个问题,一个是数据来源问题 一个是数据如何发送问题1.数据来源,如果是要网页文件信息,从他的方法来看,是利用lr读取数据库,那么我们来看看结合lr如何做呢?首先是利用vc写一个dll,从服务器上读取字段内容,然后在lr中读取该代码,2007年的时候当时帮大连的一个朋友写过一个dll代码,就是读取oracle数据库字段内容的,然后再lr中使用。根据以上思路写了个vc中伪代码(伪代码就是没有经过调试,或者没有实现完全的代码)如下:
_ConnectionPtr pConnection;
_RecordsetPtr pRecordSet;
ICfxDataAdoPtr pCfxDataAdo;pRecordSet.CreateInstance(__uuidof(Recordset));
pConnection.CreateInstance(__uuidof(Connection));// Read from database
pConnection->ConnectionString ="Provider=Microsoft.Jet.OLEDB.4.0;Persist Security Info=False;Data Source=";
pConnection->Open("","","",-1);
pRecordSet = pConnection->Execute("SELECT * FROM xxx",NULL,-1);
//获得pRecordSet字段数据
pRecordSet->Close();
pConnection->Close();
以上代码可以封装成函数放入dll后,在lr中调用dll,lr中的代码伪代码如下,
rc=lr_load_dll("xxx.dll");
if( rc != 0 ){
lr_error_message("lr_load_dll of advapi32.dll failed. Aborted for rc=%d",rc);
lr_abort();
}else{
strcpy(data,dll中调用返回的函数数据);
web_submit_form 发送出去要发送的数据包}
我们在换一个思路,如果就是为了得到html这样的数据,那么我们是否可以自己写代码得到网站上任何的Html代码作为自己的数据,用vbs写了段代码:
Set XML = CreateObject("Microsoft.XMLHTTP")
XML.Open "GET", "http://www.51testing.com", False
XML.Send
vResponse = XML.responseText
MsgBox vResponse
以上代码调试通过
2.如何发送数据包
方法可以是利用lr发送,可以是直接发送请求,我们看如何直接发送请求,代码如下:
Dim xmlhttp
Set xmlhttp = New MSXML2.ServerXMLHTTP
xmlhttp.Open "POST", "http://xxxxxxx", False
xmlhttp.setRequestHeader "Content-Type", "xxxxx"
xmlhttp.send DataToSend
Set xmlhttp = NothingDataToSend
=====================================================
<HTML><HEAD>
<title>demo</title>
</HEAD><BODY>
xxxxxx
</BODY>
</HTML>
思路如上,解决问题中具体用到的技术大家可以找相应资料深入研究下。
-
WinRunner 曾经的记忆
2008-09-01 17:47:49
昨天在做Qtp培训发给学员资料的时候无意中找到以前的资料,记得刚参加工作的时候自动化测试就知道Rational robot,感觉工具特别好,也激起了很大的热情和兴趣,对Robot做了很多研究,当时还写了很多关于robot的文章,也曾经憧憬着做中国第一个自动化测试项目,后来加入51testing由于工作需要接触了wr,qtp,silktest等功能自动化测试工具,随着时间的推移wr robot逐渐淡出大家的视线。其实每一个工具都有自己的特点在里面,好比一个人一样有自己的风格,在不同的项目用不同的工具,只有结合起来才能发挥最大的威力,下面是一些代码例子,算是做个纪念:
1.WinRunner如何加载Dll,例子代码如下:
rc1 = reload ( "win32api", 1, 1 );
if ( rc1 != 0 )
{ tl_step ( "Error Message", FAIL, "[reload(\"win32api\")] - FAILED!"); }
else
{ tl_step ( "Status Message", PASS, "[reload(\"win32api\")] - OK"); }public function phone_num_gen (out phone)
{
auto x, areacode, prefix, sufix;x = int ( rand(GetTickCount())*1000000000000 );
areacode = substr(x, 1 ,3);
prefix = substr(x, 4, 3);
sufix = substr(x, 7, 4);
phone = "(" & areacode & ") " & prefix & "-" & sufix;
return ( E_OK );
}for (i = 1; i < 10; i++)
{
phone_num_gen(num);
printf(num);
}rc1 = unload ( "win32api" );
if ( rc1 != 0 )
{ tl_step ( "Error Message", FAIL, "[unload(\"win32api\")] - FAILED!"); }
else
{ tl_step ( "Status Message", PASS, "[unload(\"win32api\")] - OK"); }2.在winrunner中如何统计代码行,提供一种解决思路
public function cloc ( in fileX, inout lines_code, inout lines_blank_or_comment, inout total_LOC )
{
auto line;# Initialize variables ...
lines_code = 0;file_open ( fileX, FO_MODE_READ );
while ( file_getline( fileX, line ) == 0 )
{
# Blank or Coment lines ...
if ( match( line, "[^#]" ) == 1 )
{
lines_blank_or_comment++;
}
else
{
lines_code++;
}
}total_LOC = lines_code + lines_blank_or_comment;
file_close ( fileX );
}
rc1 = cloc( "C:\\Junk1\\scrīpt", LOC, BlankOrComment, TLOC );printf ( "Total Number of lines of code: %s\r\n", LOC );
printf ( "Total Comment or blank lines in code: %s\r\n", BlankOrComment );
printf ( "Total lines in file: %s\r\n", TLOC );3.winrunner错误处理函数
function CreateErrorMessageArray ( inout ErrorMsgArray[] )
{
# Location of WinRunner scrīpt which defines the error message codes
auto ErrorMsgFile = getenv ( "M_ROOT" ) & "\\lib\\wr_gen\\scrīpt";
auto rc = E_OK; # Return code for this function
auto character; # Store a character
auto line; # Store a line from file
auto tmpline; # Store a line with no tab or spaces
auto counter; # Used to strip tab and spaces out of the line
static tmpArray[]; # Store split ( ) results# Delete everything in the ErrorMsgArray
for ( counter in ErrorMsgArray )
delete ErrorMsgArray[counter];rc = file_open ( ErrorMsgFile,FO_MODE_READ );
if ( rc != E_OK )
return ( rc );#Parse the file looking for "public const E_"
while ( file_getline ( ErrorMsgFile,line ) == 0 )
{
if ( index(line, "public const E_") > 0 )
{
# Get rid of spaces and tab characters
tmpline = "";
for ( counter = 1; counter <= length(line); counter++ )
{
character = substr ( line, counter, 1 );
if ( (character != " ") && (character != "\t") )
tmpline = tmpline & character;
}
split ( tmpline, tmpArray, "=" );# The substr ( tmpArray[2],1, length ( tmpArray[2] ) -1 ) gets rid of the trailing semicolon ( ; )
# The substr ( tmpArray[1], 12 ) is the error code minus the public const
if ( ErrorMsgArray[substr(tmpArray[2], 1, length(tmpArray[2] - 1 ) ) ] )
{
ErrorMsgArray[substr(tmpArray[2], 1, length(tmpArray[2] - 1) ) ] =
ErrorMsgArray[substr(tmpArray[2], 1, length(tmpArray[2] - 1) ) ]&", "&
substr(tmpArray[1], 12 );
}
else
{
ErrorMsgArray[substr(tmpArray[2], 1, length(tmpArray[2]) - 1) ] =
substr(tmpArray[1], 12);
}
}
}
file_close ( ErrorMsgFile );
return ( E_OK );
}function GetErrorMessage ( inout ErrorMsgArray[], in ErrorCode )
{
return ( ErrorMsgArray[ErrorCode] );
} -
AOM对象创建不成功
2008-08-31 00:32:01
今天用VB写创建AOM对象,代码执行如下的时候就报错:
Set Qtp_App=CreateObject("QuickTest.Application")'报错 ActiveX控件不能创建
单独打开qtp没有问题,就是以上代码会出现问题,最后找了下论坛中的是否有人遇到同样的问题,也没有最后给解决办法,最后自动动手丰衣足食,分析结果是直接注册QTObjectModel.dll也不能解决以上问题,分析应该是有相关dll对象耦合在一起了,所以重新注册相应的dll就可以解决了,大胆推断后,然后就是小心求证,最后问题解决
解决方法尝试如下:重新执行C:\Program Files\Mercury Interactive\QuickTest Professional\bin\QuickTestProfessional.bat,也就是重新注册qtp dll,执行完毕后执行代码,没有报错
-
自动化测试框架中如何远程启动进程的技术
2008-08-22 22:46:07
在自动化测试框架中经常设计分布式执行用例,调用不同计算机机上的qtp来执行,如何远程调用qtp有以下几个方法:
1.利用qc启动相应机器上qtp执行自动化测试用例
打开QC 打开test lab--> in the execution flow-->设置
2.利用wmi对象远程启动远程进程
脚本代码例子:
strComputer = "."
strCommand = "QPro.exe"
Const INTERVAL = "n"
Const MINUTES = 1Set ōbjWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set ōbjScheduledJob = objWMIService.Get("Win32_ScheduledJob")
Set ōbjSWbemDateTime = CreateObject("Wbemscrīpting.SWbemDateTime")objSWbemDateTime.SetVarDate(DateAdd(INTERVAL, MINUTES, Now()))
errReturn = objScheduledJob.Create(strCommand, objSWbemDateTime.Value, False, 0, 0, True, intJobID)
If errReturn = 0 Then
Wscrīpt.Echo "notepad.exe was started with a process ID: " & intJobID
Else
Wscrīpt.Echo "notepad.exe could not be started due to error: "&errReturn
End If
3.利用lr controller远程启动qtp自动化测试脚本
打开controller,然后加载qtp脚本,设置load generator
-
老婆测试工具培训记 - QTP 函数实现 - 1
2008-08-20 13:13:18
Write a program to delete Line in file?
思路:封装scrīpting.filesystemobject对象,利用readline方法
Function DeleteLine(strFile, strKey, LineNumber, CheckCase)
'Use strFile = "c:\file.txt" (Full path to text file)
'Use strKey = "John Doe" (Lines containing this text string to be deleted)
'Use strKey = "" (To not use keyword search)
'Use LineNumber = "1" (Enter specific line number to delete)
'Use LineNumber = "0" (To ignore line numbers)
'Use CheckCase = "1" (For case sensitive search )
'Use CheckCase = "0" (To ignore upper/lower case characters)Const ForReading=1:Const ForWriting=2
Dim objFSO,objFile,Count,strLine,strLineCase,strNewFile
Set ōbjFSO=CreateObject("scrīpting.FileSystemObject")
Set ōbjFile=objFSO.OpenTextFile(strFile,ForReading)
Do Until objFile.AtEndOfStream
strLine=objFile.Readline
If CheckCase=0 then strLineCase=ucase(strLine):strKey=ucase(strKey)
If LineNumber=objFile.Line-1 or LineNumber=0 then
If instr(strLine,strKey) or instr(strLineCase,strkey) or strKey="" then
strNewFile=strNewFile
Else
strNewFile=strNewFile&strLine&vbcrlf
End If
Else
strNewFile=strNewFile&strLine&vbcrlf
End If
Loop
objFile.Close
Set ōbjFSO=CreateObject("scrīpting.FileSystemObject")
Set ōbjFile=objFSO.OpenTextFile(strFile,ForWriting)
objFile.Write strNewFile
objFile.CloseEnd Function
-
vbs创建对象的知识点
2008-08-20 11:18:58
一般vb中创建对象有两种形式:
(1)
dim a as object_x
这样只是定义对象A的一个标识,并没有开辟内存空间来创建这个对象.
dim a as new object_x
这样定义就开辟了内存空间,即在定义对象时就构建了这个对象.其它等于:
(2)
dim a as object_x
set a= new object_x或者
dim a as object_x
set a= CreateObject("objectname”)
CreateObject支持更多的对象定义.比如用VB6写的DLL不是标准格式的DLL,一般用CreateObject来完成对象的创建,而且在VBscrīpt(qtp)等VB的子集中也只支持用CreateObject.
标题搜索
我的存档
数据统计
- 访问量: 200563
- 日志数: 169
- 图片数: 10
- 文件数: 4
- 建立时间: 2006-11-30
- 更新时间: 2012-08-18