Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Spring Bootで作成したAPIテストのコスパを高めよう!

Spring Bootで作成したAPIテストのコスパを高めよう!

JJUC CCC 2024 Spring

APIを作った際、curlコマンドやgrpcurlコマンドを使って動作確認をしていました。しかし、OpenAPIやgRPCを利用してAPIを作っている場合においてシナリオテストを導入すると、とても素早くしかもコマンドで確認するより正確なテストができました。

このセッションでは、OpenAPIまたはProtocol BuffersをつかったgRPCをSpring Bootで実装されたAPIに対してどのようにシナリオテストを行うのか、シナリオテストによって得られるメリット・デメリットについてお話しします。

gorou_178

June 15, 2024
Tweet

More Decks by gorou_178

Other Decks in Technology

Transcript

  1. ࣗݾ঺հ • ݀Ҫ ྯ(͞ͱ͠) • ෱Ԭ͔Βདྷ·ͨ͠ʢॳࢀՃͰ͢ʣ • X(چTwitter): @gorou_178 •

    Bluesky: gorou178.bsky.social • ۚ༥ܥͰEMͯ͠·͢ • εΩϧ: GCP/Java/Kotlin/PHP/AWS • ίʔώʔ/ϓϦϯ/θϧμ/ϥϯχϯά
  2. APIͷςετ • Postman → ಠࣗUI(JSON) • JMeter → ಠࣗUI(jmxϑΝΠϧ) •

    locust → Python • K6 → JavaScript(goja) • Cypress → JavaScript • etc…
  3. openapi-generator-cliΛdocker͔Βར༻ͯ͠ 
 OpenAPI͔Βιʔείʔυੜ੒ OpenAPI͔Βιʔείʔυੜ੒ docker run --rm \ -v "${PWD}/build/openapi:/out"

    \ openapitools/openapi-generator-cli:v7.3.0 generate \ --generator-name spring \ --input-spec "${PWD}/petstore.yaml" \ --output /out
  4. ੜ੒ͨ͠ίʔυΛར༻ͯ͠APIΛ࣮૷ APIΛ࣮૷ @Controller @RequestMapping("/pet") public class PetController implements PetApi {

    @PostMapping("/") @Override public ResponseEntity<Pet> addPet(Pet pet) { return ResponseEntity.ok(new Pet("addDog", Collections.emptyList())); } @DeleteMapping("/") @Override public ResponseEntity<Void> deletePet(Long petId, String apiKey) { return ResponseEntity.ok().build(); }
  5. ҎԼͷ༷ͳγφϦΦΛ࡞੒(pet-api-runn.yml) runnͷrunnbook(γφϦΦϑΝΠϧ)࡞੒ desc: "pet api test" runners: req: endpoint: http://localhost:8080

    openapi3: ./petstore.yaml steps: getPets: req: /pet/123: get: headers: Accept: application/json body: null test: steps.getPets.res.status == 200
  6. APIυΩϡϝϯτͷΧόϨοδΛදࣔ 
 ͲͷAPI͕ςετͰ͖͍ͯͳ͍͔͸ "—long" ΦϓγϣϯͰදࣔՄ runn coverage $ runn coverage

    pet-api-runn.yml Spec Coverage -------------------------------------- Total 5.0% OpenAPI Petstore:1.0.0 5.0%
  7. APIυΩϡϝϯτͷϨεϙϯε࢓༷ͱҟͳΔϨεϙϯεΛ 
 ฦͨ͠৔߹ɺΤϥʔʹͳͬͯৄࡉ͕දࣔ͞ΕΔ runn OpenAPI3 check $ runn run pet-api-runn.yml

    F 1) pet-api-runn.yml xxxxxxxxxxxxxxxx Failure/Error: http request failed on "pet api test".steps.getPets: openapi3 validation error: response body doesn't match schema #/components/schemas/Pet: Error at "/tags": Value is not nullable
  8. protobufΛ࡞੒ syntax = "proto3"; option java_package = "com.example.grpc.server.greeter"; option java_outer_classname

    = "Greeter"; message HelloRequest { string name = 1; } message HelloReply { string message = 1; } service GreeterService { rpc SayHello (HelloRequest) returns (HelloReply) {} }
  9. buf.gen.ymlΛ࡞੒ protocolbuffers͸͋͑ͯv25.1Λࢦఆ(ഁյతมߋ͕͋ΔͨΊ) version: v1 plugins: - plugin: buf.build/grpc/java out: src/main/java

    - plugin: buf.build/grpc/kotlin out: src/main/kotlin - plugin: buf.build/protocolbuffers/java:v25.1 out: src/main/java - plugin: buf.build/protocolbuffers/kotlin:v25.1 out: src/main/kotlin
  10. gRPCαʔϏεΛ࡞੒ • ੜ੒͞ΕͨίʔυΛܧঝͯ͠த਎Λ࣮૷ class GreeterService : GreeterServiceGrpc.GreeterServiceImplBase() { override fun

    sayHello( request: Greeter.HelloRequest, responseObserver: StreamObserver<Greeter.HelloReply>) { val reply = Greeter.HelloReply.newBuilder().apply { message = "Hello ${request.name}" }.build() responseObserver.onNext(reply) responseObserver.onCompleted() } }
  11. runnγφϦΦϑΝΠϧΛ࡞੒ ϦΫΤετ/Ϩεϙϯε͔Βੜ੒Ͱ͖Δ runn newίϚϯυ 
 localhostͩͱTLSΤϥʔ͕ग़ΔͨΊ —grpc-no-tlsΛ෇༩ $ runn new

    —grpc-no-tls --out test-runn.yml -- grpcurl \ -d '{"name": "abc"}' localhost:9090 GreeterService/SayHello $ cat test-runn.yml desc: Generated by `runn new` runners: greq: grpc://localhost:9090 steps: - greq: GreeterService/SayHello: message: name: abc
  12. γφϦΦςετ΋ࣗಈੜ੒ • runn new —and-run Ͱඞཁ࠷௿ݶͳςετ΋ग़ྗ $ runn new --and-run

    --grpc-no-tls --out test-runn.yml \ -- grpcurl -d '{"name": "test"}' localhost:9090 GreeterService/SayHello $ cat test-runn.yml desc: Generated by `runn new` runners: greq: grpc://localhost:9090 steps: - greq: GreeterService/SayHello: message: name: abc test: | current.res.headers["content-type"][0] == "application/grpc" && current.res.headers["grpc-accept-encoding"][0] == "gzip" && compare(current.res.message, {"message":"Hello abc"}) && current.res.status == 0
  13. ࢀߟURL • k1LoW/runn - GitHub • runnΫοΫϒοΫ • Quick Start

    Kotlin - gRPC • BufͱProtobufͰ࢝ΊΔϞμϯgRPC։ൃ • αόϯφศΓʙࣗಈςετʹؔ͢Δ࿈ࡌͰಘΒΕͨ஌ݟͷ·ͱΊʢ2023೥5݄൛ʣ • Buf CLI • https://circleci.com/blog/testing-pyramid/ • https://github.com/bufbuild/protovalidate • Protocol Buffers v26.0 ͷഁյతมߋ - qiita