VBScript 函数作为参数,或类似的构造

VBScript Function as Parameter, or similar Construct

我正在尝试在 HP Unified Functional Testing 中组合测试 程序员的方式。 对于那些不知道的人,该工具使用 VBScript 作为其驱动程序。

因为我想在多个 UFT 操作中使用同一个 DataTable 中的数据 -- 因为全局 table 已经有一组不同的数据 -- 我想从外部文件中检索数据。 UFT 很高兴支持此功能。

我目前的计划是 运行,这取决于我进行的测试, 我将仅遍历 table.

中的一系列行

这是我想出的脚本:

' targets the local sheet, but 
' not the same value as dtLocalSheet
Const sheetNum = 2 

dim sheetRowCount
DataTable.ImportSheet "PersonFile.xlsx", 1, sheetNum
sheetRowCount = DataTable.GetSheet(sheetNum).GetRowCount

dim firstRow, lastRow
firstRow = Parameter("FirstPersonIndex")
lastRow = Parameter("LastPersonIndex")
If sheetRowCount < lastRow Then
    lastRow = sheetRowCount
End If

If sheetRowCount >= firstRow Then
    Dim i
    For i = firstRow To lastRow
        DataTable.SetCurrentRow i

        ' begin payload
        MsgBox(DataTable.Value("LastName", dtLocalSheet))
        ' end payload

    Next
End if

我不想重复所有这些样板文件 每次我想使用这种模式。 我真的很想有这样的东西:

在函数库中:

sub LoopThroughSheetAnd(sheetFile, doThis)
    ' targets the local sheet, but 
    ' not the same value as dtLocalSheet
    Const sheetNum = 2 

    dim sheetRowCount
    DataTable.ImportSheet sheetFile, 1, sheetNum
    sheetRowCount = DataTable.GetSheet(sheetNum).GetRowCount

    dim firstRow, lastRow
    firstRow = Parameter("FirstRow")
    lastRow = Parameter("LastRow")
    If sheetRowCount < lastRow Then
        lastRow = sheetRowCount
    End If

    If sheetRowCount >= firstRow Then
        Dim i
        For i = firstRow To lastRow
            DataTable.SetCurrentRow i

            call doThis()
        Next
    End if
end sub

原动作中...

sub Payload1()
    MsgBox(DataTable.Value("LastName", dtLocalSheet))
end sub
LoopThroughSheetAnd "PersonFile.xlsx", Payload1

在一个单独的动作中,3 或 4 步之后...

sub Payload2()
    ' compare the data against another data source
end sub
LoopThroughSheetAnd "PersonFile.xlsx", Payload2

以上代码在 VBScript 中不起作用。 抛出类型不匹配错误 一旦我们尝试将 Payload1 作为参数传递。

如何在 VBScript 中合理地实现这一目标? 如果答案在 UFT 中也有效,则加分。

当我写下这个问题时,我的记忆开始运转, 然后我开始研究我曾经发现的 "feature"。

这在 HP UFT 的上下文中不起作用, 但是如果你是 运行 cscript,或者使用 Classic ASP, 你可以延迟声明一个函数,或者替换之前的声明, 改变它的工作方式。

VBScript 允许您声明相同的函数或子例程 在程序中多次。 它将最后的声明视为正确的声明。

你可以在 cscript 和 ASP 中通过物理分离来解决这个问题 函数的不同版本, 这样一个人就不会被另一个人破坏。 你必须小心不要将两者放在彼此靠近的任何地方, 或者你(你的继任者)可能有动脉瘤试图调试结果。

老实说,以其他方式重构代码可能会更好。

现在,免责声明已取消, 以下示例用于 cscript 或 wscript。

代码

因为这在 UFT 中无论如何都行不通,所以我将从头开始写。

WrapperSub.vbs:

' Sub WrapperSub_Payload doesn't exist in this file.
' It must be declared by the calling file or the program will crash.
Sub WrapperSub()
    wscript.echo("This begins the wrapper.")
    WrapperSub_Payload
    wscript.echo("This ends the wrapper.")
End Sub

WrapperSubUseA.vbs:

