Testing

in Golang

Ricardo Longa

Nivelamento sobre testes

/$GOPATH/src/hello/main.go
/$GOPATH/src/hello/calculadora.go
/$GOPATH/src/hello/calculadora_test.go
/$GOPATH/src/hello/main.go
/$GOPATH/src/hello/calculadora.go
/$GOPATH/src/hello/calculadora_test.go
/$GOPATH/src/hello/main.go
/$GOPATH/src/hello/calculadora.go
/$GOPATH/src/hello/calculadora_test.go
/$GOPATH/src/hello/main.go
/$GOPATH/src/hello/calculadora.go
/$GOPATH/src/hello/calculadora_test.go

Arquivos com o sufixo _test.go serão ignorados na fase de build.

package service

type Calculadora struct {}

func (me Calculadora) Soma(a, b int) int {
   return a + b
}
package service

import "testing"

func TestSoma(t *testing.T) {
   calculadora := Calculadora{}

   resultado := calculadora.Soma(1, 2)

   if resultado != 3 {
      t.Errorf("1 + 2 = 3 e não %d.", resultado)
   }
}
package service

import "testing"

func TestSoma(t *testing.T) {
   calculadora := Calculadora{}

   resultado := calculadora.Soma(1, 2)

   if resultado != 3 {
      t.Errorf("1 + 2 = 3 e não %d.", resultado)
   }
}
package service

import "testing"

func TestSoma(t *testing.T) {
   calculadora := Calculadora{}

   resultado := calculadora.Soma(1, 2)

   if resultado != 3 {
      t.Errorf("1 + 2 = 3 e não %d.", resultado)
   }
}
package service

import "testing"

func TestSoma(t *testing.T) {
   calculadora := Calculadora{}

   resultado := calculadora.Soma(1, 2)

   if resultado != 3 {
      t.Errorf("1 + 2 = 3 e não %d.", resultado)
   }
}
package service

import "testing"

func TestSoma(t *testing.T) {
   calculadora := Calculadora{}

   resultado := calculadora.Soma(1, 2)

   if resultado != 3 {
      t.Errorf("1 + 2 = 3 e não %d.", resultado)
   }
}
$GOPATH/src/hello go test

Movies CRUD

func main() {
   session, _ := mgo.Dial(os.Getenv("MONGO_URL"))

   repo := repository.New(session)

   router := gin.New()

   movies := router.Group("/goteca")
   movies.GET("/movies", controller.GetAll(repo))
   movies.GET("/movies/:id", controller.Get(repo))
   movies.POST("/movies", middleware.CheckNewMovie(), controller.Post(repo))
   movies.DELETE("/movies/:id", controller.Delete(repo))

   router.Run()
}
func main() {
   session, _ := mgo.Dial(os.Getenv("MONGO_URL"))

   repo := repository.New(session)

   router := gin.New()

   movies := router.Group("/goteca")
   movies.GET("/movies", controller.GetAll(repo))
   movies.GET("/movies/:id", controller.Get(repo))
   movies.POST("/movies", middleware.CheckNewMovie(), controller.Post(repo))
   movies.DELETE("/movies/:id", controller.Delete(repo))

   router.Run()
}
func main() {
   session, _ := mgo.Dial(os.Getenv("MONGO_URL"))

   repo := repository.New(session)

   router := gin.New()

   movies := router.Group("/goteca")
   movies.GET("/movies", controller.GetAll(repo))
   movies.GET("/movies/:id", controller.Get(repo))
   movies.POST("/movies", middleware.CheckNewMovie(), controller.Post(repo))
   movies.DELETE("/movies/:id", controller.Delete(repo))

   router.Run()
}
func main() {
   session, _ := mgo.Dial(os.Getenv("MONGO_URL"))

   repo := repository.New(session)

   router := gin.New()

   movies := router.Group("/goteca")
   movies.GET("/movies", controller.GetAll(repo))
   movies.GET("/movies/:id", controller.Get(repo))
   movies.POST("/movies", middleware.CheckNewMovie(), controller.Post(repo))
   movies.DELETE("/movies/:id", controller.Delete(repo))

   router.Run()
}
func main() {
   session, _ := mgo.Dial(os.Getenv("MONGO_URL"))

   repo := repository.New(session)

   router := gin.New()

   movies := router.Group("/goteca")
   movies.GET("/movies", controller.GetAll(repo))
   movies.GET("/movies/:id", controller.Get(repo))
   movies.POST("/movies", middleware.CheckNewMovie(), controller.Post(repo))
   movies.DELETE("/movies/:id", controller.Delete(repo))

   router.Run()
}
func main() {
   session, _ := mgo.Dial(os.Getenv("MONGO_URL"))

   repo := repository.New(session)

   router := gin.New()

   movies := router.Group("/goteca")
   movies.GET("/movies", controller.GetAll(repo))
   movies.GET("/movies/:id", controller.Get(repo))
   movies.POST("/movies", middleware.CheckNewMovie(), controller.Post(repo))
   movies.DELETE("/movies/:id", controller.Delete(repo))

   router.Run()
}
func main() {
   session, _ := mgo.Dial(os.Getenv("MONGO_URL"))

   repo := repository.New(session)

   router := gin.New()

   movies := router.Group("/goteca")
   movies.GET("/movies", controller.GetAll(repo))
   movies.GET("/movies/:id", controller.Get(repo))
   movies.POST("/movies", middleware.CheckNewMovie(), controller.Post(repo))
   movies.DELETE("/movies/:id", controller.Delete(repo))

   router.Run()
}
func main() {
   session, _ := mgo.Dial(os.Getenv("MONGO_URL"))

   repo := repository.New(session)

   router := gin.New()

   movies := router.Group("/goteca")
   movies.GET("/movies", controller.GetAll(repo))
   movies.GET("/movies/:id", controller.Get(repo))
   movies.POST("/movies", middleware.CheckNewMovie(), controller.Post(repo))
   movies.DELETE("/movies/:id", controller.Delete(repo))

   router.Run()
}

