菜单
开源

JavaScript 扩展

利用 Go 的速度、能力和效率,同时提供在测试脚本中使用 JavaScript API 的灵活性。

通过实现 k6 接口,您可以弥合测试设置中的各种差距

  • 新的网络协议
  • 性能提升
  • k6 核心版不支持的功能

开始之前

要运行本教程,您需要安装以下应用

  • Go
  • Git

您还需要安装 xk6

bash
go install go.k6.io/xk6/cmd/xk6@latest

编写一个简单的扩展

  1. 首先,设置一个工作目录
bash
mkdir xk6-compare; cd xk6-compare; go mod init xk6-compare
  1. 在该目录中,为您的 JavaScript 扩展创建一个 Go 文件。

一个简单的 JavaScript 扩展需要一个结构体,该结构体暴露供测试脚本调用的方法。

Go
package compare

import "fmt"

// Compare is the type for our custom API.
type Compare struct{
    ComparisonResult string // textual description of the most recent comparison
}

// IsGreater returns true if a is greater than b, or false otherwise, setting textual result message.
func (c *Compare) IsGreater(a, b int) bool {
    if a > b {
        c.ComparisonResult = fmt.Sprintf("%d is greater than %d", a, b)
        return true
    } else {
        c.ComparisonResult = fmt.Sprintf("%d is NOT greater than %d", a, b)
        return false
    }
}
  1. 注册模块以便从 k6 测试脚本中使用这些方法。

注意

k6 扩展必须带有 k6/x/ 前缀,并且在同一 k6 二进制文件中构建的所有扩展中,短名称必须唯一。

Go
import "go.k6.io/k6/js/modules"

// init is called by the Go runtime at application startup.
func init() {
    modules.Register("k6/x/compare", new(Compare))
}
  1. 将文件保存为类似 compare.go 的名称。最终代码如下所示
Go
package compare

import (
    "fmt"
    "go.k6.io/k6/js/modules"
)

// init is called by the Go runtime at application startup.
func init() {
    modules.Register("k6/x/compare", new(Compare))
}

// Compare is the type for our custom API.
type Compare struct{
    ComparisonResult string // textual description of the most recent comparison
}

// IsGreater returns true if a is greater than b, or false otherwise, setting textual result message.
func (c *Compare) IsGreater(a, b int) bool {
    if a > b {
        c.ComparisonResult = fmt.Sprintf("%d is greater than %d", a, b)
        return true
    } else {
        c.ComparisonResult = fmt.Sprintf("%d is NOT greater than %d", a, b)
        return false
    }
}

编译您的扩展 k6

要使用此扩展构建 k6 二进制文件,运行此命令

bash
xk6 build --with xk6-compare=.

注意

从源代码构建时,xk6-compare 是传递给 go mod init 的 Go 模块名称。通常,这将是一个类似于 github.com/grafana/xk6-compare 的 URL。

使用您的扩展

现在,在测试脚本中使用该扩展!

  1. 创建一个名为 test.js 的文件,然后添加以下代码
JavaScript
import compare from 'k6/x/compare';

export default function () {
  console.log(`${compare.isGreater(2, 1)}, ${compare.comparison_result}`);
  console.log(`${compare.isGreater(1, 3)}, ${compare.comparison_result}`);
}
  1. 使用 ./k6 run test.js 运行测试。

它应该输出以下内容

shell
INFO[0000] true, 2 is greater than 1                     source=console
INFO[0000] false, 1 is NOT greater than 3                source=console

使用高级模块 API

假设您的扩展需要访问 k6 内部对象,例如,在执行期间检查测试状态。我们将需要对上面的示例进行稍微复杂的更改。

我们的主 Compare 结构体应实现 modules.Instance 接口,以访问 modules.VU,进而检查 k6 内部对象,例如

此外,应该有一个根模块实现 modules.Module 接口,作为每个 VU 的 Compare 实例的工厂。

注意

重要性取决于您的模块大小。

这看起来像这样

Go
package compare

import (
    "fmt"
    "go.k6.io/k6/js/modules"
)

// init is called by the Go runtime at application startup.
func init() {
    modules.Register("k6/x/compare", New())
}

