Slide 1

Slide 1 text

1 E2E Testing with `main` Yuki ITO

Slide 2

Slide 2 text

Table of Contents !2 ɾExample Server ɾTesting without `main` ɾTesting with `main`

Slide 3

Slide 3 text

Table of Contents !3 ɾExample Server ɾTesting without `main` ɾTesting with `main`

Slide 4

Slide 4 text

Example Server !4 https://github.com/110y/go-e2e-example Repository

Slide 5

Slide 5 text

Example Server !5 !"" e2e # !"" e2e_test.go # $"" echo_test.go !"" server # !"" pb # # !"" server.pb.go # # $"" server.proto # $"" server.go !"" main.go $"" main_test.go packages

Slide 6

Slide 6 text

Example Server !6 !"" e2e # !"" e2e_test.go # $"" echo_test.go !"" server # !"" pb # # !"" server.pb.go # # $"" server.proto # $"" server.go !"" main.go $"" main_test.go packages

Slide 7

Slide 7 text

Example Server !7 service Server { rpc Echo(EchoRequest) returns (EchoResponse) {} } message EchoRequest { string echo = 1; } message EchoResponse { string echo = 1; } server/pb/server.proto

Slide 8

Slide 8 text

Example Server !8 !"" e2e # !"" e2e_test.go # $"" echo_test.go !"" server # !"" pb # # !"" server.pb.go # # $"" server.proto # $"" server.go !"" main.go $"" main_test.go packages

Slide 9

Slide 9 text

Example Server !9 func (s *server) Echo(…) … { return &pb.EchoResponse{ Echo: req.Echo, }, nil } server/server.go

Slide 10

Slide 10 text

Example Server !10 !"" e2e # !"" e2e_test.go # $"" echo_test.go !"" server # !"" pb # # !"" server.pb.go # # $"" server.proto # $"" server.go !"" main.go $"" main_test.go packages

Slide 11

Slide 11 text