With CreateObject("Scripting.FileSystemObject")
    call ExecuteGlobal(.openTextFile("WrapperSub.vbs").readAll())
End With

Sub WrapperSub_Payload
    wscript.echo("This is payload A.")
End Sub

WrapperSub

WrapperSubUseB.vbs:

With CreateObject("Scripting.FileSystemObject")
    call ExecuteGlobal(.openTextFile("WrapperSub.vbs").readAll())
End With

Sub WrapperSub_Payload
    wscript.echo("This is payload B.")
End Sub

WrapperSub

输出

>cscript wrappersubusea.vbs
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.

This begins the wrapper.
This is payload A.
This ends the wrapper.

>cscript wrappersubuseb.vbs
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.

This begins the wrapper.
This is payload B.
This ends the wrapper.

请注意,如果 WrapperSub_Payload 的占位符 在源文件中声明, 该占位符将始终执行而不是预期的子例程。 这可能是由于 ExecuteGlobal 在解析当前文件后执行, 导致占位符在局部声明之后加载。

当您在 UFT 中尝试此操作时 -- 将 WrapperSub.vbs 的内容放入函数库中 -- 函数库理所当然地忽略了调用者的作用域。 然后它将失败,因为 WrapperSub_Payload 不存在于范围内。

可以将函数作为参数传递给GetRef()函数。这是一个实用程序 map 函数,就像您在 JavaScript 中找到的那样,它接受一个数组并为数组的每个元素调用一个函数:

Sub Map(a, f)
    Dim i
    For i = 0 To UBound(a)
        ' Call a function on each element and replace its value with the function return value
        a(i) = f(a(i))      
    Next
End Sub

Map MyArray, GetRef("SomeFunc")

现在您可以编写 SomeFunc 以便它对值和 returns 更新值进行操作:

Function SomeFunc(i)
    SomeFunc = i + 1
End Function

这很好用。 map 使用我们传递给它的函数 "pointer" 调用 SomeFunc

您可以使用 LoopThroughStreetAnd 函数执行类似的操作:

LoopThroughStreetAnd "PersonFile.xlsx", GetRef("Payload2")

VBScript 中的 标准 回调方式使用 GetRef, as in this demo.

使用对象时,可以将对方法的调用包装在对象中,然后传递对象。 (这在其他语言中已经差不多发生了,您只需在 VBScript 中手动完成即可。)

唯一的问题是任何以这种方式调用的方法都必须是 Public

我会使用 "Func1"、"Func2"、"Action1"、"Action2" 等命名方案,具体取决于函数的数量以及是否他们 return 值与否。

Dim s : Set s = New Something : s.Run

Class Something
   Public Sub HowToPassMe(pValue)
      WScript.Echo pValue
   End Sub

   Public Sub Run
      Dim action : Set action = New Action1Wrapper
      Set action.Target = Me
      Dim se : Set se = New SomethingElse
      se.DoSomethingElse action
   End Sub
End Class

Class SomethingElse
   Public Sub DoSomethingElse(pAction1)
      pAction1.Action1("something")
   End Sub
End Class

Class Action1Wrapper
   Private mTarget
   Public Property Set Target(value) : Set mTarget = value : End Property

   Public Sub Action1(p1)
      mTarget.HowToPassMe(p1)
   End Sub
End Class

利用ExecuteAction1Wrapper也可以写成类似下面的样子。也可以自己写一个工厂class方便使用

Class Action1Wrapper
   Private mTarget
   Public Property Set Target(value) : Set mTarget = value : End Property

   Private mName
   Public Property Let Name(value) : mName = value : End Property

   Public Sub Action1(p1)
      Execute "mTarget." & mName & "(p1)"
   End Sub
End Class

Class Action1Factory_
   Public Function Create(pTarget, pName)
      Dim a1 : Set a1 = New Action1Wrapper
      Set a1.Target = pTarget
      a1.Name = pName
      Set Create = a1
   End Function
End Class
Dim Action1Factory : Set Action1Factory = New Action1Factory_

用作:

Dim action : Set action = Action1Factory.Create(Me, "HowToPassMe")
Dim se : Set se = New SomethingElse
se.DoSomethingElse action