Unit testing

func CheckNewMovie() gin.HandlerFunc {
   return func(context *gin.Context) {
      movie := &entity.Movie{}

      // Binding ...

      if err := validate(movie); err != nil {
         context.AbortWithError(http.StatusBadRequest, err)
         return
      }

      context.Set("movie", movie)
      context.Next()
   }
}
func CheckNewMovie() gin.HandlerFunc {
   return func(context *gin.Context) {
      movie := &entity.Movie{}

      // Binding ...

      if err := validate(movie); err != nil {
         context.AbortWithError(http.StatusBadRequest, err)
         return
      }

      context.Set("movie", movie)
      context.Next()
   }
}
func validate(movie *entity.Movie) error {
   if movie.Category == "" {
      return errors.New("Category is required.")
   }

   return nil
}
package middleware

import (
    "testing"
    "github.com/ricardolonga/goteca/entity"
)

func Test_InvalidMovie(t *testing.T) {
    movie := &entity.Movie{ Name: "Batman" }

    err := validate(movie)
    if err == nil || err.Error() != "Category is required." {
        t.Fail()
    }
}
package middleware

import (
    "testing"
    "github.com/ricardolonga/goteca/entity"
)

func Test_InvalidMovie(t *testing.T) {
    movie := &entity.Movie{ Name: "Batman" }

    err := validate(movie)
    if err == nil || err.Error() != "Category is required." {
        t.Fail()
    }
}

Assertions with Testify

package middleware

import (
    "testing"
    "github.com/ricardolonga/goteca/entity"
)

func Test_InvalidMovie(t *testing.T) {
    movie := &entity.Movie{ Name: "Batman" }

    err := validate(movie)
    if err == nil || err.Error() != "Category is required." {
        t.Fail()
    }
}
package middleware

import (
    "testing" "github.com/ricardolonga/goteca/entity"
    "github.com/stretchr/testify/assert"
)

func Test_InvalidMovie(t *testing.T) {
    movie := &entity.Movie{ Name: "Batman" }

    err := validate(movie)
    assert.NotNil(t, err)
    assert.Equal(t, "Category is required.", err.Error())
}
package middleware

import (
    "testing" "github.com/ricardolonga/goteca/entity"
    . "github.com/stretchr/testify/assert"
)

func Test_InvalidMovie(t *testing.T) {
    movie := &entity.Movie{ Name: "Batman" }

    err := validate(movie)
    NotNil(t, err)
    Equal(t, "Category is required.", err.Error())
}

API Testing

Podemos facilmente testar APIs Rest utilizando os pacotes net/http e net/http/httptest.

Criamos um http.NewRequest(), um httptest.NewRecorder() e passamos para ServeHTTP(res, req) do Gin.

Mas não queremos testar nosso repositório. Criaremos um mock.

func Post(repository repository.Repository) gin.HandlerFunc {
   return func(context *gin.Context) {
      movie, _ := context.Get("movie")

      savedMovie, err := repository.Save("movies", movie)
      if err != nil {
         context.AbortWithError(http.StatusInternalServerError, err)
         return
      }

      context.JSON(http.StatusOK, savedMovie)
   }
}
func Post(repository repository.Repository) gin.HandlerFunc {
   return func(context *gin.Context) {
      movie, _ := context.Get("movie")

      savedMovie, err := repository.Save("movies", movie)
      if err != nil {
         context.AbortWithError(http.StatusInternalServerError, err)
         return
      }

      context.JSON(http.StatusOK, savedMovie)
   }
}
func Post(repository repository.Repository) gin.HandlerFunc {
   return func(context *gin.Context) {
      movie, _ := context.Get("movie")

      savedMovie, err := repository.Save("movies", movie)
      if err != nil {
         context.AbortWithError(http.StatusInternalServerError, err)
         return
      }

      context.JSON(http.StatusOK, savedMovie)
   }
}
func Post(repository repository.Repository) gin.HandlerFunc {
   return func(context *gin.Context) {
      movie, _ := context.Get("movie")

      savedMovie, err := repository.Save("movies", movie)
      if err != nil {
         context.AbortWithError(http.StatusInternalServerError, err)
         return
      }

      context.JSON(http.StatusOK, savedMovie)
   }
}
func Post(repository repository.Repository) gin.HandlerFunc {
   return func(context *gin.Context) {
      movie, _ := context.Get("movie")

      savedMovie, err := repository.Save("movies", movie)
      if err != nil {
         context.AbortWithError(http.StatusInternalServerError, err)
         return
      }

      context.JSON(http.StatusOK, savedMovie)
   }
}
package repository

