Операции с данными QTP * чрезвычайно * медленные (намного лучше при работе с пакетным исполнителем MMDRV)? - PullRequest
3 голосов
/ 28 июля 2011

Возможно, это потрясающая история - 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 секунды на моем бластере.(Анимированный запуск, конечно, выключен.)

Теперь я выполняю это, используя mmdrv.exe из папки bin QTP, давая ему параметр "-usr '" с полным именем, включая путь к тесту QTPФайл .usr.

Это занимает 0,07 секунды .

Здравствуйте?Это увеличение производительности в 215 раз, но идентичная функциональность.Как дела?

Я копаюсь здесь, поскольку мы делаем некоторые экзотические вещи с таблицами данных QTP и сталкиваемся с серьезными проблемами с производительностью в QTP.Я полагаю, что выяснил причину свойств / методов DataTable.GetSheet и DTSheet.GetParameter.

Теперь, когда я вижу, что MMDRV, предназначенный для выполнения тестов QTP из сценариев LoadRunner, не имеет такого снижения производительности, мне интересно следующее:

  • Есть лиАльтернатива 1: 1 для доступа к файлам xls?
  • Не должен ли кто-нибудь в Ex-Mercury / HP заметить, что доступ к таблицам данных в QTP очень неэффективен, как демонстрирует MMDRV.EXE, и делаетчто-нибудь об этом?
  • Насколько я вижу, все остальные функции QTP имеют сопоставимую скорость в MMDRV и QTP.Кто-нибудь может это признать?* Кто-нибудь еще знает об этом?

Спасибо за любые ответы, независимо от того, насколько тревожными они могут быть.

* ОБНОВЛЕНИЕ * Выполнение с QTP невидимыми принимает1,54 секунды.Это 10-кратное улучшение, если скрыть QTP, как указано в одном из ответов.Вздох.

Ответы [ 4 ]

3 голосов
/ 25 августа 2011

У нас те же проблемы с производительностью QTP.После расследования мы загнали в угол проблемы в 2 областях.

  • Таблица данных (ужасная производительность)
  • 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 голос
/ 28 июля 2011

Работа с полной средой разработки GUI приводит к снижению производительности.Вы также можете наблюдать это различие в VUGEN в LoadRunner, поскольку работа в MDRV обеспечивает существенное повышение производительности при использовании сложного кода.Вы также увидите, что люди довольно часто жалуются, что VUGEN «медленнее, чем само приложение».

Итак, удивляет ли меня это?На самом деле, нет.Интересно то, что я не рассматривал существование MDRV при установке QTP, но с тех пор это делается с учетом общего наследия QTP с технологией TULIP, которая вышла из QUICKTEST для Web.Эта база тюльпанов была основой для QuicktestPro с функциональной стороны и с некоторыми новыми веб-технологиями HTTP на стороне нагрузки.

0 голосов
/ 19 сентября 2013

Операции с таблицей данных в QTP очень медленные Поэтому используйте формулы Excel в Datatable для ускорения работы.

0 голосов
/ 19 августа 2011

200-кратное снижение производительности связано с операциями DataTable. Другие операции все еще медленнее в QTP, чем в MMDRV, но не с таким ужасным фактором.

Я обошел это, «кэшируя» все вызовы DataTable в пользовательской структуре (на самом деле это коллекция объектов). Построение этого занимает 5 секунд, так как я запрашиваю много листов и свойств параметров. Обработка моей структуры вместо вызова свойств DTSheet и DTParameter выполняется намного быстрее и достаточно быстро.

Я подозреваю, что в QTP все обращения к таблицам данных осуществляются через пользовательский элемент управления Excel, который они (HP) лицензировали от третьей стороны, в то время как в 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 обнаруживает это значение в соответствии с именем и выплевывает "enum". Вот оно (я не покажу 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 , и даже при том, что мы уже избежали всех избыточных вызовов и сделали все другие очевидные оптимизации в традиционном коде доступа к таблице данных.

...