Модульное тестирование в Go с использованием Mockery
Вступление
В современной разработке программного обеспечения модульное тестирование необходимо для любого серьезного проекта.
Одна из проблем при написании модульных тестов на Go заключается в том, что наш код взаимодействует с файлами, веб-сайтами или любыми сторонними сервисами.
Mockery – это библиотека, которая помогает нам решать такие проблемы.
Структура файлов
В нашем примере мы хотим имитировать функцию, которая читает файл. Но в нашем тесте мы не хотим читать сам файл.
Во-первых, давайте взглянем на файловую структуру на высоком уровне:
fil е connector.go -> это структура, которая позволит нам читать из файла.
filesystem.go -> это высокоуровневая структура, которая будет использовать файловое соединение.
filesystem_test.go -> файл модульного теста для filesystem.go
Код
fileconnector.go.
package mockingdemo
import "os"
type FileConnector interface{
ReadFromFile(name string) ([]byte, error)
}
type fileConnector struct{}
func NewFileConnector() FileConnector{
return &fileConnector{}
}
func (f*fileConnector) ReadFromFile(filename string) ([]byte, error){
data, err := os.ReadFile(filename)
if err != nil{
return nil, err
}
return data, err
}
Все, что делает файловое соединение – это чтение из файла.
Структура fileConnector реализует интерфейс FileConnector.
Функция NewFileConnector (), возвращаемый тип – это интерфейс, но на самом деле она возвращает структуру fileConnector.
filesystem.go
package mockingdemo
import "fmt"
type FileSystem struct{
conn FileConnector
}
func NewFS() FileSystem{
fs := FileSystem{conn: NewFileConnector()}
return fs
}
func (fs* FileSystem) PrintFileContent(fileName string) error{
fileContent,err := fs.conn.ReadFromFile(fileName)
if err != nil {
return err
}
fmt.Println(string(fileContent))
return nil
}
Установка и использование Mockery
Установка Mockery:
go get github.com/vektra/mockery/v2/…/
Теперь запустите Mockery в том же каталоге, что и файл filesystem.go.
mockery --all
Теперь у вас должен быть файл с именем FileConnector.go в папке mocks.
Содержимое этого файла:
// Code generated by mockery v2.9.4. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
// FileConnector is an autogenerated mock type for the FileConnector type
type FileConnector struct {
mock.Mock
}
// ReadFromFile provides a mock function with given fields: name
func (_m *FileConnector) ReadFromFile(name string) ([]byte, error) {
ret := _m.Called(name)
var r0 []byte
if rf, ok := ret.Get(0).(func(string) []byte); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
Несколько примечаний:
- Все типы интерфейсов, встречающиеся в нашем коде, теперь имитируются. Структура FileConnector реализует наш интерфейс.
- У файла есть собственный пакет с именем mocks.
- Никогда не редактируйте этот файл напрямую.
- Если вы измените код, повторно сгенерируйте макеты с помощью команды, указанной выше.
Использование сгенерированного макета
Тестовый файл:
package mockingdemo
import (
"golangtips/mockingdemo/mocks"
"testing"
)
func TestFileSystem_PrintFileContent(t *testing.T) {
type fields struct {
conn FileConnector
}
const fileToRead = "mockFileToread.txt"
connectorMock := mocks.FileConnector{}
connectorMock.On("ReadFromFile", fileToRead).Return([]byte("hello"),nil)
type args struct {
fileName string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
"test",
fields{&connectorMock},
args{fileName: fileToRead},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fs := &FileSystem{
conn: tt.fields.conn,
}
if err := fs.PrintFileContent(tt.args.fileName); (err != nil) != tt.wantErr {
t.Errorf("PrintFileContent() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
Важная строка:
connectorMock.On("ReadFromFile",fileToRead).Return([]byte("hello"),nil)
Это определяет поведение, которое мы хотим, чтобы макет имел.
Вот и все. Теперь вы знаете, как использовать Mockery для вашего интерфейса, и как использовать его в модульных тестах!