type (
    // RootModule is the global module instance that will create module
    // instances for each VU.
    RootModule struct{}

    // ModuleInstance represents an instance of the JS module.
    ModuleInstance struct {
        // vu provides methods for accessing internal k6 objects for a VU
        vu modules.VU
        // comparator is the exported type
        comparator *Compare
    }
)

// Ensure the interfaces are implemented correctly.
var (
    _ modules.Instance = &ModuleInstance{}
    _ modules.Module   = &RootModule{}
)

// New returns a pointer to a new RootModule instance.
func New() *RootModule {
    return &RootModule{}
}

// NewModuleInstance implements the modules.Module interface returning a new instance for each VU.
func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
    return &ModuleInstance{
        vu: vu,
        comparator: &Compare{vu: vu},
    }
}

// Compare is the type for our custom API.
type Compare struct{
    vu modules.VU           // provides methods for accessing internal k6 objects
    ComparisonResult string // textual description of the most recent comparison
}

// IsGreater returns true if a is greater than b, or false otherwise, setting textual result message.
func (c *Compare) IsGreater(a, b int) bool {
    if a > b {
        c.ComparisonResult = fmt.Sprintf("%d is greater than %d", a, b)
        return true
    } else {
        c.ComparisonResult = fmt.Sprintf("%d is NOT greater than %d", a, b)
    return false
    }
}

// Exports implements the modules.Instance interface and returns the exported types for the JS module.
func (mi *ModuleInstance) Exports() modules.Exports {
    return modules.Exports{
        Default: mi.comparator,
    }
}

注意

请注意,我们实现了 Module API,现在 modules.Register 注册的是根模块,而不是我们的 Compare 对象!

访问运行时状态

目前,我们已从 Compare 类型提供了对 modules.VU 的访问;但是,我们尚未利用提供的方法。以下是一个如何利用运行时状态的人为示例

Go
// InternalState holds basic metadata from the runtime state.
type InternalState struct {
	ActiveVUs       int64       `js:"activeVUs"`
	Iteration       int64
	VUID            uint64      `js:"vuID"`
	VUIDFromRuntime sobek.Value `js:"vuIDFromRuntime"`
}

// GetInternalState interrogates the current virtual user for state information.
func (c *Compare) GetInternalState() *InternalState {
	state := c.vu.State()
	ctx := c.vu.Context()
	es := lib.GetExecutionState(ctx)
	rt := c.vu.Runtime()

	return &InternalState{
		VUID:            state.VUID,
		VUIDFromRuntime: rt.Get("__VU"),
		Iteration:       state.Iteration,
		ActiveVUs:       es.GetCurrentlyActiveVUsCount(),
	}
}

创建一个测试脚本以利用新的 getInternalState() 函数,如下所示

JavaScript
import compare from 'k6/x/compare';

export default function () {
  const state = compare.getInternalState();
  console.log(
    `Active VUs: ${state.activeVUs}, Iteration: ${state.iteration}, VU ID: ${state.vuID}, VU ID from runtime: ${state.vuIDFromRuntime}`
  );
}

执行脚本 ./k6 run test-state.js --vus 2 --iterations 5 将产生类似于以下内容的输出

shell
INFO[0000] Active VUs: 2, Iteration: 0, VU ID: 2, VU ID from runtime: 2  source=console
INFO[0000] Active VUs: 2, Iteration: 0, VU ID: 1, VU ID from runtime: 1  source=console
INFO[0000] Active VUs: 2, Iteration: 1, VU ID: 2, VU ID from runtime: 2  source=console
INFO[0000] Active VUs: 2, Iteration: 1, VU ID: 1, VU ID from runtime: 1  source=console
INFO[0000] Active VUs: 2, Iteration: 2, VU ID: 2, VU ID from runtime: 2  source=console

有关此 API 的更广泛使用示例,请查看 k6/execution 模块。

注意事项

  • default 函数(或由 exec 指定的其他函数)中的代码将在测试运行期间多次执行,并且可能由数千个 VU 并行执行。因此,您的扩展的任何操作都应具有高性能并是线程安全的。
  • 任何繁重的初始化应尽可能在初始化上下文中完成,而不是作为 default 函数执行的一部分。
  • 使用注册表的 NewMetric 方法创建自定义指标;要发送它们,使用 metrics.PushIfNotDone()

有疑问?欢迎加入 k6 社区论坛 的扩展讨论。

接下来,创建一个输出扩展,将测试指标发布到 k6 尚不支持的目标。