Go语言 使用接口进行模拟以进行测试

fnatzsnv  于 2023-04-18  发布在  Go
关注(0)|答案(2)|浏览(143)

我是刚接触Go语言的,而且我是从OOP语言学来的。现在,在Go语言中,接口和类的概念似乎完全不同。
我想知道在测试的情况下mocking是如何工作的。我的困惑是是否可以使用struct作为一个类,如果下面的方法是你应该怎么做?假设DefaultArticlesRepository是真实的数据,MockArticlesRepository是模拟它。

  1. type ArticlesRepository interface {
  2. GetArticleSections() []ArticleSectionResponse
  3. }
  4. type DefaultArticlesRepository struct{}
  5. type MockArticlesRepository struct{}
  6. func (repository DefaultArticlesRepository) GetArticleSections() []ArticleSectionResponse {
  7. return []ArticleSectionResponse{
  8. {
  9. Title: "Default response",
  10. Tag: "Default Tag",
  11. },
  12. }
  13. }
  14. func (repository MockArticlesRepository) GetArticleSections() []ArticleSectionResponse {
  15. return []ArticleSectionResponse{
  16. {
  17. Title: "Mock response",
  18. Tag: "Mock Tag",
  19. },
  20. }
  21. }
  22. func ArticleSectionsProvider(v ArticlesRepository) ArticlesRepository {
  23. return v
  24. }
  25. func TestFoo(t *testing.T) {
  26. realProvider := ArticleSectionsProvider(DefaultArticlesRepository{})
  27. mockProvider := ArticleSectionsProvider(MockArticlesRepository{})
  28. assert.Equal(t, realProvider.GetArticleSections(), []ArticleSectionResponse{
  29. {
  30. Title: "Default response",
  31. Tag: "Default Tag",
  32. },
  33. })
  34. assert.Equal(t, mockProvider.GetArticleSections(), []ArticleSectionResponse{
  35. {
  36. Title: "Mock response",
  37. Tag: "Mock Tag",
  38. },
  39. })
  40. }
9nvpjoqh

9nvpjoqh1#

首先,我建议你使用https://github.com/vektra/mockery来自动生成基于接口的mock结构体。实现一个像你这样的mock结构体是可以的,但我认为如果你真的不需要一个非常特殊的行为,那只会浪费你的时间和精力。
其次,我们不需要像在代码中那样测试模拟结构体。

  1. assert.Equal(t, mockProvider.GetArticleSections(), []ArticleSectionResponse{
  2. {
  3. Title: "Mock response",
  4. Tag: "Mock Tag",
  5. },
  6. })

因此,当我们使用mock结构体时,假设struct***a***是struct***B***的依赖项。例如:

  1. type A interface {
  2. DoTask() bool
  3. }
  4. type a struct {}
  5. func (sa *a) DoTask() bool {
  6. return true
  7. }
  8. type b struct {
  9. a A
  10. }
  11. func (sb *b) DoSomething() bool {
  12. //Do some logic
  13. sb.a.DoTask();
  14. //Do some logic
  15. return true;
  16. }

而你想测试struct***B***的函数DoSomething,当然你并不关心,也不想在这种情况下测试struct***a***的函数DoTask,那么你只需要在测试中提供一个struct***a***的mock给struct***b***即可,这个mock也可以帮助你在测试struct***b * 时避免处理任何与struct***a*相关的挣扎。.现在你的测试应该是这样的:

  1. func (s *TestSuiteOfStructB) TestDoSomething_NoError() {
  2. //Suppose that mockedOfA is a mock of struct a
  3. instanceOfB := b{a: mockedOfA}
  4. mockedOfA.On("DoTask").Return(true)
  5. actualResult := instanceOfB.DoSomething()
  6. s.Equal(true, actualResult)
  7. }

最后,这只是一件小事,但看不到您的 * ArticleSectionsProvider * 的明确责任。

展开查看全部
ymdaylpp

ymdaylpp2#

首先,不需要使用任何外部mocking库,例如:

您真正需要的只是一个接口和一些使用标准库并行运行所有测试的代码。
检查下面的真实的世界示例,而不是下一个“计算器测试示例”:

  1. ├── api
  2.    ├── storage.go
  3.    ├── server.go
  4.    └── server_test.go
  5. └── main.go

