可能是一个粉碎的故事 - QTP似乎无缘无故地浪费了我们的工作时间:
考虑这个脚本,具有一个全局行的数据表,其中26列名为“A”到“Z”,填充任何值:
Print "Started"
Services.StartTransaction "Simpletest"
Set G=DataTable.GetSheet ("Global")
For J=1 to 26
For I=1 to 100
Set P=G.GetParameter (Chr (J+64))
If P.Value = "Hi" Then
End If
Next
Next
Services.EndTransaction "Simpletest"
Print "Ended"
在我的冲击波下,在QTP 10下执行此操作需要 15.1秒。 (当然,动画运行是关闭的。)
现在我使用来自QTP bin文件夹的mmdrv.exe执行此操作,为其提供参数“-usr''”,其中包含全名,包括QTP测试.usr文件的路径。
0.07秒。
喂?这是性能提升215倍,但功能相同。怎么来的?
我正在这里挖掘,因为我们在QTP数据表中做了一些奇特的东西,并且在QTP下面临严重的性能问题。我相信已经找到了DataTable.GetSheet和DTSheet.GetParameter属性/方法的原因。
现在我发现用于在LoadRunner场景中执行QTP测试的MMDRV没有性能损失,我想知道以下内容:
感谢任何回复,无论他们多么令人不安。
* UPDATE * 执行QTP隐身需要1.54秒。仅仅通过隐藏其中一个答案中概述的QTP,这是一个10倍的改进。叹息。
答案 0 :(得分:3)
我们遇到与QTP相同的性能问题。经过调查,我们将问题归咎于两个方面。
我们发现QTP在隐藏时运行速度提高了5-6倍
我们制作了一个小脚本,用于在开发/调试时切换QTP可见性(因为您始终可以强制QTP隐藏在远程代理设置中) '此脚本用于显示/隐藏QTP窗口 '隐藏
时,QTP的运行速度要快得多Dim qtApp
Set qtApp = CreateObject("QuickTest.Application")
qtApp.Launch ' Start QuickTest
If qtApp.Visible = False Then ' Make the QuickTest application invisible/visible
qtApp.Visible = True
Else
qtApp.Visible = False
End If
您是否愿意分享缓存DataTable的想法,因为我们正在考虑开发相同的机制,并且会看到这样的示例。
亲切的问候, Achraf
答案 1 :(得分:1)
使用完整的GUI开发环境运行会提取性能损失。您可以在LoadRunner中观察VUGEN中的这种差异,在MDRV上运行可以在使用复杂代码时提供显着的性能提升。您还会看到人们经常抱怨VUGEN“比实际应用程序慢”。
那么,如果这让我感到惊讶?并不是的。有趣的是,我没有在QTP安装上考虑过MDRV的存在,但这是因为鉴于QTP传统与QICKIP技术共同传承了QUICKTEST for Web。郁金香基础是功能方面的QuicktestPro和负载方面的一些新的Web HTTP技术的基础。
答案 2 :(得分:0)
200倍的性能损失来自DataTable操作。 QTP下的其他操作仍然比MMDRV慢,但没有这种恐怖因素。
我通过在自定义结构(实际上是对象集合)中“缓存”所有DataTable调用来解决这个问题。从查询大量的Sheets和参数属性开始构建一个需要5秒的时间。处理我的结构而不是调用DTSheet和DTParameter属性更快,确实足够快。
我怀疑在QTP下,所有数据表访问都是通过他们(HP)从第三方获得许可的自定义Excel控件完成的,而在MMDRV下,他们使用更紧密集成的代码,从而减少每次调用的开销。
杜克会说:“哈哈哈,真是一团糟。”**更新**根据要求,这里是“缓存”DataTable调用的概述。这是相当多的代码,所以要做好准备......
这是一团糟(对不起,现在没时间翻译德语内联评论)(对不起格式化,我显然不能做太多关于它,也许你想切并将其粘贴到QTP的编辑器中:
这一切都从一个通用的Container类开始,我可以在其中存储(并通过索引访问)N个对象引用:
' Container-Klasse, die N Objektreferenzen aufnehmen kann.
Class TContainer
Public iItems() ' Array, das die Objektreferenzen aufnimmt
Private iItemsHaveUBound ' True, wenn das Array mindestens ein Element hat
' Konstruktor
Private Sub Class_Initialize
iItemsHaveUBound=false ' Kein Element in iItems vorhanden
End Sub
' Anzahl der enthaltenen Objektreferenzen?
Public Property Get Count
If iItemsHaveUBound Then ' Nur wenn > 0 Elemente enthalten sind (also mindestens einmal ReDim Preserve für iItems gelaufen ist),
' können wir UBound aufrufen. Macht keinen Sinn, ist aber so, ein UBound (E) liefert für ein frisches Private E() einen Subscript error...
Count=UBound (iItems)+1 ' Grösstmöglicher Index+1, da Zählung bei 0 beginnt, und 0-basierender Index+1 = Abzahl
else
Count=0 ' Jungfräuliches iItems(), direkt 0 liefern
End If
End Property
' Getter für indizierte Referenz (Index ist 1-basierend!)
Public Default Property Get Item (ByVal Index)
Set Item=iItems(Index-1)
End Property
' Setter für indizierte Zuweisung (Index ist 1-basierend!)
Public Property Set Item (ByVal Index, ByVal Val)
' MBLogDebugComment "SetItem","Index=" & Index
If Count <= (Index-1) Then
ReDim Preserve iItems (Index-1)
iItemsHaveUBound=true
End If
Set iItems(Index-1)=Val
End Property
Public Property Get AddItem (ByVal Val)
Item(Count+1)=Val
Set AddItem=Val
End Property
End Class
我使用特殊的列名来赋予列特殊的含义。 DetectColumnKind
根据名称检测到该含义并吐出“枚举”。就是这样(我不会在这里显示DetectColumnKind
):
' Von MBCollectAllTestData unterstützte Spaltenarten in Datentabellen
Private Const ckData = 0
Private Const ckReference = 1
Private Const ckComment = 2
现在出现了真实的东西:
包含N张表示的容器。我收集每个工作表的属性并将它们存储在该容器中。
' Klassen, die die Tabellenbkattstrukturen repräsentieren. Hintergrund ist ein ganz abgefahrener: Der Kollektor muss sich die Spaltenstrukturen aller
' intensiv anschauen, um seinen Job zu machen (Verweise verstehen, Klassencode generieren, Zuweisungscode generieren). Dafür greift er wieder und wieder
' auf DTSheet- und DTParameter-Instanzen zu. Das ist performancemässig aber sehr, sehr teuer (warum auch immer!). Um erträgliche Laufzeiten zu erhalten,
' enumeriert der Kollektor im helper BuildTestDataDescr die Sheets und deren Spalten und merkt sich in eigenen Datenstrukturen alles, was er später
' über die Spalten so wissen muss. Anschliessend macht der Kollektor seinen Job anhand dieser Repräsentationen, nicht mehr anhand der
' DataTable-Eigenschaften. Das ergibt funktional das gleiche, macht aber performancemässig einen Riesen-Unterschied.
' Klasse, die eine Tabellenblattspalte repräsentiert
Class TestDataColumnDescr
Public Column ' as DTParameter; Referenz auf die Original-Spalte
Public ColumnName ' as String; der Name der Spalte
Public ColumnKind ' fertig ausgerechnete Spaltenart in Sachen Kollektor
Public ColumnRefdSheet ' as DTSheet; bei Verweisspalte: das verwiesene Sheet
Public ColumnRefdSheetName ' as String; bei Verweisspalte: der Name des verwiesenen Sheets
Public ColumnRefdSheetDescr ' as TestDataSheetDescr; bei Verweisspalte: Referenz auf den TestDataSheetDescr-Descriptor des verwiesenen Sheets
Public ColumnRefdSheetIDColumn ' as DTParameter; bei Verweisspalte: Referenz auf die Original-ID-Spalte des verwiesenen Sheets
Public ColumnRefdSheetPosColumn ' as DTParameter; bei Verweisspalte: Referenz auf die Original-Pos-Spalte des verwiesenen Sheets (Nothing, wenn 1:1)
' Konstruktor
Private Sub Class_Initialize
End Sub
End Class
' Klasse, die ein Tabellenblatt repräsentiert
Class TestDataSheetDescr
Public Sheet ' as DTSheet; Referenz auf das Original-Sheet
Public SheetName ' as String; Name des Sheets
Public SheetRowCount ' as Integer; Anzahl Zeilen im Original-Sheet
Public SheetColumnCount ' as Integer; Anzahl Spalten im Original-Sheet
Public SheetColumn ' as TContainer; Container aller Spaltendescriptoren (TestDataColumnDescr)
' Konstruktor
Private Sub Class_Initialize
Set SheetColumn=New TContainer
End Sub
End Class
以下是构建容器内容的内容:
' Container aller Tabellenblattrepräsentationen
Dim TestDataDescr ' wird in BuildTestDataDescr instanziiert
' Aufbau von Tabellenblattrepräsentationen, damit Kollektor nicht dauernd DataSheet-Funktionen aufrufen muss. TestDataDescr instanziieren, aufbauen.
Public Sub BuildTestDataDescr
' Build N Sheet Descriptors
Dim SheetIndex
Dim ColumnIndex
Dim S
Dim S1
Dim S2
Dim Index
dim SheetDescr, ColumnDescr
' Zunächst die N Sheet-Descriptoren mit ihren Spaltendescriptoren anlegen
'Services.StartTransaction "BuildTestDataDescr"
Set TestDataDescr = New TContainer
For SheetIndex=1 to DataTable.GetSheetCount
set SheetDescr = New TestDataSheetDescr
With TestDataDescr.AddItem (SheetDescr)
Set .Sheet=DataTable.GetSheet (SheetIndex)
.SheetName=.Sheet.Name
.SheetRowCount=.Sheet.GetRowCount
.SheetColumnCount=.Sheet.GetParameterCount
Set S=.Sheet ' .Sheet ist im folgenden With nicht erreichbar, keine Ahnung, warum (nested Withes funken nicht anscheinend)
For ColumnIndex=1 to .SheetColumnCount
set ColumnDescr = New TestDataColumnDescr
With .SheetColumn.AddItem (ColumnDescr)
Set .Column=S.GetParameter (ColumnIndex)
.ColumnName=.Column.Name
End With
Next
End With
Next
' Jetzt etwaige Verweisspalten mit zugehöriger Info anreichern (wir machen das in einem zweiten Schritt, damit wir garantiert zu allen
' verwiesenen Blättern einen Descriptor finden -- ohne Rekursion und komplizierten Abbruchbedingungen bei zyklischen Verweisen...); ferner
' müssen die Namen von auswahltabellenbasierten Spalten angepasst werden:
For SheetIndex=1 to TestDataDescr.Count
With TestDataDescr(SheetIndex)
For ColumnIndex=1 to .SheetColumnCount
Set S=.Sheet ' .Sheet ist im folgenden With nicht erreichbar, keine Ahnung, warum (nested Withes funken nicht anscheinend)
With .SheetColumn(ColumnIndex)
.ColumnKind=DetectColumnKind (.ColumnName,S1,S2)
Select Case .ColumnKind
Case ckComment
' Nuttin', weil: Ist ja eine Gruppier- oder Kommentarspalte -- ignorieren
Case ckData
' Datenspalte -- hier nichts weiter zu tun
.ColumnName=S1 ' ausser: Namen bereinigen (hat nur Folgen für auswahllistenbasierte Spalten)
Case ckReference
' Verweisspalte -- merken, was später immer wieder an info benötigt wird
.ColumnName=S1
Set .ColumnRefdSheet=MBFindSheet (S2)
If .ColumnRefdSheet is Nothing Then
MBErrorAbort "MBUtil.MBCollectAllTestData", _
"Fehler beim Definieren von Klassen;" & vbNewline _
& "Spalte '" & .ColumnName & "' definiert einen Verweis auf Datentabellenblatt '" & S2 & "', welches nicht existiert." & vbNewline _
& "Bitte überprüfen Sie die entsprechenden Datentabellenblätter"
End If
.ColumnRefdSheetName=.ColumnRefdSheet.Name
Set .ColumnRefdSheetIDColumn=.ColumnRefdSheet.GetParameter ("ID")
Set .ColumnRefdSheetPosColumn=MBFindColumn (.ColumnRefdSheet,"Pos")
For Index=1 to TestDataDescr.Count
If TestDataDescr(Index).SheetName = .ColumnRefdSheetName then
Exit For
End If
Next
Set .ColumnRefdSheetDescr=TestDataDescr(Index)
End Select
End With
Next
End With
Next
'Services.EndTransaction "BuildTestDataDescr"
End Sub
根据容器中的信息,我使用TestDataDescr中的结构迭代列等。
就像在这个接收一个容器元素(SourceSheetDescr)的示例中一样,查看每一列,并根据列类型执行“某事”,这是构建到容器元素中的信息的一部分:
For ParamIndex=1 to SourceSheetDescr.SheetColumnCount
With SourceSheetDescr.SheetColumn(ParamIndex)
Select Case .ColumnKind
Case ckComment
' Do something
Case ckData
' Do something else Case ckReference
' Do other stuff End Select
End With
Next
这样我就可以避免查询DTSheet.GetParameter(),并且可以调用任何其他DataTable方法。 当然,这只是使用容器所拥有的信息的一个例子。
在我们的典型用例中,与调用DataTable方法相比,这个性能增加了三倍,即使我们已经避免了所有冗余调用,并且在传统数据表访问代码中进行了所有其他明显的优化
答案 3 :(得分:0)
QTP中的数据表操作非常慢。因此,在Datatable中使用Excel公式可以加快操作速度。