diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0d457bc..c2eb90a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -24,7 +24,7 @@ jobs: go-version: '1.24.x' - name: Build - run: go build -v ./... + run: cd good_abstracts; go build -v ./... ; cd .. - name: Test - run: go test -v ./... + run: cd good_abstracts; go test -v ./... ; cd .. diff --git a/README.md b/README.md index 7dbc831..acd9bb4 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,36 @@ -Разработать набор модульных тестов +## Запуск тестов +``` +cd good_abstracts/ +go test ./... -coverprofile coverage.txt +``` + +## Проверка покрытия +``` +go tool cover -html=coverage.txt +``` + + +Движение игровых объектов по полю + ### Цель: -Научиться писать модульные тесты по TDD, так как модульные тесты является важной частью концепции Time To Market и часто применяется в современных проектах. -### Описание +Выработка навыка применения SOLID принципов на примере игры "Космическая битва". -1. Создать проект на gitlab/github. +В результате выполнения ДЗ будет получен код, отвечающий за движение объектов по игровому полю, устойчивый к появлению новых игровых объектов и дополнительных ограничений, накладываемых на это движение. -2. Настроить CI, чтобы можно было собирать проект и прогонять тесты. -Необходимо реализовать операцию нахождения квадратного уравнения. Предположим, что эта операция описывается следующей функцией c поправкой на конкретный язык программирования. В ООП языках эта функция реализуется в виде метода класса. -``` -solve(double a, double b, double c): double[] -``` -здесь a, b, c - коэффициенты квадратного уравнения, функция возвращает список корней квадратного уравнения. - -3. Написать тест, который проверяет, что для уравнения x^2+1 = 0 корней нет (возвращается пустой массив) -4. Написать минимальную реализацию функции solve, которая удовлетворяет данному тесту. -5. Написать тест, который проверяет, что для уравнения x^2-1 = 0 есть два корня кратности 1 (x1=1, x2=-1) -6. Написать минимальную реализацию функции solve, которая удовлетворяет тесту из п.5. -7. Написать тест, который проверяет, что для уравнения x^2+2x+1 = 0 есть один корень кратности 2 (x1= x2 = -1). -8. Написать минимальную реализацию функции solve, которая удовлетворяет тесту из п.7. -9. Написать тест, который проверяет, что коэффициент a не может быть равен 0. В этом случае solve выбрасывает исключение. **Примечание**. Учесть, что a имеет тип double и сравнивать с 0 через == нельзя. -10. Написать минимальную реализацию функции solve, которая удовлетворяет тесту из п.9. -11. С учетом того, что дискриминант тоже нельзя сравнивать с 0 через знак равенства, подобрать такие коэффициенты квадратного уравнения для случая одного корня кратности два, чтобы дискриминант был отличный от нуля, но меньше заданного эпсилон. Эти коэффициенты должны заменить коэффициенты в тесте из п. 7. -12. При необходимости поправить реализацию квадратного уравнения. -13. Посмотреть какие еще значения могут принимать числа типа double, кроме числовых и написать тест с их использованием на все коэффициенты. solve должен выбрасывать исключение. -14. Написать минимальную реализацию функции solve, которая удовлетворяет тесту из п.13. -15. Сделать merge request/pull request и ссылку на него указать при сдаче ДЗ. \ No newline at end of file +### Описание: + +В далекой звездной системе встретились две флотилии космических кораблей. Корабли могут передвигаться по всему пространству звездной системы по прямой, поворачиваться против и по часовой стрелке, стрелять фотонными торпедами. Попадание фотонной торпеды в корабль выводит его из строя. +От каждой флотилии в сражении принимают участие по три космических корабля. +Победу в битве одерживает та флотилия, которая первой выведет из строя все корабли соперника. + +Управление флотилиями осуществляется игрокам компьютерными программами (то есть не с клавиатуры). + +Концептуально игра состоит из трех подсистем: + +1. Игровой сервер, где реализуется вся игровая логика. +2. Player - консольное приложение, на котором отображается конкретная битва. +3. Агент - приложение, которое запускает программу управления корабли от имени игрока и отправляет управляющие команды на игровой сервер. + +Реализовать движение объектов на игровом поле в рамках подсистемы Игровой сервер. \ No newline at end of file diff --git a/coverage.txt b/coverage.txt index f375fbf..83aff0e 100644 --- a/coverage.txt +++ b/coverage.txt @@ -1,4 +1,4 @@ -mode: set +mode: atomic module_test/Hello/hello.go:5.58,6.16 1 1 module_test/Hello/hello.go:6.16,8.3 1 0 module_test/Hello/hello.go:10.2,12.18 2 1 @@ -7,3 +7,14 @@ module_test/Hello/hello.go:15.17,16.18 1 0 module_test/Hello/hello.go:17.16,18.19 1 0 module_test/Hello/hello.go:19.10,20.64 1 0 module_test/Hello/hello.go:23.2,23.33 1 1 +module_test/equasion/solution.go:7.61,10.54 3 8 +module_test/equasion/solution.go:10.54,12.4 1 1 +module_test/equasion/solution.go:13.3,13.63 1 7 +module_test/equasion/solution.go:13.63,15.4 1 1 +module_test/equasion/solution.go:16.3,16.28 1 6 +module_test/equasion/solution.go:16.28,18.4 1 1 +module_test/equasion/solution.go:19.3,20.28 2 5 +module_test/equasion/solution.go:20.28,22.4 1 1 +module_test/equasion/solution.go:22.9,22.39 1 4 +module_test/equasion/solution.go:22.39,25.4 2 3 +module_test/equasion/solution.go:26.3,26.21 1 5 diff --git a/go.mod b/go.mod index 09f0e87..de6c8aa 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,10 @@ module module_test go 1.24.1 +require github.com/stretchr/testify v1.10.0 + require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.10.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fe99d71..713a0b4 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/good_abstracts/actions/actions.go b/good_abstracts/actions/actions.go new file mode 100644 index 0000000..c61bffa --- /dev/null +++ b/good_abstracts/actions/actions.go @@ -0,0 +1,36 @@ +package actions + +import ( + "good_abstracts/adapters" + "good_abstracts/models" +) + +type Move struct { + movingObject adapters.MovingObject +} + +func NewMove(movingObject adapters.MovingObject) *Move { + return &Move{movingObject: movingObject} +} + +func (m *Move) Execute() { + location := m.movingObject.GetLocation() + velocity := m.movingObject.GetVelocity() + + newLoc := location.Add(velocity) + m.movingObject.SetLocation(newLoc) +} + +type Rotate struct { + rotatableObject adapters.RotatableObject +} + +func NewRotate(rotatableObject adapters.RotatableObject) *Rotate { + return &Rotate{rotatableObject: rotatableObject} +} + +func (r *Rotate) Execute(deltaAngle models.Angle) { + angle := r.rotatableObject.GetAngle() + newAngle := angle.Add(deltaAngle) + r.rotatableObject.SetAngle(newAngle) +} diff --git a/good_abstracts/actions/actions_test.go b/good_abstracts/actions/actions_test.go new file mode 100644 index 0000000..8970210 --- /dev/null +++ b/good_abstracts/actions/actions_test.go @@ -0,0 +1,94 @@ +package actions + +import ( + "good_abstracts/models" + "testing" + + "github.com/stretchr/testify/mock" +) + +// MockMovingObject Move +type MockMovingObject struct { + mock.Mock +} + +func (m *MockMovingObject) GetLocation() models.Point { + args := m.Called() + return args.Get(0).(models.Point) +} + +func (m *MockMovingObject) GetVelocity() models.Vector { + args := m.Called() + return args.Get(0).(models.Vector) +} + +func (m *MockMovingObject) SetLocation(newPoint models.Point) { + m.Called(newPoint) +} + +// MockRotatableObject Rotate +type MockRotatableObject struct { + mock.Mock +} + +func (m *MockRotatableObject) GetAngle() models.Angle { + args := m.Called() + return args.Get(0).(models.Angle) +} + +func (m *MockRotatableObject) SetAngle(newAngle models.Angle) { + m.Called(newAngle) +} + +func TestMove(t *testing.T) { + t.Run("Move корректный", func(t *testing.T) { + mockObj := new(MockMovingObject) + + initialLocation := models.Point{X: 10, Y: 20} + velocity := models.Vector{X: 5, Y: -3} + expectedNewLocation := models.Point{X: 15, Y: 17} + + mockObj.On("GetLocation").Return(initialLocation) + mockObj.On("GetVelocity").Return(velocity) + mockObj.On("SetLocation", expectedNewLocation).Once() + + move := NewMove(mockObj) + move.Execute() + + mockObj.AssertExpectations(t) + }) +} + +func TestRotate(t *testing.T) { + t.Run("rotates корректный", func(t *testing.T) { + mockObj := new(MockRotatableObject) + + initialAngle := models.Angle{Degrees: 30} + deltaAngle := models.Angle{Degrees: 15} + expectedNewAngle := models.Angle{Degrees: 45} + + mockObj.On("GetAngle").Return(initialAngle) + mockObj.On("SetAngle", expectedNewAngle).Once() + + rotate := NewRotate(mockObj) + rotate.Execute(deltaAngle) + + mockObj.AssertExpectations(t) + }) + + t.Run("отрицательное вращение", func(t *testing.T) { + mockObj := new(MockRotatableObject) + + initialAngle := models.Angle{Degrees: 45} + deltaAngle := models.Angle{Degrees: -20} + expectedNewAngle := models.Angle{Degrees: 25} + + mockObj.On("GetAngle").Return(initialAngle) + mockObj.On("SetAngle", expectedNewAngle).Once() + + rotate := NewRotate(mockObj) + rotate.Execute(deltaAngle) + + mockObj.AssertExpectations(t) + }) +} diff --git a/good_abstracts/adapters/adapters.go b/good_abstracts/adapters/adapters.go new file mode 100644 index 0000000..49b7b0c --- /dev/null +++ b/good_abstracts/adapters/adapters.go @@ -0,0 +1,65 @@ +package adapters + +import ( + "good_abstracts/models" + "good_abstracts/uobject" + "math" +) + +type MovingObjectAdapter struct { + uobj uobject.UObject +} + +func NewMovingObjectAdapter(uobj *uobject.UObject) *MovingObjectAdapter { + return &MovingObjectAdapter{uobj: *uobj} +} + +func (m *MovingObjectAdapter) GetLocation() models.Point { + prop := m.uobj.GetProperty("location") + if prop == nil { + panic("Не указан location") + } + return prop.(models.Point) +} + +func (m *MovingObjectAdapter) GetVelocity() models.Vector { + angleProp := m.uobj.GetProperty("angle") + velocityProp := m.uobj.GetProperty("velocity") + + if angleProp == nil || velocityProp == nil { + panic("Угол или скорость не указаны") + } + + angle := angleProp.(models.Angle) + velocity := velocityProp.(float64) + + radian := angle.Radians() + vx := int(velocity * math.Cos(radian)) + vy := int(velocity * math.Sin(radian)) + + return models.Vector{X: vx, Y: vy} +} + +func (m *MovingObjectAdapter) SetLocation(newPoint models.Point) { + m.uobj.SetProperty("location", newPoint) +} + +type RotatableObjectAdapter struct { + uobj uobject.UObject +} + +func NewRotatableObjectAdapter(uobj *uobject.UObject) *RotatableObjectAdapter { + return &RotatableObjectAdapter{uobj: *uobj} +} + +func (r *RotatableObjectAdapter) GetAngle() models.Angle { + prop := r.uobj.GetProperty("angle") + if prop == nil { + panic("Уголь не найден") + } + return prop.(models.Angle) +} + +func (r *RotatableObjectAdapter) SetAngle(newAngle models.Angle) { + r.uobj.SetProperty("angle", newAngle) +} diff --git a/good_abstracts/adapters/adapters_test.go b/good_abstracts/adapters/adapters_test.go new file mode 100644 index 0000000..28a8952 --- /dev/null +++ b/good_abstracts/adapters/adapters_test.go @@ -0,0 +1,97 @@ +package adapters + +import ( + "good_abstracts/models" + "good_abstracts/uobject" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMovingObjectAdapter(t *testing.T) { + t.Run("GetLocation возвращает корректное значение", func(t *testing.T) { + obj := uobject.NewUObject() + expected := models.Point{X: 10, Y: 20} + obj.SetProperty("location", expected) + + adapter := NewMovingObjectAdapter(obj) + result := adapter.GetLocation() + + assert.Equal(t, expected, result) + }) + + t.Run("GetLocation panics", func(t *testing.T) { + obj := uobject.NewUObject() + adapter := NewMovingObjectAdapter(obj) + + assert.Panics(t, func() { + adapter.GetLocation() + }) + }) + + t.Run("GetVelocity вычисляет корректный вектор", func(t *testing.T) { + obj := uobject.NewUObject() + obj.SetProperty("angle", models.Angle{Degrees: 0}) + obj.SetProperty("velocity", 10.0) + + adapter := NewMovingObjectAdapter(obj) + result := adapter.GetVelocity() + + assert.Equal(t, models.Vector{X: 10, Y: 0}, result) + }) + + t.Run("GetVelocity и 90 градусов", func(t *testing.T) { + obj := uobject.NewUObject() + obj.SetProperty("angle", models.Angle{Degrees: 90}) + obj.SetProperty("velocity", 5.0) + + adapter := NewMovingObjectAdapter(obj) + result := adapter.GetVelocity() + + assert.Equal(t, models.Vector{X: 0, Y: 5}, result) + }) + + t.Run("SetLocation обновляет значение", func(t *testing.T) { + obj := uobject.NewUObject() + adapter := NewMovingObjectAdapter(obj) + newPoint := models.Point{X: 100, Y: 200} + + adapter.SetLocation(newPoint) + + result := obj.GetProperty("location").(models.Point) + assert.Equal(t, newPoint, result) + }) +} + +func TestRotatableObjectAdapter(t *testing.T) { + t.Run("GetAngle возвраает корректный угол", func(t *testing.T) { + obj := uobject.NewUObject() + expected := models.Angle{Degrees: 45} + obj.SetProperty("angle", expected) + + adapter := NewRotatableObjectAdapter(obj) + result := adapter.GetAngle() + + assert.Equal(t, expected, result) + }) + + t.Run("GetAngle panics", func(t *testing.T) { + obj := uobject.NewUObject() + adapter := NewRotatableObjectAdapter(obj) + + assert.Panics(t, func() { + adapter.GetAngle() + }) + }) + + t.Run("SetAngle обновляет значение", func(t *testing.T) { + obj := uobject.NewUObject() + adapter := NewRotatableObjectAdapter(obj) + newAngle := models.Angle{Degrees: 90} + + adapter.SetAngle(newAngle) + + result := obj.GetProperty("angle").(models.Angle) + assert.Equal(t, newAngle, result) + }) +} diff --git a/good_abstracts/adapters/interfaces.go b/good_abstracts/adapters/interfaces.go new file mode 100644 index 0000000..34a46ac --- /dev/null +++ b/good_abstracts/adapters/interfaces.go @@ -0,0 +1,14 @@ +package adapters + +import "good_abstracts/models" + +type MovingObject interface { + GetLocation() models.Point + GetVelocity() models.Vector + SetLocation(newPoint models.Point) +} + +type RotatableObject interface { + GetAngle() models.Angle + SetAngle(newAngle models.Angle) +} diff --git a/good_abstracts/coverage.txt b/good_abstracts/coverage.txt new file mode 100644 index 0000000..830ee89 --- /dev/null +++ b/good_abstracts/coverage.txt @@ -0,0 +1,41 @@ +mode: atomic +good_abstracts/uobject/uobject.go:7.28,11.2 1 4 +good_abstracts/uobject/uobject.go:13.60,15.2 1 6 +good_abstracts/uobject/uobject.go:17.67,19.2 1 6 +good_abstracts/main.go:11.44,16.2 4 0 +good_abstracts/main.go:18.13,45.2 19 0 +good_abstracts/adapters/adapters.go:13.73,15.2 1 5 +good_abstracts/adapters/adapters.go:17.58,19.17 2 2 +good_abstracts/adapters/adapters.go:19.17,20.38 1 1 +good_abstracts/adapters/adapters.go:22.2,22.28 1 1 +good_abstracts/adapters/adapters.go:25.59,29.45 3 2 +good_abstracts/adapters/adapters.go:29.45,30.64 1 0 +good_abstracts/adapters/adapters.go:33.2,40.36 6 2 +good_abstracts/adapters/adapters.go:43.66,45.2 1 1 +good_abstracts/adapters/adapters.go:51.79,53.2 1 3 +good_abstracts/adapters/adapters.go:55.58,57.17 2 2 +good_abstracts/adapters/adapters.go:57.17,58.40 1 1 +good_abstracts/adapters/adapters.go:60.2,60.28 1 1 +good_abstracts/adapters/adapters.go:63.66,65.2 1 1 +good_abstracts/actions/actions.go:12.56,14.2 1 1 +good_abstracts/actions/actions.go:16.26,22.2 4 1 +good_abstracts/actions/actions.go:28.66,30.2 1 2 +good_abstracts/actions/actions.go:32.51,36.2 3 2 +good_abstracts/models/models.go:9.36,14.2 1 1 +good_abstracts/models/models.go:20.42,25.2 1 1 +good_abstracts/models/models.go:31.34,33.2 1 1 +good_abstracts/models/models.go:35.35,37.20 2 3 +good_abstracts/models/models.go:37.20,39.3 1 1 +good_abstracts/models/models.go:40.2,40.35 1 3 +good_abstracts/models/models.go:43.45,44.27 1 2 +good_abstracts/models/models.go:45.13,46.47 1 1 +good_abstracts/models/models.go:47.11,48.39 1 1 +good_abstracts/models/models.go:49.10,50.83 1 0 +good_abstracts/models/models.go:54.45,55.27 1 2 +good_abstracts/models/models.go:56.13,57.47 1 1 +good_abstracts/models/models.go:58.11,59.39 1 1 +good_abstracts/models/models.go:60.10,61.83 1 0 +good_abstracts/models/models.go:65.46,66.27 1 2 +good_abstracts/models/models.go:67.13,68.32 1 1 +good_abstracts/models/models.go:69.11,70.24 1 1 +good_abstracts/models/models.go:71.10,72.15 1 0 diff --git a/good_abstracts/go.mod b/good_abstracts/go.mod new file mode 100644 index 0000000..60d38b3 --- /dev/null +++ b/good_abstracts/go.mod @@ -0,0 +1,12 @@ +module good_abstracts + +go 1.25.3 + +require github.com/stretchr/testify v1.11.1 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/good_abstracts/go.sum b/good_abstracts/go.sum new file mode 100644 index 0000000..625f862 --- /dev/null +++ b/good_abstracts/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/good_abstracts/main.go b/good_abstracts/main.go new file mode 100644 index 0000000..2b4f7e9 --- /dev/null +++ b/good_abstracts/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "good_abstracts/actions" + "good_abstracts/adapters" + "good_abstracts/models" + "good_abstracts/uobject" +) + +func printShipState(ship *uobject.UObject) { + location := ship.GetProperty("location").(models.Point) + angle := ship.GetProperty("angle").(models.Angle) + velocity := ship.GetProperty("velocity").(float64) + fmt.Printf("Location: %v, Angle: %v, Velocity: %.2f\n", location, angle, velocity) +} + +func main() { + ship := uobject.NewUObject() + ship.SetProperty("location", models.Point{X: 12, Y: 5}) + ship.SetProperty("angle", models.Angle{Degrees: 155}) + ship.SetProperty("velocity", 8.0) // Модуль скорости + + fmt.Println("== Начало игры ==") + printShipState(ship) + + // Движение + moveAdapter := adapters.NewMovingObjectAdapter(ship) + moveAction := actions.NewMove(moveAdapter) + moveAction.Execute() + fmt.Println("\n== После движения ==") + printShipState(ship) + + // Поворот + rotateAdapter := adapters.NewRotatableObjectAdapter(ship) + rotateAction := actions.NewRotate(rotateAdapter) + rotateAction.Execute(models.Angle{Degrees: 10}) + fmt.Println("\n== После поворота на 10° ==") + printShipState(ship) + + // Движение после поворота + moveAction.Execute() + fmt.Println("\n== После движения ==") + printShipState(ship) +} diff --git a/good_abstracts/main_test.go b/good_abstracts/main_test.go new file mode 100644 index 0000000..d0886dc --- /dev/null +++ b/good_abstracts/main_test.go @@ -0,0 +1,53 @@ +package main + +import ( + "good_abstracts/actions" + "good_abstracts/adapters" + "good_abstracts/models" + "good_abstracts/uobject" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIntegration(t *testing.T) { + t.Run("Тестирование сценария движения и поворота", func(t *testing.T) { + // Создаем корабль как в main + ship := uobject.NewUObject() + ship.SetProperty("location", models.Point{X: 12, Y: 5}) + ship.SetProperty("angle", models.Angle{Degrees: 155}) + ship.SetProperty("velocity", 8.0) + + // Проверяем начальное состояние + initialLocation := ship.GetProperty("location").(models.Point) + initialAngle := ship.GetProperty("angle").(models.Angle) + + assert.Equal(t, models.Point{X: 12, Y: 5}, initialLocation) + assert.Equal(t, models.Angle{Degrees: 155}, initialAngle) + + // Двигаем корабль + moveAdapter := adapters.NewMovingObjectAdapter(ship) + moveAction := actions.NewMove(moveAdapter) + moveAction.Execute() + + // Проверяем новое положение + locationAfterMove := ship.GetProperty("location").(models.Point) + assert.Equal(t, models.Point{X: 5, Y: 8}, locationAfterMove) + + // Поворачиваем корабль + rotateAdapter := adapters.NewRotatableObjectAdapter(ship) + rotateAction := actions.NewRotate(rotateAdapter) + rotateAction.Execute(models.Angle{Degrees: 10}) + + // Проверяем новый угол + angleAfterRotate := ship.GetProperty("angle").(models.Angle) + assert.Equal(t, models.Angle{Degrees: 165}, angleAfterRotate) + + // Двигаем снова + moveAction.Execute() + + // Проверяем финальное положение + finalLocation := ship.GetProperty("location").(models.Point) + assert.NotEqual(t, locationAfterMove, finalLocation) + }) +} diff --git a/good_abstracts/models/models.go b/good_abstracts/models/models.go new file mode 100644 index 0000000..11ff4b6 --- /dev/null +++ b/good_abstracts/models/models.go @@ -0,0 +1,74 @@ +package models + +import "math" + +type Point struct { + X, Y int +} + +func (p Point) Add(v Vector) Point { + return Point{ + X: p.X + v.X, + Y: p.Y + v.Y, + } +} + +type Vector struct { + X, Y int +} + +func (v Vector) Add(other Vector) Vector { + return Vector{ + X: v.X + other.X, + Y: v.Y + other.Y, + } +} + +type Angle struct { + Degrees int +} + +func (a Angle) Radians() float64 { + return math.Pi * float64(a.Degrees) / 180.0 +} + +func (a Angle) Normalized() Angle { + normalized := a.Degrees % 360 + if normalized < 0 { + normalized += 360 + } + return Angle{Degrees: normalized} +} + +func (a Angle) Add(other interface{}) Angle { + switch v := other.(type) { + case Angle: + return Angle{Degrees: a.Degrees + v.Degrees} + case int: + return Angle{Degrees: a.Degrees + v} + default: + panic("Неподдерживаемый тип для изменения Angle") + } +} + +func (a Angle) Sub(other interface{}) Angle { + switch v := other.(type) { + case Angle: + return Angle{Degrees: a.Degrees - v.Degrees} + case int: + return Angle{Degrees: a.Degrees - v} + default: + panic("Неподдерживаемый тип для изменения Angle") + } +} + +func (a Angle) Equal(other interface{}) bool { + switch v := other.(type) { + case Angle: + return a.Degrees == v.Degrees + case int: + return a.Degrees == v + default: + return false + } +} diff --git a/good_abstracts/models/models_test.go b/good_abstracts/models/models_test.go new file mode 100644 index 0000000..6c08d08 --- /dev/null +++ b/good_abstracts/models/models_test.go @@ -0,0 +1,109 @@ +package models + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPoint(t *testing.T) { + t.Run("Point Add Vector", func(t *testing.T) { + point := Point{X: 10, Y: 20} + vector := Vector{X: 5, Y: -3} + + result := point.Add(vector) + + assert.Equal(t, Point{X: 15, Y: 17}, result) + }) +} + +func TestVector(t *testing.T) { + t.Run("Vector Add", func(t *testing.T) { + v1 := Vector{X: 3, Y: 4} + v2 := Vector{X: 1, Y: 2} + + result := v1.Add(v2) + + assert.Equal(t, Vector{X: 4, Y: 6}, result) + }) +} + +func TestAngle(t *testing.T) { + t.Run("Конвертируем в радианы", func(t *testing.T) { + angle := Angle{Degrees: 180} + + radians := angle.Radians() + + assert.InEpsilon(t, math.Pi, radians, 0.0001) + }) + + t.Run("Нормализация положительного угла", func(t *testing.T) { + angle := Angle{Degrees: 45} + + normalized := angle.Normalized() + + assert.Equal(t, Angle{Degrees: 45}, normalized) + }) + + t.Run("Нормализация", func(t *testing.T) { + angle := Angle{Degrees: 450} + + normalized := angle.Normalized() + + assert.Equal(t, Angle{Degrees: 90}, normalized) + }) + + t.Run("Нормализация отрицательного угла", func(t *testing.T) { + angle := Angle{Degrees: -90} + + normalized := angle.Normalized() + + assert.Equal(t, Angle{Degrees: 270}, normalized) + }) + + t.Run("Add Angle", func(t *testing.T) { + angle1 := Angle{Degrees: 30} + angle2 := Angle{Degrees: 15} + + result := angle1.Add(angle2) + + assert.Equal(t, Angle{Degrees: 45}, result) + }) + + t.Run("Add int", func(t *testing.T) { + angle := Angle{Degrees: 30} + + result := angle.Add(20) + + assert.Equal(t, Angle{Degrees: 50}, result) + }) + + t.Run("Subtract Angle", func(t *testing.T) { + angle1 := Angle{Degrees: 45} + angle2 := Angle{Degrees: 15} + + result := angle1.Sub(angle2) + + assert.Equal(t, Angle{Degrees: 30}, result) + }) + t.Run("Subtract int", func(t *testing.T) { + angle := Angle{Degrees: 30} + + result := angle.Sub(20) + + assert.Equal(t, Angle{Degrees: 10}, result) + }) + t.Run("Equal Angle", func(t *testing.T) { + angle1 := Angle{Degrees: 45} + angle2 := Angle{Degrees: 45} + + assert.True(t, angle1.Equal(angle2)) + }) + + t.Run("Equal int", func(t *testing.T) { + angle := Angle{Degrees: 45} + + assert.True(t, angle.Equal(45)) + }) +} diff --git a/good_abstracts/uobject/uobject.go b/good_abstracts/uobject/uobject.go new file mode 100644 index 0000000..e8ab3b8 --- /dev/null +++ b/good_abstracts/uobject/uobject.go @@ -0,0 +1,19 @@ +package uobject + +type UObject struct { + properties map[string]interface{} +} + +func NewUObject() *UObject { + return &UObject{ + properties: make(map[string]interface{}), + } +} + +func (u *UObject) GetProperty(property string) interface{} { + return u.properties[property] +} + +func (u *UObject) SetProperty(property string, value interface{}) { + u.properties[property] = value +} diff --git a/good_abstracts/uobject/uobject_test.go b/good_abstracts/uobject/uobject_test.go new file mode 100644 index 0000000..7197b1d --- /dev/null +++ b/good_abstracts/uobject/uobject_test.go @@ -0,0 +1,45 @@ +package uobject + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUObject(t *testing.T) { + t.Run("NewUObject, создаем пустой объект", func(t *testing.T) { + obj := NewUObject() + assert.NotNil(t, obj) + assert.Nil(t, obj.GetProperty("nonexistent")) + }) + + t.Run("Set и Get", func(t *testing.T) { + obj := NewUObject() + + obj.SetProperty("test", "value") + result := obj.GetProperty("test") + + assert.Equal(t, "value", result) + }) + + t.Run("Перезапись", func(t *testing.T) { + obj := NewUObject() + + obj.SetProperty("key", "old") + obj.SetProperty("key", "new") + + assert.Equal(t, "new", obj.GetProperty("key")) + }) + + t.Run("Много значений", func(t *testing.T) { + obj := NewUObject() + + obj.SetProperty("string", "text") + obj.SetProperty("number", 42) + obj.SetProperty("bool", true) + + assert.Equal(t, "text", obj.GetProperty("string")) + assert.Equal(t, 42, obj.GetProperty("number")) + assert.Equal(t, true, obj.GetProperty("bool")) + }) +}