type Repository interface {
   Save(collection string, object interface{}) (interface{}, error)
   FindAll(collection string) ([]interface{}, error)
   Find(collection string, id string) (interface{}, error)
   Delete(collection string, id string) error
}
func main() {
   session, _ := mgo.Dial(os.Getenv("MONGO_URL"))

   repo := repository.New(session)

   router := gin.New()

   movies := router.Group("/goteca")
   movies.GET("/movies", controller.GetAll(repo))
   movies.GET("/movies/:id", controller.Get(repo))
   movies.POST("/movies", middleware.CheckNewMovie(), controller.Post(repo))
   movies.DELETE("/movies/:id", controller.Delete(repo))

   router.Run()
}
func Test_validMovie(t *testing.T) {
     router := gin.New()
     router.POST("/goteca/movies", middleware.CheckNewMovie(), controller.Post(&MockRepository{T: t}))

     res := httptest.NewRecorder()
     validMovieBytes := []byte("{ \"name\": \"Man of Fire\", \"category\": \"Action\" }")
     req, _ := http.NewRequest("POST", "/goteca/movies", strings.NewReader(string(validMovieBytes)))
     router.ServeHTTP(res, req)

     assert.Equal(t, http.StatusOK, res.Code)

     body, err := ioutil.ReadAll(res.Body)
     assert.Nil(t, err)
     assert.NotNil(t, body)
}
func Test_validMovie(t *testing.T) {
     router := gin.New()
     router.POST("/goteca/movies", middleware.CheckNewMovie(), controller.Post(&MockRepository{T: t}))

     res := httptest.NewRecorder()
     validMovieBytes := []byte("{ \"name\": \"Man of Fire\", \"category\": \"Action\" }")
     req, _ := http.NewRequest("POST", "/goteca/movies", strings.NewReader(string(validMovieBytes)))
     router.ServeHTTP(res, req)

     assert.Equal(t, http.StatusOK, res.Code)

     body, err := ioutil.ReadAll(res.Body)
     assert.Nil(t, err)
     assert.NotNil(t, body)
}
func Test_validMovie(t *testing.T) {
     router := gin.New()
     router.POST("/goteca/movies", middleware.CheckNewMovie(), controller.Post(&MockRepository{T: t}))

     res := httptest.NewRecorder()
     validMovieBytes := []byte("{ \"name\": \"Man of Fire\", \"category\": \"Action\" }")
     req, _ := http.NewRequest("POST", "/goteca/movies", strings.NewReader(string(validMovieBytes)))
     router.ServeHTTP(res, req)

     assert.Equal(t, http.StatusOK, res.Code)

     body, err := ioutil.ReadAll(res.Body)
     assert.Nil(t, err)
     assert.NotNil(t, body)
}
func Test_validMovie(t *testing.T) {
     router := gin.New()
     router.POST("/goteca/movies", middleware.CheckNewMovie(), controller.Post(&MockRepository{T: t}))

     res := httptest.NewRecorder()
     validMovieBytes := []byte("{ \"name\": \"Man of Fire\", \"category\": \"Action\" }")
     req, _ := http.NewRequest("POST", "/goteca/movies", strings.NewReader(string(validMovieBytes)))
     router.ServeHTTP(res, req)

     assert.Equal(t, http.StatusOK, res.Code)

     body, err := ioutil.ReadAll(res.Body)
     assert.Nil(t, err)
     assert.NotNil(t, body)
}
func Test_validMovie(t *testing.T) {
     router := gin.New()
     router.POST("/goteca/movies", middleware.CheckNewMovie(), controller.Post(&MockRepository{T: t}))

     res := httptest.NewRecorder()
     validMovieBytes := []byte("{ \"name\": \"Man of Fire\", \"category\": \"Action\" }")
     req, _ := http.NewRequest("POST", "/goteca/movies", strings.NewReader(string(validMovieBytes)))
     router.ServeHTTP(res, req)

     assert.Equal(t, http.StatusOK, res.Code)

     body, err := ioutil.ReadAll(res.Body)
     assert.Nil(t, err)
     assert.NotNil(t, body)
}
type MockRepository struct {
   T *testing.T
}

func (me *MockRepository) Save(collection string, object interface{}) (interface{}, error) {
   assert.Equal(me.T, "movies", collection)
   movie := object.(*entity.Movie)
   movie.Id = "1"
   return object, nil
}

func (me *MockRepository) FindAll(collection string) ([]interface{}, error) {
   assert.Fail(me.T, "Nao deveria ter chamado este metodo...")
   return nil, nil
}
type MockRepository struct {
   T *testing.T
}

func (me *MockRepository) Save(collection string, object interface{}) (interface{}, error) {
   assert.Equal(me.T, "movies", collection)
   movie := object.(*entity.Movie)
   movie.Id = "1"
   return object, nil
}

func (me *MockRepository) FindAll(collection string) ([]interface{}, error) {
   assert.Fail(me.T, "Nao deveria ter chamado este metodo...")
   return nil, nil
}
type MockRepository struct {
   T *testing.T
}

