performance - texto - Operaciones de tabla de datos QTP*extremadamente*lento(mucho mejor bajo el ejecutor de lotes MMDRV)?
como centrar un texto en html (4)
Posiblemente una historia sensacional: QTP parece desperdiciar nuestro tiempo de trabajo sin ninguna razón:
Considere este script, que tiene una tabla de datos de exactamente una fila global con 26 columnas llamadas "A" a "Z" llenas de cualquier valor:
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"
Ejecutar esto bajo QTP 10 toma 15.1 segundos en mi bláster. (La ejecución animada está desactivada, por supuesto).
Ahora ejecuto esto usando mmdrv.exe desde la carpeta bin de QTP, dándole el parámetro "-usr ''''" con el nombre completo que incluye la ruta al archivo .usr de la prueba QTP.
Eso demora 0.07 segundos .
¿Hola? Eso es un aumento de rendimiento de 215 veces, pero una funcionalidad idéntica. ¿Cómo viene?
Estoy cavando por aquí ya que hacemos algunas cosas exóticas con tablas de datos QTP, y enfrentamos serios problemas de rendimiento bajo QTP. Creo que he rastreado la causa hasta las propiedades / métodos DataTable.GetSheet y DTSheet.GetParameter.
Ahora que veo que el MMDRV, que es para ejecutar pruebas QTP desde escenarios de LoadRunner, no tiene esa penalización de rendimiento, me pregunto lo siguiente:
- ¿Hay una alternativa 1: 1 para acceder a los archivos xls?
- ¿No debería alguien en Ex-Mercury / HP notar que el acceso a la tabla de datos bajo QTP es muy ineficiente, como lo demuestra MMDRV.EXE, y hacer algo al respecto?
- Por lo que puedo ver, todas las demás funciones de QTP tienen una velocidad comparable en MMDRV y QTP. ¿Alguien puede reconocer eso? * ¿Alguien más sabe sobre esto?
Gracias por cualquier respuesta, sin importar cuán inquietantes puedan ser.
* ACTUALIZACIÓN * La ejecución con QTP invisible toma 1.54 segundos. Eso es una mejora de 10 veces simplemente ocultando QTP como se indica en una de las respuestas. Suspiro.
Ejecutar con un entorno de desarrollo de GUI completo extrae una penalización de rendimiento. También se puede observar esta diferencia en VUGEN en LoadRunner, corriendo en MDRV proporcionando un aumento sustancial en el rendimiento donde se usa código complejo. También verá gente que con frecuencia se queja de que VUGEN es "más lento que la aplicación real".
Entonces, si esto me sorprende? Realmente no. Lo que es interesante es que no había considerado la existencia de MDRV en la instalación de QTP, pero eso lo hace dado el legado común de QTP con la tecnología TULIP que salió de QUICKTEST for Web. Esa base de tulipán ha sido la base de QuicktestPro en el lado funcional y parte de la tecnología HTTP más nueva en el lado de la carga.
Estamos teniendo los mismos problemas de rendimiento con QTP. Después de las investigaciones, arrinconamos los problemas en 2 áreas.
- La tabla de datos (rendimiento horrible)
- QTP es visible / invisible
Descubrimos que QTP funciona 5-6 veces más rápido cuando está oculto
Hicimos una pequeña secuencia de comandos para alternar la visibilidad QTP mientras desarrollamos / depuramos (porque siempre puede forzar que QTP esté oculto en la configuración del agente remoto) ''Este script se usa para mostrar / ocultar la ventana QTP'' QTP se ejecuta mucho más rápido cuando está oculto
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
¿Le gustaría compartir la idea de almacenar en caché el DataTable ya que estamos pensando en desarrollar el mismo mecanismo y nos beneficiaríamos de ver ese ejemplo?
Saludos cordiales, Achraf
La penalización de rendimiento de 200 veces proviene de las operaciones de DataTable. Otras operaciones aún son más lentas bajo QTP que bajo MMDRV, pero no con un factor de horror como ese.
Trabajé en eso al "caché" de todas las llamadas de DataTable en una estructura personalizada (realmente una colección de objetos). Construir eso lleva 5 segundos ya que consulto muchas Hojas y propiedades de parámetros. Procesar mi estructura en lugar de llamar a las propiedades DTSheet y DTParameter es mucho más rápido, de hecho lo suficientemente rápido.
Sospecho que bajo QTP todos los accesos a la tabla de datos se realizan a través del control personalizado de Excel que (HP) tienen licencia de un tercero, mientras que bajo MMDRV usan código que se integra más estrechamente, lo que genera menos sobrecarga por llamada.
Duke diría: "Jajaja, qué desastre".
** Actualización ** A petición, aquí hay un resumen de lo que quiero decir con "caché" de llamadas a DataTable. Es bastante código, así que prepárate ...
Aquí está ese desastre (lo siento, no tengo tiempo para traducir los comentarios en línea de Alemania en este momento) (y lo siento por el formato, obviamente no puedo hacer mucho al respecto, tal vez quieras cortar y pegar esto en el editor de QTP):
Todo comienza con una clase Container genérica en la que puedo almacenar (y acceder por índice) referencias de objetos 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
Utilizo nombres de columna especiales para dar a las columnas significados especiales. DetectColumnKind
detecta ese significado según el nombre y escupe una "enumeración". Esto es todo (no mostraré DetectColumnKind
aquí):
'' Von MBCollectAllTestData unterstützte Spaltenarten in Datentabellen
Private Const ckData = 0
Private Const ckReference = 1
Private Const ckComment = 2
Ahora viene lo verdadero:
Un contenedor con N representaciones de hoja. Recolecto las propiedades de cada hoja y las almaceno en ese contenedor.
'' 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
Aquí están las cosas para construir los contenidos del contenedor:
'' 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
Basado en la información del contenedor, uso las estructuras en TestDataDescr para iterar sobre columnas, etc.
Al igual que en este ejemplo, que recibe un elemento contenedor (SourceSheetDescr), mira cada columna y hace "algo" según el tipo de columna, que es parte de la información incorporada en el elemento contenedor:
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
De esta manera, evito tener que consultar DTSheet.GetParameter (), y con el nombre de coid para llamar a cualquier otro método de DataTable. Esto, por supuesto, es solo un ejemplo del uso de la información que contiene el contenedor.
En nuestros casos de uso típicos, este rendimiento se triplicó en comparación con los métodos de DataTable , y aunque ya habíamos evitado todas las llamadas redundantes, y todas las demás optimizaciones obvias en el código de acceso a la tabla de datos tradicional.
Las operaciones de tabla de datos son muy lentas en QTP. Así que use las fórmulas de Excel en su Datatable para acelerar la operación.