Scenarigoの紹介と
それを利用したメルペイでの
インテグレーションテスト実例
APIテストツール4選!開発者が語る各ツールの特徴と魅力
30 November 2023
@zoncoen
2
Who am I?
Kenta Mori
@zoncoen
Backend Engineer at Merpay, Inc.
Agenda
3
About Scenarigo
4
Scenarigo
5
Scenarigo
6
title: echo-service
steps:
- title: POST /echo
protocol: http
request:
method: POST
url: '{{env.ECHO_ADDR}}/echo'
body:
message: hello
expect:
body:
message: '{{request.message}}'
$ scenarigo run example.yaml
echo-service
Scenarigo
1. POST {{env.ECHO_ADDR}}/echo
2. Receive response
3. Check response
Background
7
Mercari App
API Gateway
Microservice A
Microservice B
Microservice C
Team A
Team B
Team C
Authority Service
Partner
HTTP
gRPC
Motivation
※ 2018年当時の話です
8
Goal
9
Include Example
10
title: echo
steps:
- title: login
include: ./login.yaml < 読み込むシナリオファイルを指定する
bind:
vars:
token: '{{vars.token}}' < login.yamlの結果をbind
- title: POST /echo
protocol: http
request:
method: POST
url: 'http://{{env.SERVER_ADDR}}/echo'
header:
Authorization: 'Bearer {{vars.token}}' < bindした値を利用する
body:
message: hello
expect:
code: 200
body:
message: '{{request.message}}'
gRPC Example
11
title: Echo
plugins:
grpc: grpc.so
steps:
- title: Echo
protocol: grpc
request:
client: '{{plugins.grpc.CreateClient(ctx, env.TEST_ADDR)}}'
^ gRPCクライアントを渡す
method: Echo < Method指定
message:
messageBody: hello
expect:
code: OK
message:
messageBody: '{{request.messageBody}}'
Plugin
12
Plugin
13
title: echo
plugins:
date: date.so
steps:
- title: POST /echo
protocol: http
request:
method: POST
url: '{{env.ECHO_ADDR}}/echo'
body:
message: '{{plugins.date.Today()}}'
expect:
code: 200
package main
import "time"
func Today() string {
return time.Now().Format(Layout)
}
$ scenarigo plugin build
schemaVersion: config/v1
scenarios:
- scenarios
pluginDirectory: ./gen
plugins:
date.so:
src: ./plugins/date
Why Use the std plugin?
14
Philosophy
15
Explicitly Define
16
title: echo
steps:
- title: login
include: ./login.yaml
bind:
vars:
token: '{{vars.token}}'
- title: POST /echo
protocol: http
request:
method: POST
url: 'http://{{env.SERVER_ADDR}}/echo'
header:
Authorization: 'Bearer {{vars.token}}'
body:
message: hello
expect:
code: 200
body:
message: '{{request.message}}'
明示的にbindした値のみ以降のstepで参照できる
-> どこで定義されているかこのファイルを読むだけでわかる
LSPなどを実装するときにも楽(な気がしている)
Scenario Testing Platform in Merpay
17
Customize
18
CI
19
horologium
Create periodic ProwJobs
k8s API
plank
ProwJob Controller
deck
Dashboard
GCS
Job Controller
ProwJobs
hook
GitHub
Send events
Create ProwJob
Create ProwJob
Create
Jobs
Create Job based on ProwJob
Create
Create Pod
based on Job
Create
Store logs
Pods
Run Scenarigo
Get logs
Micro
services
Send Requests
Dashboard
21
Result
22
Run Test on Local
23
Run Test in the Cluster
24
foo-service
foo-namespace
Pod
test-namespace
foo-service.foo-namespace
.svc.cluster.local
local
Run Test on Local
25
foo-service
foo-namespace
Pod
local
test-namespace
foo-service.foo-namespace
.svc.cluster.local
Run Test on Local
26
foo-service
foo-namespace
Pod
local
test-namespace
foo-service.foo-namespace
.svc.cluster.local
Proxy
Host: foo-service.foo-namespace
.svc.cluster.local
Access Control
27
SA Based Access Control
28
foo-service
foo-namespace
Pod
test-namespace
local
foo-repo@
example.com
zoncoen@
example.com
Google Secret Manager
GetSecret()
SA Based Access Control
29
foo-service
foo-namespace
Pod
test-namespace
local
foo-repo@
example.com
zoncoen@
example.com
Google Secret Manager
GetSecret()
SA Based Access Control
30
foo-service
foo-namespace
Pod
test-namespace
local
foo-repo@
example.com
zoncoen@
example.com
Google Secret Manager
GetSecret()
foo-repo@
example.com
Impersonate
Proxy
Conclusion
31
Conclusion
32
Appendix - How to write test scenario
33
Send HTTP requests
34
title: check /message
steps:
- title: GET /message
protocol: http
request:
method: GET
Send HTTP requests
35
title: check /message
steps:
- title: GET /message
protocol: http
request:
method: GET
url: http://example.com/message
query:
id: 1
Send HTTP requests
36
title: check /message
steps:
- title: POST /message
protocol: http
request:
method: POST
url: http://example.com/message
body:
message: hello
Send HTTP requests
37
title: check /message
steps:
- title: POST /message
protocol: http
request:
method: POST
url: http://example.com/message
header:
Content-Type: application/x-www-form-urlencoded
body:
message: hello
Send HTTP requests
38
Check HTTP responses
39
title: check /message
steps:
- title: GET /message
protocol: http
request:
method: GET
url: http://example.com/message
query:
id: 1
expect:
code: OK
body:
id: 1
message: hello
Check HTTP responses
40
Retry
41
title: check /message
steps:
- title: GET /message
protocol: http
request:
method: GET
url: http://example.com/message
retry:
constant:
interval: 1s
maxElapsedTime: 5s
maxRetries: 5
title: check /message
steps:
- title: GET /message
protocol: http
request:
method: GET
url: http://example.com/message
retry:
exponential:
initialInterval: 500ms
factor: 2
jitterFactor: 0.5
maxInterval: 2m
maxElapsedTime: 5m
maxRetries: 10
Using variables
42
title: check /message
vars:
id: 1
steps:
- title: GET /message
protocol: http
request:
method: GET
url: http://example.com/message
query:
id: '{{vars.id}}'
Using variables
43
title: check /message
steps:
- title: GET /message
vars:
- 1
protocol: http
request:
method: GET
url: http://example.com/message
query:
id: '{{vars[0]}}'
Reuse response value
44
- title: POST /message
protocol: http
request:
method: POST
url: http://example.com/message
body:
message: hello
expect:
code: OK
body:
message: '{{request.message}}'
bind:
vars:
id: '{{response.id}}' < set
- title: GET /message
protocol: http
request:
method: GET
url: http://example.com/message
query:
id: '{{vars.id}}' < use
expect:
code: OK
Environment variables
45
title: check /message
steps:
- title: GET /message
protocol: http
request:
method: GET
url: '{{env.TEST_ADDR}}/message'
Reuse scenario
46
title: get message
steps:
- title: GET /message
vars:
message: hello
include: create-message.yaml < include
bind:
vars:
id: '{{vars.id}}' < set
- title: GET /message
protocol: http
request:
method: GET
url: http://example.com/message
query:
id: '{{vars.id}}' < use
title: create message
vars:
message: '{{vars.message}}' < input
steps:
- title: POST /message
protocol: http
request:
method: POST
url: http://example.com/message
body:
message: '{{vars.message}}'
bind:
vars:
id: '{{response.id}}' < output
Plugin
47
title: echo
plugins:
date: date.so
steps:
- title: POST /echo
protocol: http
request:
method: POST
url: '{{env.ECHO_ADDR}}/echo'
body:
message: '{{plugins.date.Today()}}'
expect:
code: 200
package main
import "time"
func Today() string {
return time.Now().Format(Layout)
}
$ scenarigo plugin build
schemaVersion: config/v1
scenarios:
- scenarios
pluginDirectory: ./gen
plugins:
date.so:
src: ./plugins/date
Send gRPC requests
48
title: check Ping method
plugins:
grpc: 'grpc.so'
steps:
- title: call Ping
protocol: grpc
request:
client: '{{plugins.grpc.EchoClient}'
method: Ping
body:
message: hello
expect:
code: OK
body:
message: '{{request.message}}'