func (me *MockRepository) Save(collection string, object interface{}) (interface{}, error) {
   assert.Equal(me.T, "movies", collection)
   movie := object.(*entity.Movie)
   movie.Id = "1"
   return object, nil
}

func (me *MockRepository) FindAll(collection string) ([]interface{}, error) {
   assert.Fail(me.T, "Nao deveria ter chamado este metodo...")
   return nil, nil
}

BDD with Ginkgo

$GOPATH/src/goteca/middleware ginkgo bootstrap
.../middleware/middleware_suite_test.go
package middleware_test

import (
   . "github.com/onsi/ginkgo"
   . "github.com/onsi/gomega"

   "testing"
)

func TestMiddleware(t *testing.T) {
   RegisterFailHandler(Fail)
   RunSpecs(t, "Middleware Suite")
}
package middleware_test

import (
   . "github.com/onsi/ginkgo"
   . "github.com/onsi/gomega"

   "testing"
)

func TestMiddleware(t *testing.T) {
   RegisterFailHandler(Fail)
   RunSpecs(t, "Middleware Suite")
}
$GOPATH/src/goteca/middleware ginkgo generate xxx
.../middleware/xxx_test.go
package middleware_test

import (
   . "github.com/ricardolonga/goteca/middleware"

   . "github.com/onsi/ginkgo"
   . "github.com/onsi/gomega"
)

var _ = Describe("Xxx", func() {

})
var _ = Describe("Check middlewares", func() {
   var movie *entity.Movie

   BeforeEach(func() {
      movie = &entity.Movie{ Name: "Batman" }
   })

   Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
      // ...
   })
})
var _ = Describe("Check middlewares", func() {
   var movie *entity.Movie

   BeforeEach(func() {
      movie = &entity.Movie{ Name: "Batman" }
   })

   Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
      // ...
   })
})
var _ = Describe("Check middlewares", func() {
   var movie *entity.Movie

   BeforeEach(func() {
      movie = &entity.Movie{ Name: "Batman" }
   })

   Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
      // ...
   })
})
var _ = Describe("Check middlewares", func() {
   var movie *entity.Movie

   BeforeEach(func() {
      movie = &entity.Movie{ Name: "Batman" }
   })

   Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
      // ...
   })
})
Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
   Context("QUANDO ele não informar a categoria", func() {
      It("ENTAO a mensagem de erro deve ser 'Category is required.'", func() {
         Expect(validate(movie).Error()).To(Equal("Category is required."))
      })
   })
   Context("QUANDO ele informar a categoria", func() {
      It("ENTAO o filme deve ser valido.", func() {
         movie.Category = "Action"
         Expect(validate(movie)).To(BeNil())
      })
   })
})
Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
   Context("QUANDO ele não informar a categoria", func() {
      It("ENTAO a mensagem de erro deve ser 'Category is required.'", func() {
         Expect(validate(movie).Error()).To(Equal("Category is required."))
      })
   })
   Context("QUANDO ele informar a categoria", func() {
      It("ENTAO o filme deve ser valido.", func() {
         movie.Category = "Action"
         Expect(validate(movie)).To(BeNil())
      })
   })
})
Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
   Context("QUANDO ele não informar a categoria", func() {
      It("ENTAO a mensagem de erro deve ser 'Category is required.'", func() {
         Expect(validate(movie).Error()).To(Equal("Category is required."))
      })
   })
   Context("QUANDO ele informar a categoria", func() {
      It("ENTAO o filme deve ser valido.", func() {
         movie.Category = "Action"
         Expect(validate(movie)).To(BeNil())
      })
   })
})
Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
   Context("QUANDO ele não informar a categoria", func() {
      It("ENTAO a mensagem de erro deve ser 'Category is required.'", func() {
         Expect(validate(movie).Error()).To(Equal("Category is required."))
      })
   })
   Context("QUANDO ele informar a categoria", func() {
      It("ENTAO o filme deve ser valido.", func() {
         movie.Category = "Action"
         Expect(validate(movie)).To(BeNil())
      })
   })
})
Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
   Context("QUANDO ele não informar a categoria", func() {
      It("ENTAO a mensagem de erro deve ser 'Category is required.'", func() {
         Expect(validate(movie).Error()).To(Equal("Category is required."))
      })
   })
   Context("QUANDO ele informar a categoria", func() {
      It("ENTAO o filme deve ser válido.", func() {
         movie.Category = "Action"
         Expect(validate(movie)).To(BeNil())
      })
   })
})
$GOPATH/src/goteca go test
$GOPATH/src/goteca ginkgo
$GOPATH/src/goteca ginkgo -succinct watch -r
Running Suite: Middleware Suite
===============================
Random Seed: 1477911582
Will run 2 of 2 specs

••
Ran 2 of 2 Specs in 0.000 seconds
SUCCESS! -- 2 Passed | 0 Failed | 0 Pending | 0 Skipped PASS