Example Server !11 main.go func main() { // … lis, err := net.Listen("tcp", addr) // … gs := grpc.NewServer() pb.RegisterServerServer(gs, &server.Server{}) go func() { if err := gs.Serve(lis); err != nil { // … } }() // … }

Slide 12

Slide 12 text

Example Server !12 $ grpcurl \ > -plaintext \ > -d "$(jo -p echo='Welcome to mercari.go #8')" \ > localhost:9090 server.Server/Echo { "echo": "Welcome to mercari.go #8" } Request / Response

Slide 13

Slide 13 text

Table of Contents !13 ɾExample Server ɾTesting without `main` ɾTesting with `main`

Slide 14

Slide 14 text

Testing without `main` !14 !"" e2e # !"" e2e_test.go # $"" echo_test.go !"" server # !"" pb # # !"" server.pb.go # # $"" server.proto # $"" server.go !"" main.go $"" main_test.go packages

Slide 15

Slide 15 text

Testing without `main` !15 func TestMain(m *testing.M) { os.Exit(func() (status int) { // … lis, err := net.Listen(“tcp", addr) // … gs := grpc.NewServer() pb.RegisterServerServer(gs, &Server{}) go func() { if err := gs.Serve(lis); err != nil { // … } }() // … }()) } server/server_test.go

Slide 16

Slide 16 text

Testing without `main` !16 func TestMain(m *testing.M) { os.Exit(func() (status int) { // … conn, err := grpc.DialContext( context.Background(), fmt.Sprintf(":%s", port), grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(5*time.Second), ) // … grpcClient = pb.NewServerClient(conn) // … return m.Run() }()) } server/server_test.go

Slide 17

Slide 17 text

Testing without `main` !17 server/server_test.go func TestEcho(t *testing.T) { // … req := &pb.EchoRequest{ Echo: "foo", } res, err := grpcClient.Echo(ctx, req) if err != nil { t.Fatalf("failed to request to the server: %s", err) } if res.Echo != req.Echo { t.Errorf("want %s, but got %s", req.Echo, res.Echo) } }

Slide 18

Slide 18 text

Testing without `main` !18 $ go test ./server === RUN TestEcho --- PASS: TestEcho (0.00s) PASS ok github.com/110y/go-e2e-example/server 0.022s

Slide 19

Slide 19 text

Testing without `main` !19 func (s *server) Echo(…) … { return &pb.EchoResponse{ Echo: req.Echo, }, nil } server/server.go Coverage

Slide 20

Slide 20 text

Testing without `main` !20 func (s *server) Echo(…) … { return &pb.EchoResponse{ Echo: req.Echo, }, nil } server/server.go Coverage However…

Slide 21

Slide 21 text

Testing without `main` !21 `main` is NOT covered main.go func main() { // … lis, err := net.Listen("tcp", addr) // … gs := grpc.NewServer() pb.RegisterServerServer(gs, &server.Server{}) go func() { if err := gs.Serve(lis); err != nil { // … } }() // … }

Slide 22

Slide 22 text

Testing without `main` !22 Duplication between main.go & server_test.go func main() { // … lis, err := net.Listen("tcp", addr) // … gs := grpc.NewServer() pb.RegisterServerServer(gs, &server.Server{}) go func() { if err := gs.Serve(lis); err != nil { // … } }() // … }

Slide 23

Slide 23 text

Testing without `main` !23 Duplication between main.go & server_test.go func TestMain(m *testing.M) { os.Exit(func() (status int) { // … lis, err := net.Listen(“tcp", addr) // … gs := grpc.NewServer() pb.RegisterServerServer(gs, &Server{}) go func() { if err := gs.Serve(lis); err != nil { // … } }() //…

Slide 24

Slide 24 text

Table of Contents !24 ɾExample Server ɾTesting without `main` ɾTesting with `main`

Slide 25

Slide 25 text

Testing with `main` !25 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 26

Slide 26 text

Testing with `main` !26 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 27

Slide 27 text

Testing with `main` !27 !"" e2e # !"" e2e_test.go # $"" echo_test.go !"" server # !"" pb # # !"" server.pb.go # # $"" server.proto # $"" server.go !"" main.go $"" main_test.go packages

Slide 28

Slide 28 text

Testing with `main` !28 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶉ m.Run() Launch ᶈ Connect

Slide 29

Slide 29 text

Testing with `main` !29 e2e/e2e_test.go func TestMain(m *testing.M) { os.Exit(func() (status int) { // … cmd := exec.CommandContext( ctx, “make", “test-server”, ) if err := cmd.Start(); err != nil { return 1 } // ... }()) }

Slide 30

Slide 30 text

Testing with `main` !30 Makefile .PHONY: test-server test-server: @go test . \ -race \ -count=1 \ -covermode=atomic \ -coverprofile=coverage.out \ -run='^TestRunMain$$'

Slide 31

Slide 31 text

Testing with `main` !31 !"" e2e # !"" e2e_test.go # $"" echo_test.go !"" server # !"" pb # # !"" server.pb.go # # $"" server.proto # $"" server.go !"" main.go $"" main_test.go packages

Slide 32

Slide 32 text

Testing with `main` !32 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 33

Slide 33 text

Testing with `main` !33 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 34

Slide 34 text

Testing with `main` !34 main_test.go func TestRunMain(t *testing.T) { // … go main() // … }

Slide 35

Slide 35 text

Testing with `main` !35 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 36

Slide 36 text

Testing with `main` !36 main.go func main() { // … lis, err := net.Listen("tcp", addr) // … gs := grpc.NewServer() pb.RegisterServerServer(gs, &server.Server{}) go func() { if err := gs.Serve(lis); err != nil { // … } }() // … }

Slide 37

Slide 37 text

Testing with `main` !37 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 38

Slide 38 text

Testing with `main` !38 e2e/e2e_test.go func TestMain(m *testing.M) { os.Exit(func() (status int) { // … if err := cmd.Start(); err != nil { return 1 } conn, err := grpc.DialContext( ctx, fmt.Sprintf(":%s", port), grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(5*time.Second), ) //… grpcClient = pb.NewServerClient(conn) //… }

Slide 39

Slide 39 text

Testing with `main` !39 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 40

Slide 40 text

Testing with `main` !40 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 41

Slide 41 text

Testing with `main` !41 e2e/e2e_test.go func TestMain(m *testing.M) { os.Exit(func() (status int) { // … return m.Run() }()) }

Slide 42

Slide 42 text

Testing with `main` !42 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 43

Slide 43 text

Testing with `main` !43 !"" e2e # !"" e2e_test.go # $"" echo_test.go !"" server # !"" pb # # !"" server.pb.go # # $"" server.proto # $"" server.go !"" main.go $"" main_test.go packages

Slide 44

Slide 44 text

Testing with `main` !44 echo_test.go func TestEcho(t *testing.T) { // … req := &pb.EchoRequest{ Echo: "foo", } res, err := grpcClient.Echo(ctx, req) if err != nil { t.Fatalf("failed to request to the server: %s", err) } if res.Echo != req.Echo { t.Errorf("want %s, but got %s", req.Echo, res.Echo) } }

Slide 45

Slide 45 text

Testing with `main` !45 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 46

Slide 46 text

Testing with `main` !46 $ go test ./e2e === RUN TestEcho --- PASS: TestEcho (0.00s) PASS ok github.com/110y/go-e2e-example/e2e 3.070s

Slide 47

Slide 47 text

Testing with `main` !47 func (s *server) Echo(…) … { return &pb.EchoResponse{ Echo: req.Echo, }, nil } server/server.go Coverage

Slide 48

Slide 48 text

Testing with `main` !48 Coverage main.go func main() { // … lis, err := net.Listen("tcp", addr) // … gs := grpc.NewServer() pb.RegisterServerServer(gs, &server.Server{}) go func() { if err := gs.Serve(lis); err != nil { // … } }() // … }

Slide 49

Slide 49 text

Testing with `main` !49 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 50

Slide 50 text

Testing with `main` !50 ᶃ go test ./e2e ᶆ go test . .PHONY: test-server test-server: @go test . \ -race \ -count=1 \ -covermode=atomic \ -coverprofile=coverage.out \ -run='^TestRunMain$$' .PHONY: test test: go test -v -race -count=1 ./e2e

Slide 51

Slide 51 text

Testing with `main` !51 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 52

Slide 52 text

Testing with `main` !52 func main() { // … lis, err := net.Listen("tcp", addr) // … gs := grpc.NewServer() pb.RegisterServerServer(gs, &server.Server{}) go func() { if err := gs.Serve(lis); err != nil { // … } }() // … } func (s *server) Echo(…) … { return &pb.EchoResponse{ Echo: req.Echo, }, nil }

Slide 53

Slide 53 text

Testing with `main` !53 Termination main_test.go func TestRunMain(t *testing.T) { // … go main() // … }

Slide 54

Slide 54 text

Testing with `main` !54 Termination main_test.go func TestRunMain(t *testing.T) { go main() lis, err := net.Listen("tcp", addr) // … conn, err := lis.Accept() // … } Blocking with `Accept()`

Slide 55

Slide 55 text

Testing with `main` !55 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch

Slide 56

Slide 56 text

Testing with `main` !56 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶉ m.Run() Launch ᶈ Connect

Slide 57

Slide 57 text

Testing with `main` !57 e2e/e2e_test.go func TestMain(m *testing.M) { os.Exit(func() (status int) { // … return m.Run() }()) } Termination

Slide 58

Slide 58 text

Testing with `main` !58 e2e/e2e_test.go func TestMain(m *testing.M) { os.Exit(func() (status int) { // … defer func() { // … termConn, err := net.Dial("tcp", addr) // … }() return m.Run() }()) } Termination

Slide 59

Slide 59 text

Testing with `main` !59 ᶃ go test ./e2e ᶆ go test . ᶇ go main() ᶄ TestMain ᶊ TestEcho Start ᶅ exec.Command Request ᶈ Connect ᶉ m.Run() Launch ᶋ Dial

Slide 60

Slide 60 text

Testing with `main` !60 Termination main_test.go func TestRunMain(t *testing.T) { go main() lis, err := net.Listen("tcp", addr) // … conn, err := lis.Accept() // … } Blocking with `Accept()`

Slide 61

Slide 61 text

Conclusion !61 Testing with `main` gives us …. No redundant setup Covering `main` code

Slide 62

Slide 62 text

Future Work !62 Make termination more easier ɾGet test server pid . from socket (like `lsof -i …`)? . using pgid? ɾSend `SIGTERM` to the process.