QTP数据表操作*极其缓慢(在MMDRV批处理执行器下更好)?

时间:2011-07-28 12:57:37

标签: performance datatable qtp

可能是一个粉碎的故事 - 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没有性能损失,我想知道以下内容:

  • 是否有1:1替代方法来访问xls文件?
  • Ex-Mercury / HP的某些人不应该注意到QTP下的数据表访问非常效率不高,正如MMDRV.EXE演示的那样,并对此做些什么?
  • 据我所知,所有其他QTP功能在MMDRV和QTP下具有相当的速度。任何人都可以承认吗? *有其他人知道吗?

感谢任何回复,无论他们多么令人不安。

* UPDATE * 执行QTP隐身需要1.54秒。仅仅通过隐藏其中一个答案中概述的QTP,这是一个10倍的改进。叹息。

4 个答案:

答案 0 :(得分:3)

我们遇到与QTP相同的性能问题。经过调查,我们将问题归咎于两个方面。

  • 数据表(可怕的表现)
  • 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公式可以加快操作速度。