Ginkgo ran 1 suite in 1.553796512s
Test Suite Passed
Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
   Context("QUANDO ele não informar a categoria", func() {
      It("ENTAO a mensagem de erro deve ser 'Category is required.'", func() {
         Expect(validate(movie).Error()).To(Equal("Category is required."))
      })
   })
   Context("QUANDO ele informar a categoria", func() {
      It("ENTAO o filme deve ser valido.", func() {
         movie.Category = "Action"
         Expect(validate(movie)).To(BeNil())
      })
   })
})
Describe("DADO que o usuário deseja cadastrar um novo filme", func() {
   Context("QUANDO ele não informar a categoria", func() {
      It("ENTAO a mensagem de erro deve ser 'Category is required.'", func() {
         Expect(validate(movie).Error()).To(Equal("Category is REQUIRED."))
      })
   })
   Context("QUANDO ele informar a categoria", func() {
      It("ENTAO o filme deve ser valido.", func() {
         movie.Category = "Action"
         Expect(validate(movie)).To(BeNil())
      })
   })
})
$GOPATH/src/goteca ginkgo
Summarizing 1 Failure:

[Fail] Check middlewares DADO que o usuário deseja cadastrar um novo filme QUANDO ele não informar a categoria [It] ENTAO a mensagem de erro deve ser 'Category is required.'
/home/longa/.../goteca/middleware/check_new_movie_ginkgo_test.go:26

Ran 2 of 2 Specs in 0.001 seconds
FAIL! -- 1 Passed | 1 Failed | 0 Pending | 0 Skipped --- FAIL: TestMiddleware (0.00s)
FAIL

Ginkgo ran 1 suite in 1.594353315s
Test Suite Failed
Summarizing 1 Failure:

[Fail] Check middlewares DADO que o usuário deseja cadastrar um novo filme QUANDO ele não informar a categoria [It] ENTAO a mensagem de erro deve ser 'Category is required.'
/home/longa/.../goteca/middleware/check_new_movie_ginkgo_test.go:26

Ran 2 of 2 Specs in 0.001 seconds
FAIL! -- 1 Passed | 1 Failed | 0 Pending | 0 Skipped --- FAIL: TestMiddleware (0.00s)
FAIL

Ginkgo ran 1 suite in 1.594353315s
Test Suite Failed

Assertions with Gomega

gomega.RegisterFailHandler(ginkgo.Fail)

O ginkgo bootstrap já configura o Gomega

automaticamente.

Expect(ACTUAL).To(BeNil())
Expect(ACTUAL).To(BeEmpty())
Expect(ACTUAL).NotTo(Equal(EXPECTED))
Ω(result).Should(Equal("foo"))
Ω(result).To(BeTrue())

err := DoSomethingSimple()
Ω(err).ShouldNot(HaveOccurred()) 
#or...
Ω(DoSomethingSimple()).Should(Succeed())

A DSL do Gomega

func DoSomethingHard() (string, error) {
    // ...
}

result, err := DoSomethingHard()
Ω(err).ShouldNot(HaveOccurred())
Ω(result).Should(Equal("foo"))

 

Ω(DoSomethingHard()).Should(Equal("foo"))
This will only pass if the return value of DoSomethingHard() is ("foo", nil).

Dica: Um atalho...

func DoSomethingHard() (string, error) {
    // ...
}

result, err := DoSomethingHard()
Ω(err).ShouldNot(HaveOccurred())
Ω(result).Should(Equal("foo"))

 

Ω(DoSomethingHard()).Should(Equal("foo"))
This will only pass if the return value of DoSomethingHard() is ("foo", nil).

Dica: Um atalho...

func DoSomethingHard() (string, error) {
    // ...
}

result, err := DoSomethingHard()
Ω(err).ShouldNot(HaveOccurred())
Ω(result).Should(Equal("foo"))

 

Ω(DoSomethingHard()).Should(Equal("foo"))
This will only pass if the return value of DoSomethingHard() is ("foo", nil).

Dica: Um atalho...

Using Gomega with Golang’s XUnit-style Tests

func TestFarmHasCow(t *testing.T) {
    RegisterTestingT(t)

    f := farm.New([]string{"Cow", "Horse"})
    Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
}
func TestFarmHasCow(t *testing.T) {
    RegisterTestingT(t)

    f := farm.New([]string{"Cow", "Horse"})
    Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
}

BDD with GoConvey