api/storage.go

  1. package api
  2. import "database/sql"
  3. type storage struct {
  4. // any database driver of your choice...
  5. pool *sql.DB
  6. }
  7. func NewStorage(p *sql.DB) *storage {
  8. return &storage{pool: p}
  9. }
  10. func (s *storage) Find() (string, error) {
  11. // use database driver to find from storage...
  12. return `{ "id": "123" }`, nil
  13. }
  14. func (s *storage) Add(v string) error {
  15. // use database driver to add to storage...
  16. return nil
  17. }

api/server.go

  1. package api
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6. type Storage interface {
  7. Find() (string, error)
  8. Add(string) error
  9. }
  10. type server struct {
  11. storage Storage
  12. }
  13. func NewServer(s Storage) *server {
  14. return &server{storage: s}
  15. }
  16. func (s *server) Find(w http.ResponseWriter, r *http.Request) {
  17. response, err := s.storage.Find()
  18. if err != nil {
  19. w.WriteHeader(http.StatusNotFound)
  20. fmt.Fprint(w, `{ "message": "not found" }`)
  21. return
  22. }
  23. fmt.Fprint(w, response)
  24. }
  25. func (s *server) Add(w http.ResponseWriter, r *http.Request) {
  26. query := r.URL.Query()
  27. name := query.Get("name")
  28. if err := s.storage.Add(name); err != nil {
  29. w.WriteHeader(http.StatusNotFound)
  30. fmt.Fprint(w, `{ "message": "not found" }`)
  31. return
  32. }
  33. fmt.Fprint(w, `{ "message": "success" }`)
  34. }

api/server_test.go

  1. package api
  2. import (
  3. "errors"
  4. "net/http"
  5. "net/http/httptest"
  6. "testing"
  7. )
  8. type mock struct {
  9. find func() (string, error)
  10. add func(string) error
  11. }
  12. func (m *mock) Find() (string, error) { return m.find() }
  13. func (m *mock) Add(v string) error { return m.add(v) }
  14. func TestFindOk(t *testing.T) {
  15. t.Parallel()
  16. // Arrange
  17. expectedBody := `{ "message": "ok" }`
  18. expectedStatus := http.StatusOK
  19. m := &mock{find: func() (string, error) { return expectedBody, nil }}
  20. server := NewServer(m)
  21. recorder := httptest.NewRecorder()
  22. // Act
  23. server.Find(recorder, &http.Request{})
  24. // Assert
  25. if recorder.Code != expectedStatus {
  26. t.Errorf("want %d, got %d", expectedStatus, recorder.Code)
  27. }
  28. if recorder.Body.String() != expectedBody {
  29. t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())
  30. }
  31. }
  32. func TestFindNotFound(t *testing.T) {
  33. t.Parallel()
  34. // Arrange
  35. expectedBody := `{ "message": "not found" }`
  36. expectedStatus := http.StatusNotFound
  37. m := &mock{find: func() (string, error) { return expectedBody, errors.New("not found") }}
  38. server := NewServer(m)
  39. recorder := httptest.NewRecorder()
  40. // Act
  41. server.Find(recorder, &http.Request{})
  42. // Assert
  43. if recorder.Code != expectedStatus {
  44. t.Errorf("want %d, got %d", expectedStatus, recorder.Code)
  45. }
  46. if recorder.Body.String() != expectedBody {
  47. t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())
  48. }
  49. }
  50. func TestAddOk(t *testing.T) {
  51. t.Parallel()
  52. // Arrange
  53. expectedBody := `{ "message": "success" }`
  54. expectedStatus := http.StatusOK
  55. m := &mock{add: func(string) error { return nil }}
  56. server := NewServer(m)
  57. recorder := httptest.NewRecorder()
  58. // Act
  59. request, _ := http.NewRequest("GET", "/add?name=mike", nil)
  60. server.Add(recorder, request)
  61. // Assert
  62. if recorder.Code != expectedStatus {
  63. t.Errorf("want %d, got %d", expectedStatus, recorder.Code)
  64. }
  65. if recorder.Body.String() != expectedBody {
  66. t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())
  67. }
  68. }

运行测试

  1. go clean -testcache
  2. go test ./...
展开查看全部

相关问题