$GOPATH/src/goteca/middleware goconvey
func TestMovieCRUD(t *testing.T) {
   Convey("DADO que o usuário deseja cadastrar um novo filme", t, func() {
      Convey("QUANDO ele não informar a categoria", func() {
         Convey("ENTAO a mensagem de erro deve ser 'Category is required", nil)
      })

      Convey("QUANDO ele informar a categoria", func() {
         Convey("ENTAO o filme deve ser valido", nil)
      })
   })

   Convey("DADO que o usuário deseja excluir um filme", t, nil)
}
func TestMovieCRUD(t *testing.T) {
   Convey("DADO que o usuário deseja cadastrar um novo filme", t, func() {
      Convey("QUANDO ele não informar a categoria", func() {
         Convey("ENTAO a mensagem de erro deve ser 'Category is required", nil)
      })

      Convey("QUANDO ele informar a categoria", func() {
         Convey("ENTAO o filme deve ser valido", nil)
      })
   })

   Convey("DADO que o usuário deseja excluir um filme", t, nil)
}
func TestMovieCRUD(t *testing.T) {
   Convey("DADO que o usuário deseja cadastrar um novo filme", t, func() {
      Convey("QUANDO ele não informar a categoria", func() {
         Convey("ENTAO a mensagem de erro deve ser 'Category is required", nil)
      })

      Convey("QUANDO ele informar a categoria", func() {
         Convey("ENTAO o filme deve ser valido", nil)
      })
   })

   Convey("DADO que o usuário deseja excluir um filme", t, nil)
}
func TestMovieCRUD(t *testing.T) {
   Convey("DADO que o usuário deseja cadastrar um novo filme", t, func() {
      Convey("QUANDO ele não informar a categoria", func() {
         Convey("ENTAO a mensagem de erro deve ser 'Category is required", nil)
      })

      Convey("QUANDO ele informar a categoria", func() {
         Convey("ENTAO o filme deve ser valido", nil)
      })
   })

   Convey("DADO que o usuário deseja excluir um filme", t, nil)
}
func TestMovieCRUD(t *testing.T) {
   Convey("DADO que o usuário deseja cadastrar um novo filme", t, func() {
      Convey("QUANDO ele não informar a categoria", func() {
         Convey("ENTAO a mensagem de erro deve ser 'Category is required", nil)
      })

      Convey("QUANDO ele informar a categoria", func() {
         Convey("ENTAO o filme deve ser valido", nil)
      })
   })

   Convey("DADO que o usuário deseja excluir um filme", t, nil)
}
func TestMovieCRUD(t *testing.T) {
   Convey("DADO que o usuário deseja cadastrar um novo filme", t, func() {
      Convey("QUANDO ele não informar a categoria", func() {
         Convey("ENTAO a mensagem de erro deve ser 'Category is required", func() {
         })
      })

      Convey("QUANDO ele informar a categoria", func() {
         Convey("ENTAO o filme deve ser valido", nil)
      })
   })

   Convey("DADO que o usuário deseja excluir um filme", t, nil)
}
func TestMovieCRUD(t *testing.T) {
   Convey("DADO que o usuário deseja cadastrar um novo filme", t, func() {
      Convey("QUANDO ele não informar a categoria", func() {
         Convey("ENTAO a mensagem de erro deve ser 'Category is required", func() {
         })
      })

      Convey("QUANDO ele informar a categoria", func() {
         Convey("ENTAO o filme deve ser valido", nil)
      })
   })

   Convey("DADO que o usuário deseja excluir um filme", t, nil)
}
func TestMovieCRUD(t *testing.T) {
   Convey("DADO que o usuário deseja cadastrar um novo filme", t, func() {
      Convey("QUANDO ele não informar a categoria", func() {
         Convey("ENTAO a mensagem de erro deve ser 'Category is required", func() {
         })
      })

      Convey("QUANDO ele informar a categoria", func() {
         Convey("ENTAO o filme deve ser valido", func() {
         })
      })
   })

   Convey("DADO que o usuário deseja excluir um filme", t, nil)
}
   Convey("DADO que o usuário deseja cadastrar um novo filme", t, func() {
      Convey("QUANDO ele não informar a categoria", func() {
         movie := &entity.Movie{ Name: "Batman" }
         Convey("ENTAO a mensagem de erro deve ser 'Category is required", func() {
            err := validate(movie)
            So(err, ShouldNotBeNil)
            So(err.Error(), ShouldEqual, "Category is required.")
         })
      })
      Convey("QUANDO ele informar a categoria", func() {
         movie := &entity.Movie{ Name: "Batman", Category: "Action" }
         Convey("ENTAO o filme deve ser valido", func() {
            err := validate(movie)
            So(err, ShouldBeNil)
         })
   Convey("DADO que o usuário deseja cadastrar um novo filme", t, func() {
      Convey("QUANDO ele não informar a categoria", func() {
         movie := &entity.Movie{ Name: "Batman" }
         Convey("ENTAO a mensagem de erro deve ser 'Category is required", func() {
            err := validate(movie)
            So(err, ShouldNotBeNil)
            So(err.Error(), ShouldEqual, "Category is required.")
         })
      })
      Convey("QUANDO ele informar a categoria", func() {
         movie := &entity.Movie{ Name: "Batman", Category: "Action" }
         Convey("ENTAO o filme deve ser valido", func() {
            err := validate(movie)
            So(err, ShouldBeNil)
         })

Suite with Testify

type CheckMiddlewareSuite struct {
   suite.Suite
   movie *entity.Movie
}
func (suite *CheckMiddlewareSuite) SetupTest() {
   suite.movie = &entity.Movie{ Name: "Batman" }
}
func (suite *CheckMiddlewareSuite) TestInvalidMovie() {
   err := validate(suite.movie)
   suite.NotNil(err)
   suite.Equal("Category is required.", err.Error())
}
func TestCheckMiddlewareSuite(t *testing.T) {
   suite.Run(t, new(CheckMiddlewareSuite))
}
type CheckMiddlewareSuite struct {
   suite.Suite
   movie *entity.Movie
}
func (suite *CheckMiddlewareSuite) SetupTest() {
   suite.movie = &entity.Movie{ Name: "Batman" }
}
func (suite *CheckMiddlewareSuite) TestInvalidMovie() {
   err := validate(suite.movie)
   suite.NotNil(err)
   suite.Equal("Category is required.", err.Error())
}
func TestCheckMiddlewareSuite(t *testing.T) {
   suite.Run(t, new(CheckMiddlewareSuite))
}
type CheckMiddlewareSuite struct {
   suite.Suite
   movie *entity.Movie
}
func (suite *CheckMiddlewareSuite) SetupTest() {
   suite.movie = &entity.Movie{ Name: "Batman" }
}
func (suite *CheckMiddlewareSuite) TestInvalidMovie() {
   err := validate(suite.movie)
   suite.NotNil(err)
   suite.Equal("Category is required.", err.Error())
}
func TestCheckMiddlewareSuite(t *testing.T) {
   suite.Run(t, new(CheckMiddlewareSuite))
}
type CheckMiddlewareSuite struct {
   suite.Suite
   movie *entity.Movie
}
func (suite *CheckMiddlewareSuite) SetupTest() {
   suite.movie = &entity.Movie{ Name: "Batman" }
}
func (suite *CheckMiddlewareSuite) TestInvalidMovie() {
   err := validate(suite.movie)
   suite.NotNil(err)
   suite.Equal("Category is required.", err.Error())
}
func TestCheckMiddlewareSuite(t *testing.T) {
   suite.Run(t, new(CheckMiddlewareSuite))
}
type CheckMiddlewareSuite struct {
   suite.Suite
   movie *entity.Movie
}
func (suite *CheckMiddlewareSuite) SetupTest() {
   suite.movie = &entity.Movie{ Name: "Batman" }
}
func (suite *CheckMiddlewareSuite) TestInvalidMovie() {
   err := validate(suite.movie)
   suite.NotNil(err)
   suite.Equal("Category is required.", err.Error())
}
func TestCheckMiddlewareSuite(t *testing.T) {
   suite.Run(t, new(CheckMiddlewareSuite))
}
type CheckMiddlewareSuite struct {
   suite.Suite
   movie *entity.Movie
}
func (suite *CheckMiddlewareSuite) SetupTest() {
   suite.movie = &entity.Movie{ Name: "Batman" }
}
func (suite *CheckMiddlewareSuite) TestInvalidMovie() {
   err := validate(suite.movie)
   suite.NotNil(err)
   suite.Equal("Category is required.", err.Error())
}
func TestCheckMiddlewareSuite(t *testing.T) {
   suite.Run(t, new(CheckMiddlewareSuite))
}
type CheckMiddlewareSuite struct {
   suite.Suite
   movie *entity.Movie
}
func (suite *CheckMiddlewareSuite) SetupTest() {
   suite.movie = &entity.Movie{ Name: "Batman" }
}
func (suite *CheckMiddlewareSuite) TestInvalidMovie() {
   err := validate(suite.movie)
   suite.NotNil(err)
   suite.Equal("Category is required.", err.Error())
}
func TestCheckMiddlewareSuite(t *testing.T) {
   suite.Run(t, new(CheckMiddlewareSuite))
}
type CheckMiddlewareSuite struct {
   suite.Suite
   movie *entity.Movie
}
func (suite *CheckMiddlewareSuite) SetupTest() {
   suite.movie = &entity.Movie{ Name: "Batman" }
}
func (suite *CheckMiddlewareSuite) TestInvalidMovie() {
   err := validate(suite.movie)
   suite.NotNil(err)
   suite.Equal("Category is required.", err.Error())
}
func TestCheckMiddlewareSuite(t *testing.T) {
   suite.Run(t, new(CheckMiddlewareSuite))
}
type CheckMiddlewareSuite struct {
   suite.Suite
   movie *entity.Movie
}
func (suite *CheckMiddlewareSuite) SetupTest() {
   suite.movie = &entity.Movie{ Name: "Batman" }
}
func (suite *CheckMiddlewareSuite) TestInvalidMovie() {
   err := validate(suite.movie)
   suite.NotNil(err)
   suite.Equal("Category is required.", err.Error())
}
func TestCheckMiddlewareSuite(t *testing.T) {
   suite.Run(t, new(CheckMiddlewareSuite))
}
func (suite *CheckMiddlewareSuite) SetupSuite() {}
func (suite *CheckMiddlewareSuite) TearDownTest() {}
func (suite *CheckMiddlewareSuite) TearDownSuite() {}

Mock with Testify

type Service interface {
   GetLanguages(apps []string) (map[string]map[string]string, *Error)
}
$GOPATH/src/goteca mockery -name=Service
$GOPATH/src/goteca/mocks/Service.go
type Service struct {
   mock.Mock
}

func (_m *Service) GetLanguages(apps []string) (map[string]map[string]string, *gna.Error) {
   ret := _m.Called(apps)
   // Código boilerplate...
   return r0, r1
}

var _ gna.Service = (*Service)(nil)
func (suite *EngineTestSuite) SetupTest() {
   suite.service = new(mocks.Service)
   suite.handler = NewEngine(suite.service).Handler()
}
func (suite *EngineTestSuite) TestGetLanguages() {
   ptbr := map[string]string{"produto": "produto", "pesquisar": "pesquisar"}
   dicts := map[string]map[string]string{"pt_br": ptbr}
   suite.service.On("GetLanguages", []string{"pt_br"}).Return(dicts, nil)
   r, _ := http.NewRequest("GET", "/i18n/languages?apps=pt_br,en_us", nil)
   w := httptest.NewRecorder()
   suite.handler.ServeHTTP(w, r)
   e, _ := json.Marshal(dicts)
   suite.Equal(string(e)+"\n", w.Body.String())
   suite.service.AssertExpectations(suite.T())
}
func (suite *EngineTestSuite) SetupTest() {
   suite.service = new(mocks.Service)
   suite.handler = NewEngine(suite.service).Handler()
}
func (suite *EngineTestSuite) TestGetLanguages() {
   ptbr := map[string]string{"produto": "produto", "pesquisar": "pesquisar"}
   dicts := map[string]map[string]string{"pt_br": ptbr}
   suite.service.On("GetLanguages", []string{"pt_br"}).Return(dicts, nil)
   r, _ := http.NewRequest("GET", "/i18n/languages?apps=pt_br,en_us", nil)
   w := httptest.NewRecorder()
   suite.handler.ServeHTTP(w, r)
   e, _ := json.Marshal(dicts)
   suite.Equal(string(e)+"\n", w.Body.String())
   suite.service.AssertExpectations(suite.T())
}
func (suite *EngineTestSuite) SetupTest() {
   suite.service = new(mocks.Service)
   suite.handler = NewEngine(suite.service).Handler()
}
func (suite *EngineTestSuite) TestGetLanguages() {
   ptbr := map[string]string{"produto": "produto", "pesquisar": "pesquisar"}
   dicts := map[string]map[string]string{"pt_br": ptbr}
   suite.service.On("GetLanguages", []string{"pt_br"}).Return(dicts, nil)
   r, _ := http.NewRequest("GET", "/i18n/languages?apps=pt_br,en_us", nil)
   w := httptest.NewRecorder()
   suite.handler.ServeHTTP(w, r)
   e, _ := json.Marshal(dicts)
   suite.Equal(string(e)+"\n", w.Body.String())
   suite.service.AssertExpectations(suite.T())
}
func (suite *EngineTestSuite) SetupTest() {
   suite.service = new(mocks.Service)
   suite.handler = NewEngine(suite.service).Handler()
}
func (suite *EngineTestSuite) TestGetLanguages() {
   ptbr := map[string]string{"produto": "produto", "pesquisar": "pesquisar"}
   dicts := map[string]map[string]string{"pt_br": ptbr}
   suite.service.On("GetLanguages", []string{"pt_br"}).Return(dicts, nil)
   r, _ := http.NewRequest("GET", "/i18n/languages?apps=pt_br,en_us", nil)
   w := httptest.NewRecorder()
   suite.handler.ServeHTTP(w, r)
   e, _ := json.Marshal(dicts)
   suite.Equal(string(e)+"\n", w.Body.String())
   suite.service.AssertExpectations(suite.T())
}
func (suite *EngineTestSuite) SetupTest() {
   suite.service = new(mocks.Service)
   suite.handler = NewEngine(suite.service).Handler()
}
func (suite *EngineTestSuite) TestGetLanguages() {
   ptbr := map[string]string{"produto": "produto", "pesquisar": "pesquisar"}
   dicts := map[string]map[string]string{"pt_br": ptbr}
   suite.service.On("GetLanguages", []string{"pt_br"}).Return(dicts, nil)
   r, _ := http.NewRequest("GET", "/i18n/languages?apps=pt_br,en_us", nil)
   w := httptest.NewRecorder()
   suite.handler.ServeHTTP(w, r)
   e, _ := json.Marshal(dicts)
   suite.Equal(string(e)+"\n", w.Body.String())
   suite.service.AssertExpectations(suite.T())
}
func (suite *EngineTestSuite) SetupTest() {
   suite.service = new(mocks.Service)
   suite.handler = NewEngine(suite.service).Handler()
}
func (suite *EngineTestSuite) TestGetLanguages() {
   ptbr := map[string]string{"produto": "produto", "pesquisar": "pesquisar"}
   dicts := map[string]map[string]string{"pt_br": ptbr}
   suite.service.On("GetLanguages", []string{"pt_br"}).Return(dicts, nil)
   r, _ := http.NewRequest("GET", "/i18n/languages?apps=pt_br,en_us", nil)
   w := httptest.NewRecorder()
   suite.handler.ServeHTTP(w, r)
   e, _ := json.Marshal(dicts)
   suite.Equal(string(e)+"\n", w.Body.String())
   suite.service.AssertExpectations(suite.T())
}

Acceptance with Agouti

Primeiro vamos iniciar o Selenium Standalone Server

docker run -d --name selenium --net=host -P selenium/standalone-chrome
#or...
java -jar selenium-server-standalone-2.53.0.jar

Instalando o Agouti

go get github.com/sclevine/agouti

Uma navegação com Agouti:

page, _ := agouti.NewPage("http://localhost:4444/wd/hub", agouti.Browser("chrome"))
page.Navigate("https://www.google.com.br/")
page.Find("input[name=\"txtSenha\"]").Fill(password)
page.Find(".botaoVermelho").Click()
page.FindByID("txtCPF").Fill(document)
page.Find("input.loginOk:nth-child(4)").Click()

Próximos passos

O projeto de exemplo

Testing in Golang

By Ricardo Longa