From eb80029b9b725b9ed6394d843e1009cfceab1a37 Mon Sep 17 00:00:00 2001 From: "Ivanov, Stepan Ivanov" Date: Mon, 5 Jan 2026 02:06:24 +0300 Subject: [PATCH 1/6] first commit --- internal/daysteps/daysteps.go | 41 ++++++++++- internal/spentcalories/spentcalories.go | 92 +++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 8 deletions(-) diff --git a/internal/daysteps/daysteps.go b/internal/daysteps/daysteps.go index 92001d6..51f9828 100644 --- a/internal/daysteps/daysteps.go +++ b/internal/daysteps/daysteps.go @@ -1,7 +1,20 @@ package daysteps import ( + "errors" + "fmt" + "strconv" + "strings" "time" + + "github.com/Yandex-Practicum/tracker/internal/spentcalories" +) + +var ( + ErrIncSliceEl = errors.New("Incorrect number of slice elements.") + ErrConvToInt = errors.New("Error converting a string to a number.") + ErrNegativeSteps = errors.New("Negative or zero number of steps.") + ErrConvToTime = errors.New("Time conversion error.") ) const ( @@ -12,9 +25,33 @@ const ( ) func parsePackage(data string) (int, time.Duration, error) { - // TODO: реализовать функцию + dataSlice := strings.Split(data, ",") + if len(dataSlice) != 2 { + return 0, 0, ErrIncSliceEl + } + steps, err := strconv.Atoi(dataSlice[0]) + if err != nil { + return 0, 0, ErrConvToInt + } + if steps <= 0 { + return 0, 0, ErrNegativeSteps + } + walkingTime, err := time.ParseDuration(dataSlice[1]) + if err != nil { + return 0, 0, ErrConvToTime + } + return steps, walkingTime, nil } func DayActionInfo(data string, weight, height float64) string { - // TODO: реализовать функцию + steps, walkingTime, err := parsePackage(data) + if err != nil { + return "" + } + distance := float64(steps) * stepLength / mInKm + calories, err := spentcalories.WalkingSpentCalories(steps, weight, height, walkingTime) + if err != nil { + return "" + } + return fmt.Sprintf("Количество шагов: %d.\nДистанция составила %.2f км.\nВы сожгли %.2f ккал.\n", steps, distance, calories) } diff --git a/internal/spentcalories/spentcalories.go b/internal/spentcalories/spentcalories.go index f17d134..e716c02 100644 --- a/internal/spentcalories/spentcalories.go +++ b/internal/spentcalories/spentcalories.go @@ -1,6 +1,11 @@ package spentcalories import ( + "errors" + "fmt" + "log" + "strconv" + "strings" "time" ) @@ -13,26 +18,101 @@ const ( walkingCaloriesCoefficient = 0.5 // коэффициент для расчета калорий при ходьбе ) +var ( + ErrIncSliceEl = errors.New("Incorrect number of slice elements.") + ErrConvToInt = errors.New("Error converting a string to a number.") + ErrNegativeValue = errors.New("Negative or zero number of value.") + ErrConvToTime = errors.New("Time conversion error.") + ErrSportInput = errors.New("This type of activity was not found.") +) + func parseTraining(data string) (int, string, time.Duration, error) { - // TODO: реализовать функцию + dataSlices := strings.Split(data, ",") + if len(dataSlices) != 3 { + return 0, "", 0, ErrIncSliceEl + } + steps, err := strconv.Atoi(dataSlices[0]) + if err != nil { + return 0, "", 0, ErrConvToInt + } + if steps <= 0 { + return 0, "", 0, fmt.Errorf("%w Exactly the steps.", ErrNegativeValue) + } + walkingTime, err := time.ParseDuration(dataSlices[2]) + if err != nil { + return 0, "", 0, ErrConvToTime + } + return steps, dataSlices[1], walkingTime, nil } func distance(steps int, height float64) float64 { - // TODO: реализовать функцию + return height * stepLengthCoefficient * float64(steps) / mInKm } func meanSpeed(steps int, height float64, duration time.Duration) float64 { - // TODO: реализовать функцию + if duration <= 0 { + return 0 + } + distanceWalk := distance(steps, height) + return distanceWalk / duration.Hours() } func TrainingInfo(data string, weight, height float64) (string, error) { - // TODO: реализовать функцию + steps, typeSport, walkingTime, err := parseTraining(data) + if err != nil { + log.Println(err) + return "", err + } + averageSpeed := meanSpeed(steps, height, walkingTime) + walkingDistance := distance(steps, height) + var calories float64 + switch typeSport { + case "Бег": + calories, err = RunningSpentCalories(steps, weight, height, walkingTime) + case "Ходьба": + calories, err = WalkingSpentCalories(steps, weight, height, walkingTime) + default: + return "", ErrSportInput + } + if err != nil { + return "", err + } + str := fmt.Sprintf("Тип тренировки: %s\nДлительность: %.2f ч.\nДистанция: %.2f км.\nСкорость: %.2f км/ч\nСожгли калорий: %.2f\n", typeSport, walkingTime.Hours(), walkingDistance, averageSpeed, calories) + return str, nil } func RunningSpentCalories(steps int, weight, height float64, duration time.Duration) (float64, error) { - // TODO: реализовать функцию + if steps <= 0 { + return 0, fmt.Errorf("%w Exactly the steps.", ErrNegativeValue) + } + if weight <= 0 { + return 0, fmt.Errorf("%w Exactly the weight.", ErrNegativeValue) + } + if height <= 0 { + return 0, fmt.Errorf("%w Exactly the height.", ErrNegativeValue) + } + if duration <= 0 { + return 0, fmt.Errorf("%w Exactly the time.", ErrNegativeValue) + } + averageSpeed := meanSpeed(steps, height, duration) + calories := (weight * averageSpeed * duration.Minutes()) / minInH + return calories, nil } func WalkingSpentCalories(steps int, weight, height float64, duration time.Duration) (float64, error) { - // TODO: реализовать функцию + if steps <= 0 { + return 0, fmt.Errorf("%w Exactly the steps.", ErrNegativeValue) + } + if weight <= 0 { + return 0, fmt.Errorf("%w Exactly the weight.", ErrNegativeValue) + } + if height <= 0 { + return 0, fmt.Errorf("%w Exactly the height.", ErrNegativeValue) + } + if duration <= 0 { + return 0, fmt.Errorf("%w Exactly the time.", ErrNegativeValue) + } + averageSpeed := meanSpeed(steps, height, duration) + calories := (weight * averageSpeed * duration.Minutes()) / minInH * walkingCaloriesCoefficient + return calories, nil } From 1eca136a5d3bba7a8e0393edda60636eadc30e8a Mon Sep 17 00:00:00 2001 From: "Ivanov, Stepan Ivanov" Date: Mon, 5 Jan 2026 10:19:28 +0300 Subject: [PATCH 2/6] Second commit --- cmd/tracker/main.go | 4 ++-- go.mod | 2 +- internal/daysteps/daysteps.go | 16 +++++++++++++--- internal/spentcalories/spentcalories.go | 3 +++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cmd/tracker/main.go b/cmd/tracker/main.go index afc94e7..4900b5f 100644 --- a/cmd/tracker/main.go +++ b/cmd/tracker/main.go @@ -4,8 +4,8 @@ import ( "fmt" "log" - "github.com/Yandex-Practicum/tracker/internal/daysteps" - "github.com/Yandex-Practicum/tracker/internal/spentcalories" + "github.com/St-Ivanov/step-by-step/internal/daysteps" + "github.com/St-Ivanov/step-by-step/internal/spentcalories" ) func main() { diff --git a/go.mod b/go.mod index e8101a1..0347dcd 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/Yandex-Practicum/tracker +module github.com/St-Ivanov/step-by-step go 1.24.1 diff --git a/internal/daysteps/daysteps.go b/internal/daysteps/daysteps.go index 51f9828..cd14332 100644 --- a/internal/daysteps/daysteps.go +++ b/internal/daysteps/daysteps.go @@ -3,17 +3,18 @@ package daysteps import ( "errors" "fmt" + "log" "strconv" "strings" "time" - "github.com/Yandex-Practicum/tracker/internal/spentcalories" + "github.com/St-Ivanov/step-by-step/internal/spentcalories" ) var ( ErrIncSliceEl = errors.New("Incorrect number of slice elements.") ErrConvToInt = errors.New("Error converting a string to a number.") - ErrNegativeSteps = errors.New("Negative or zero number of steps.") + ErrNegativeValue = errors.New("Negative or zero number of value.") ErrConvToTime = errors.New("Time conversion error.") ) @@ -31,26 +32,35 @@ func parsePackage(data string) (int, time.Duration, error) { } steps, err := strconv.Atoi(dataSlice[0]) if err != nil { + log.Println(ErrConvToInt) return 0, 0, ErrConvToInt } if steps <= 0 { - return 0, 0, ErrNegativeSteps + log.Println(fmt.Errorf("%w Exactly the steps.", ErrNegativeValue)) + return 0, 0, fmt.Errorf("%w Exactly the steps.", ErrNegativeValue) } walkingTime, err := time.ParseDuration(dataSlice[1]) if err != nil { + log.Println(ErrConvToTime) return 0, 0, ErrConvToTime } + if walkingTime <= 0 { + log.Println(fmt.Errorf("%w Exactly the time.", ErrNegativeValue)) + return 0, 0, fmt.Errorf("%w Exactly the time.", ErrNegativeValue) + } return steps, walkingTime, nil } func DayActionInfo(data string, weight, height float64) string { steps, walkingTime, err := parsePackage(data) if err != nil { + log.Println(err) return "" } distance := float64(steps) * stepLength / mInKm calories, err := spentcalories.WalkingSpentCalories(steps, weight, height, walkingTime) if err != nil { + log.Println(err) return "" } return fmt.Sprintf("Количество шагов: %d.\nДистанция составила %.2f км.\nВы сожгли %.2f ккал.\n", steps, distance, calories) diff --git a/internal/spentcalories/spentcalories.go b/internal/spentcalories/spentcalories.go index e716c02..2a56672 100644 --- a/internal/spentcalories/spentcalories.go +++ b/internal/spentcalories/spentcalories.go @@ -42,6 +42,9 @@ func parseTraining(data string) (int, string, time.Duration, error) { if err != nil { return 0, "", 0, ErrConvToTime } + if walkingTime <= 0 { + return 0, "", 0, fmt.Errorf("%w Exactly the time.", ErrNegativeValue) + } return steps, dataSlices[1], walkingTime, nil } From 17a071af4bbde3e0538d4edff395da0fd673d7e7 Mon Sep 17 00:00:00 2001 From: "Ivanov, Stepan Ivanov" Date: Mon, 5 Jan 2026 10:25:54 +0300 Subject: [PATCH 3/6] First commit --- internal/spentcalories/spentcalories.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/spentcalories/spentcalories.go b/internal/spentcalories/spentcalories.go index 2a56672..6fda10f 100644 --- a/internal/spentcalories/spentcalories.go +++ b/internal/spentcalories/spentcalories.go @@ -23,7 +23,7 @@ var ( ErrConvToInt = errors.New("Error converting a string to a number.") ErrNegativeValue = errors.New("Negative or zero number of value.") ErrConvToTime = errors.New("Time conversion error.") - ErrSportInput = errors.New("This type of activity was not found.") + ErrSportInput = errors.New("неизвестный тип тренировки") ) func parseTraining(data string) (int, string, time.Duration, error) { From f9afafd34d60b656b54caf7dff2ad72b428f56bd Mon Sep 17 00:00:00 2001 From: "Ivanov, Stepan Ivanov" Date: Sun, 11 Jan 2026 01:43:15 +0300 Subject: [PATCH 4/6] sprint5 --- internal/actioninfo/actioninfo.go | 27 ++++++++ internal/actioninfo/actioninfo_test.go | 85 ++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 internal/actioninfo/actioninfo.go create mode 100644 internal/actioninfo/actioninfo_test.go diff --git a/internal/actioninfo/actioninfo.go b/internal/actioninfo/actioninfo.go new file mode 100644 index 0000000..034d652 --- /dev/null +++ b/internal/actioninfo/actioninfo.go @@ -0,0 +1,27 @@ +package actioninfo + +import ( + "fmt" + "log" +) + +type DataParser interface { + Parse(string) error + ActionInfo() (string, error) +} + +func Info(dataset []string, dp DataParser) { + for _, v := range dataset { + err := dp.Parse(v) + if err != nil { + log.Println(err) + continue + } + s, err := dp.ActionInfo() + if err != nil { + log.Println(err) + continue + } + fmt.Print(s) + } +} diff --git a/internal/actioninfo/actioninfo_test.go b/internal/actioninfo/actioninfo_test.go new file mode 100644 index 0000000..b2f2e20 --- /dev/null +++ b/internal/actioninfo/actioninfo_test.go @@ -0,0 +1,85 @@ +package actioninfo + +import ( + "bytes" + "log" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// MockDataParser is a mock implementation of the DataParser interface +type MockDataParser struct { + mock.Mock +} + +func (m *MockDataParser) Parse(data string) error { + args := m.Called(data) + return args.Error(0) +} + +func (m *MockDataParser) ActionInfo() (string, error) { + args := m.Called() + return args.String(0), args.Error(1) +} + +func TestInfo(t *testing.T) { + // Set up log output capture + var logBuf bytes.Buffer + log.SetOutput(&logBuf) + defer log.SetOutput(os.Stderr) + + // Set up stdout capture + var stdoutBuf bytes.Buffer + old := os.Stdout + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + os.Stdout = w + defer func() { os.Stdout = old }() + + tests := []struct { + name string + dataset []string + setup func(*MockDataParser) + expectedOutput string + }{ + { + name: "happy path - single item", + dataset: []string{"test data"}, + setup: func(m *MockDataParser) { + m.On("Parse", "test data").Return(nil) + m.On("ActionInfo").Return("processed test data", nil) + }, + expectedOutput: "processed test data\n", + }, + { + name: "empty dataset", + dataset: []string{}, + setup: func(m *MockDataParser) {}, + expectedOutput: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logBuf.Reset() + stdoutBuf.Reset() + + mockParser := new(MockDataParser) + tt.setup(mockParser) + + Info(tt.dataset, mockParser) + + // Close the write end of the pipe and read the output + w.Close() + stdoutBuf.ReadFrom(r) + + mockParser.AssertExpectations(t) + assert.Contains(t, tt.expectedOutput, stdoutBuf.String(), "вывод в stdout должен соответствовать ожидаемому результату") + }) + } +} From b2622c2cf8338f855faa8a2b1ff16faa973f7543 Mon Sep 17 00:00:00 2001 From: "Ivanov, Stepan Ivanov" Date: Sun, 11 Jan 2026 01:45:14 +0300 Subject: [PATCH 5/6] sprint5 --- go.mod | 3 +- go.sum | 4 +- internal/daysteps/daysteps.go | 73 +- internal/daysteps/daysteps_test.go | 308 ++++---- internal/errors/errors.go | 11 + internal/personaldata/personaldata.go | 16 + internal/personaldata/personaldata_test.go | 81 +++ internal/spentcalories/spentcalories.go | 121 ---- internal/spentcalories/spentcalories_test.go | 702 ------------------- internal/spentenergy/spentenergy.go | 62 ++ internal/spentenergy/spentenergy_test.go | 401 +++++++++++ internal/trainings/trainings.go | 74 ++ internal/trainings/trainings_test.go | 337 +++++++++ 13 files changed, 1207 insertions(+), 986 deletions(-) create mode 100644 internal/errors/errors.go create mode 100644 internal/personaldata/personaldata.go create mode 100644 internal/personaldata/personaldata_test.go delete mode 100644 internal/spentcalories/spentcalories.go delete mode 100644 internal/spentcalories/spentcalories_test.go create mode 100644 internal/spentenergy/spentenergy.go create mode 100644 internal/spentenergy/spentenergy_test.go create mode 100644 internal/trainings/trainings.go create mode 100644 internal/trainings/trainings_test.go diff --git a/go.mod b/go.mod index 0347dcd..9934ea5 100644 --- a/go.mod +++ b/go.mod @@ -7,5 +7,6 @@ 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/objx v0.5.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect -) +) \ No newline at end of file diff --git a/go.sum b/go.sum index 713a0b4..e5e316f 100644 --- a/go.sum +++ b/go.sum @@ -2,9 +2,11 @@ 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.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= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= \ No newline at end of file diff --git a/internal/daysteps/daysteps.go b/internal/daysteps/daysteps.go index cd14332..ccce248 100644 --- a/internal/daysteps/daysteps.go +++ b/internal/daysteps/daysteps.go @@ -1,67 +1,54 @@ package daysteps import ( - "errors" "fmt" - "log" "strconv" "strings" "time" - "github.com/St-Ivanov/step-by-step/internal/spentcalories" + "github.com/St-Ivanov/step-by-step/internal/errors" + "github.com/St-Ivanov/step-by-step/internal/personaldata" + "github.com/St-Ivanov/step-by-step/internal/spentenergy" ) -var ( - ErrIncSliceEl = errors.New("Incorrect number of slice elements.") - ErrConvToInt = errors.New("Error converting a string to a number.") - ErrNegativeValue = errors.New("Negative or zero number of value.") - ErrConvToTime = errors.New("Time conversion error.") -) - -const ( - // Длина одного шага в метрах - stepLength = 0.65 - // Количество метров в одном километре - mInKm = 1000 -) +type DaySteps struct { + Steps int + Duration time.Duration + personaldata.Personal +} -func parsePackage(data string) (int, time.Duration, error) { - dataSlice := strings.Split(data, ",") - if len(dataSlice) != 2 { - return 0, 0, ErrIncSliceEl +func (ds *DaySteps) Parse(datastring string) (err error) { + data := strings.Split(datastring, ",") + if len(data) != 2 { + return errors.ErrIncDataEnt } - steps, err := strconv.Atoi(dataSlice[0]) + steps, err := strconv.Atoi(data[0]) if err != nil { - log.Println(ErrConvToInt) - return 0, 0, ErrConvToInt + return errors.ErrConvToInt } if steps <= 0 { - log.Println(fmt.Errorf("%w Exactly the steps.", ErrNegativeValue)) - return 0, 0, fmt.Errorf("%w Exactly the steps.", ErrNegativeValue) + return fmt.Errorf("%w Exactly the steps.", errors.ErrNegativeValue) } - walkingTime, err := time.ParseDuration(dataSlice[1]) + duration, err := time.ParseDuration(data[1]) if err != nil { - log.Println(ErrConvToTime) - return 0, 0, ErrConvToTime + return errors.ErrConvToTime } - if walkingTime <= 0 { - log.Println(fmt.Errorf("%w Exactly the time.", ErrNegativeValue)) - return 0, 0, fmt.Errorf("%w Exactly the time.", ErrNegativeValue) + if duration <= 0 { + return fmt.Errorf("%w Exactly the time.", errors.ErrNegativeValue) } - return steps, walkingTime, nil + ds.Steps = steps + ds.Duration = duration + return nil } -func DayActionInfo(data string, weight, height float64) string { - steps, walkingTime, err := parsePackage(data) - if err != nil { - log.Println(err) - return "" - } - distance := float64(steps) * stepLength / mInKm - calories, err := spentcalories.WalkingSpentCalories(steps, weight, height, walkingTime) +func (ds DaySteps) ActionInfo() (string, error) { + distance := spentenergy.Distance(ds.Steps, ds.Height) + calories, err := spentenergy.WalkingSpentCalories(ds.Steps, ds.Weight, ds.Height, ds.Duration) if err != nil { - log.Println(err) - return "" + return "", err } - return fmt.Sprintf("Количество шагов: %d.\nДистанция составила %.2f км.\nВы сожгли %.2f ккал.\n", steps, distance, calories) + return fmt.Sprintf(`Количество шагов: %d. +Дистанция составила %0.2f км. +Вы сожгли %0.2f ккал. +`, ds.Steps, distance, calories), nil } diff --git a/internal/daysteps/daysteps_test.go b/internal/daysteps/daysteps_test.go index 68565a1..ff6c873 100644 --- a/internal/daysteps/daysteps_test.go +++ b/internal/daysteps/daysteps_test.go @@ -1,13 +1,12 @@ package daysteps import ( - "bytes" - "log" - "os" "testing" "time" + "github.com/St-Ivanov/step-by-step/internal/personaldata" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -211,138 +210,211 @@ func (suite *DayStepsTestSuite) TestParsePackage() { for _, tt := range tests { suite.Run(tt.name, func() { - gotSteps, gotDuration, err := parsePackage(tt.input) + ds := &DaySteps{} + err := ds.Parse(tt.input) if tt.wantErr { - assert.Error(suite.T(), err, "parsePackage() для строки данных %q ожидалась ошибка, но её нет", tt.input) - } else { - assert.NoError(suite.T(), err, "parsePackage() неожиданная ошибка для строки данных %q: %v", tt.input, err) + require.Error(suite.T(), err, "Parse() для строки данных %q ожидалась ошибка, но её нет", tt.input) + return } - - assert.Equal(suite.T(), tt.wantSteps, gotSteps, "parsePackage() полученное количество шагов: %v, ожидается %v", gotSteps, tt.wantSteps) - assert.Equal(suite.T(), tt.wantDuration, gotDuration, "parsePackage() полученная продолжительность прогулки: %v, ожидается %v", gotDuration, tt.wantDuration) + require.NoError(suite.T(), err, "Parse() неожиданная ошибка для строки данных %q: %v", tt.input, err) + assert.Equal(suite.T(), tt.wantSteps, ds.Steps, "Parse() полученное количество шагов: %v, ожидается %v", ds.Steps, tt.wantSteps) + assert.Equal(suite.T(), tt.wantDuration, ds.Duration, "Parse() полученная продолжительность прогулки: %v, ожидается %v", ds.Duration, tt.wantDuration) }) } } func (suite *DayStepsTestSuite) TestDayActionInfo() { - var buf bytes.Buffer - log.SetOutput(&buf) - - defer log.SetOutput(os.Stderr) - tests := []struct { - name string - input string - weight float64 - height float64 - want string - wantLogOutput bool + name string + ds DaySteps + want string + wantErr bool }{ { - name: "нормальная нагрузка - один час", - input: "6000,1h00m", - weight: 75.0, - height: 1.75, - want: "Количество шагов: 6000.\nДистанция составила 3.90 км.\nВы сожгли 177.19 ккал.\n", - wantLogOutput: false, - }, - { - name: "нормальная нагрузка - полчаса", - input: "3000,30m", - weight: 75.0, - height: 1.75, - want: "Количество шагов: 3000.\nДистанция составила 1.95 км.\nВы сожгли 88.59 ккал.\n", - wantLogOutput: false, - }, - { - name: "высокая нагрузка", - input: "20000,1h00m", - weight: 75.0, - height: 1.75, - want: "Количество шагов: 20000.\nДистанция составила 13.00 км.\nВы сожгли 590.62 ккал.\n", - wantLogOutput: false, - }, - { - name: "низкая нагрузка", - input: "1000,2h00m", - weight: 75.0, - height: 1.75, - want: "Количество шагов: 1000.\nДистанция составила 0.65 км.\nВы сожгли 29.53 ккал.\n", - wantLogOutput: false, - }, - { - name: "другой вес и рост", - input: "6000,1h00m", - weight: 60.0, - height: 1.85, - want: "Количество шагов: 6000.\nДистанция составила 3.90 км.\nВы сожгли 149.85 ккал.\n", - wantLogOutput: false, - }, - { - name: "некорректный формат", - input: "not valid", - weight: 75.0, - height: 1.75, - want: "", - wantLogOutput: true, - }, - { - name: "пустая строка", - input: "", - weight: 75.0, - height: 1.75, - want: "", - wantLogOutput: true, - }, - { - name: "отрицательные шаги", - input: "-1000,1h00m", - weight: 75.0, - height: 1.75, - want: "", - wantLogOutput: true, - }, - { - name: "ноль шагов", - input: "0,1h00m", - weight: 75.0, - height: 1.75, - want: "", - wantLogOutput: true, - }, - { - name: "отрицательная продолжительность", - input: "1000,-1h00m", - weight: 75.0, - height: 1.75, - want: "", - wantLogOutput: true, - }, - { - name: "нулевая продолжительность", - input: "1000,0h00m", - weight: 75.0, - height: 1.75, - want: "", - wantLogOutput: true, + name: "нормальная нагрузка - один час", + ds: DaySteps{ + Steps: 6000, + Duration: time.Hour, + Personal: personaldata.Personal{ + Weight: 75.0, + Height: 1.75, + }, + }, + want: "Количество шагов: 6000.\nДистанция составила 4.72 км.\nВы сожгли 177.19 ккал.\n", + wantErr: false, + }, + { + name: "нормальная нагрузка - полчаса", + ds: DaySteps{ + Steps: 3000, + Duration: 30 * time.Minute, + Personal: personaldata.Personal{ + Weight: 75.0, + Height: 1.75, + }, + }, + want: "Количество шагов: 3000.\nДистанция составила 2.36 км.\nВы сожгли 88.59 ккал.\n", + wantErr: false, + }, + { + name: "высокая нагрузка", + ds: DaySteps{ + Steps: 20000, + Duration: time.Hour, + Personal: personaldata.Personal{ + Weight: 75.0, + Height: 1.75, + }, + }, + want: "Количество шагов: 20000.\nДистанция составила 15.75 км.\nВы сожгли 590.62 ккал.\n", + wantErr: false, + }, + { + name: "низкая нагрузка", + ds: DaySteps{ + Steps: 1000, + Duration: 2 * time.Hour, + Personal: personaldata.Personal{ + Weight: 75.0, + Height: 1.75, + }, + }, + want: "Количество шагов: 1000.\nДистанция составила 0.79 км.\nВы сожгли 29.53 ккал.\n", + wantErr: false, + }, + { + name: "другой вес и рост", + ds: DaySteps{ + Steps: 6000, + Duration: time.Hour, + Personal: personaldata.Personal{ + Weight: 60.0, + Height: 1.85, + }, + }, + want: "Количество шагов: 6000.\nДистанция составила 5.00 км.\nВы сожгли 149.85 ккал.\n", + wantErr: false, + }, + { + name: "нулевые шаги", + ds: DaySteps{ + Steps: 0, + Duration: time.Hour, + Personal: personaldata.Personal{ + Weight: 75.0, + Height: 1.75, + }, + }, + want: "", + wantErr: true, + }, + { + name: "отрицательные шаги", + ds: DaySteps{ + Steps: -1000, + Duration: time.Hour, + Personal: personaldata.Personal{ + Weight: 75.0, + Height: 1.75, + }, + }, + want: "", + wantErr: true, + }, + { + name: "нулевая продолжительность", + ds: DaySteps{ + Steps: 1000, + Duration: 0, + Personal: personaldata.Personal{ + Weight: 75.0, + Height: 1.75, + }, + }, + want: "", + wantErr: true, + }, + { + name: "отрицательная продолжительность", + ds: DaySteps{ + Steps: 1000, + Duration: -time.Hour, + Personal: personaldata.Personal{ + Weight: 75.0, + Height: 1.75, + }, + }, + want: "", + wantErr: true, + }, + { + name: "нулевой вес", + ds: DaySteps{ + Steps: 6000, + Duration: time.Hour, + Personal: personaldata.Personal{ + Weight: 0, + Height: 1.75, + }, + }, + want: "", + wantErr: true, + }, + { + name: "отрицательный вес", + ds: DaySteps{ + Steps: 6000, + Duration: time.Hour, + Personal: personaldata.Personal{ + Weight: -75.0, + Height: 1.75, + }, + }, + want: "", + wantErr: true, + }, + { + name: "нулевой рост", + ds: DaySteps{ + Steps: 6000, + Duration: time.Hour, + Personal: personaldata.Personal{ + Weight: 75.0, + Height: 0, + }, + }, + want: "", + wantErr: true, + }, + { + name: "отрицательный рост", + ds: DaySteps{ + Steps: 6000, + Duration: time.Hour, + Personal: personaldata.Personal{ + Weight: 75.0, + Height: -1.75, + }, + }, + want: "", + wantErr: true, }, } for _, tt := range tests { suite.Run(tt.name, func() { - buf.Reset() - - got := DayActionInfo(tt.input, tt.weight, tt.height) - - assert.Equal(suite.T(), tt.want, got, "\nDayActionInfo() получено:\n%v\nожидается:\n%v\n(ввод: %q, вес: %.1f, рост: %.2f)", - got, tt.want, tt.input, tt.weight, tt.height) - - if tt.wantLogOutput { - assert.NotEmpty(suite.T(), buf.String(), "Ожидался вывод в лог, но его нет") - } else { - assert.Empty(suite.T(), buf.String(), "Неожиданный вывод в лог: %v", buf.String()) + got, err := tt.ds.ActionInfo() + if tt.wantErr { + require.Error(suite.T(), err, "Для тестового случая %q (шаги: %d, продолжительность: %v, вес: %.1f, рост: %.2f) ожидалась ошибка, но её нет", + tt.name, tt.ds.Steps, tt.ds.Duration, tt.ds.Weight, tt.ds.Height) + require.Empty(suite.T(), got, "Для тестового случая %q (шаги: %d, продолжительность: %v, вес: %.1f, рост: %.2f) ожидалась пустая строка, но получено: %q", + tt.name, tt.ds.Steps, tt.ds.Duration, tt.ds.Weight, tt.ds.Height, got) + return } + require.NoError(suite.T(), err) + require.Equal(suite.T(), tt.want, got, "\nActionInfo() получено:\n%v\nожидается:\n%v\n(шаги: %d, продолжительность: %v, вес: %.1f, рост: %.2f)", + got, tt.want, tt.ds.Steps, tt.ds.Duration, tt.ds.Weight, tt.ds.Height) }) } } diff --git a/internal/errors/errors.go b/internal/errors/errors.go new file mode 100644 index 0000000..46197ce --- /dev/null +++ b/internal/errors/errors.go @@ -0,0 +1,11 @@ +package errors + +import "errors" + +var ( + ErrIncDataEnt = errors.New("Incorrect data entry.") + ErrNegativeValue = errors.New("Negative or zero number of value.") + ErrConvToInt = errors.New("Error converting a string to a number.") + ErrConvToTime = errors.New("Time conversion error.") + ErrIncUndTraining = errors.New("неизвестный тип тренировки") +) diff --git a/internal/personaldata/personaldata.go b/internal/personaldata/personaldata.go new file mode 100644 index 0000000..4b09af7 --- /dev/null +++ b/internal/personaldata/personaldata.go @@ -0,0 +1,16 @@ +package personaldata + +import "fmt" + +type Personal struct { + Name string + Weight float64 + Height float64 +} + +func (p Personal) Print() { + fmt.Printf(`Имя: %s +Вес: %0.2f кг. +Рост: %0.2f м. +`, p.Name, p.Weight, p.Height) +} diff --git a/internal/personaldata/personaldata_test.go b/internal/personaldata/personaldata_test.go new file mode 100644 index 0000000..5cb6292 --- /dev/null +++ b/internal/personaldata/personaldata_test.go @@ -0,0 +1,81 @@ +package personaldata + +import ( + "bytes" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPrint(t *testing.T) { + tests := []struct { + name string + personal Personal + want string + }{ + { + name: "стандартные данные", + personal: Personal{ + Name: "Иван", + Weight: 75.0, + Height: 1.75, + }, + want: "Имя: Иван\nВес: 75.00 кг.\nРост: 1.75 м.\n", + }, + { + name: "нулевые значения", + personal: Personal{ + Name: "", + Weight: 0.0, + Height: 0.0, + }, + want: "Имя: \nВес: 0.00 кг.\nРост: 0.00 м.\n", + }, + { + name: "дробные значения", + personal: Personal{ + Name: "Петр", + Weight: 75.5, + Height: 1.85, + }, + want: "Имя: Петр\nВес: 75.50 кг.\nРост: 1.85 м.\n", + }, + { + name: "большие значения", + personal: Personal{ + Name: "Алексей", + Weight: 100.0, + Height: 2.00, + }, + want: "Имя: Алексей\nВес: 100.00 кг.\nРост: 2.00 м.\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + old := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err, "Не удалось создать pipe") + os.Stdout = w + + done := make(chan bool) + go func() { + tt.personal.Print() + w.Close() + done <- true + }() + + var buf bytes.Buffer + buf.ReadFrom(r) + got := buf.String() + + os.Stdout = old + + <-done + + assert.Equal(t, tt.want, got, "Вывод Print() не соответствует ожидаемому") + }) + } +} diff --git a/internal/spentcalories/spentcalories.go b/internal/spentcalories/spentcalories.go deleted file mode 100644 index 6fda10f..0000000 --- a/internal/spentcalories/spentcalories.go +++ /dev/null @@ -1,121 +0,0 @@ -package spentcalories - -import ( - "errors" - "fmt" - "log" - "strconv" - "strings" - "time" -) - -// Основные константы, необходимые для расчетов. -const ( - lenStep = 0.65 // средняя длина шага. - mInKm = 1000 // количество метров в километре. - minInH = 60 // количество минут в часе. - stepLengthCoefficient = 0.45 // коэффициент для расчета длины шага на основе роста. - walkingCaloriesCoefficient = 0.5 // коэффициент для расчета калорий при ходьбе -) - -var ( - ErrIncSliceEl = errors.New("Incorrect number of slice elements.") - ErrConvToInt = errors.New("Error converting a string to a number.") - ErrNegativeValue = errors.New("Negative or zero number of value.") - ErrConvToTime = errors.New("Time conversion error.") - ErrSportInput = errors.New("неизвестный тип тренировки") -) - -func parseTraining(data string) (int, string, time.Duration, error) { - dataSlices := strings.Split(data, ",") - if len(dataSlices) != 3 { - return 0, "", 0, ErrIncSliceEl - } - steps, err := strconv.Atoi(dataSlices[0]) - if err != nil { - return 0, "", 0, ErrConvToInt - } - if steps <= 0 { - return 0, "", 0, fmt.Errorf("%w Exactly the steps.", ErrNegativeValue) - } - walkingTime, err := time.ParseDuration(dataSlices[2]) - if err != nil { - return 0, "", 0, ErrConvToTime - } - if walkingTime <= 0 { - return 0, "", 0, fmt.Errorf("%w Exactly the time.", ErrNegativeValue) - } - return steps, dataSlices[1], walkingTime, nil -} - -func distance(steps int, height float64) float64 { - return height * stepLengthCoefficient * float64(steps) / mInKm -} - -func meanSpeed(steps int, height float64, duration time.Duration) float64 { - if duration <= 0 { - return 0 - } - distanceWalk := distance(steps, height) - return distanceWalk / duration.Hours() -} - -func TrainingInfo(data string, weight, height float64) (string, error) { - steps, typeSport, walkingTime, err := parseTraining(data) - if err != nil { - log.Println(err) - return "", err - } - averageSpeed := meanSpeed(steps, height, walkingTime) - walkingDistance := distance(steps, height) - var calories float64 - switch typeSport { - case "Бег": - calories, err = RunningSpentCalories(steps, weight, height, walkingTime) - case "Ходьба": - calories, err = WalkingSpentCalories(steps, weight, height, walkingTime) - default: - return "", ErrSportInput - } - if err != nil { - return "", err - } - str := fmt.Sprintf("Тип тренировки: %s\nДлительность: %.2f ч.\nДистанция: %.2f км.\nСкорость: %.2f км/ч\nСожгли калорий: %.2f\n", typeSport, walkingTime.Hours(), walkingDistance, averageSpeed, calories) - return str, nil -} - -func RunningSpentCalories(steps int, weight, height float64, duration time.Duration) (float64, error) { - if steps <= 0 { - return 0, fmt.Errorf("%w Exactly the steps.", ErrNegativeValue) - } - if weight <= 0 { - return 0, fmt.Errorf("%w Exactly the weight.", ErrNegativeValue) - } - if height <= 0 { - return 0, fmt.Errorf("%w Exactly the height.", ErrNegativeValue) - } - if duration <= 0 { - return 0, fmt.Errorf("%w Exactly the time.", ErrNegativeValue) - } - averageSpeed := meanSpeed(steps, height, duration) - calories := (weight * averageSpeed * duration.Minutes()) / minInH - return calories, nil -} - -func WalkingSpentCalories(steps int, weight, height float64, duration time.Duration) (float64, error) { - if steps <= 0 { - return 0, fmt.Errorf("%w Exactly the steps.", ErrNegativeValue) - } - if weight <= 0 { - return 0, fmt.Errorf("%w Exactly the weight.", ErrNegativeValue) - } - if height <= 0 { - return 0, fmt.Errorf("%w Exactly the height.", ErrNegativeValue) - } - if duration <= 0 { - return 0, fmt.Errorf("%w Exactly the time.", ErrNegativeValue) - } - averageSpeed := meanSpeed(steps, height, duration) - calories := (weight * averageSpeed * duration.Minutes()) / minInH * walkingCaloriesCoefficient - return calories, nil -} diff --git a/internal/spentcalories/spentcalories_test.go b/internal/spentcalories/spentcalories_test.go deleted file mode 100644 index 08e95b2..0000000 --- a/internal/spentcalories/spentcalories_test.go +++ /dev/null @@ -1,702 +0,0 @@ -package spentcalories - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" -) - -type SpentCaloriesTestSuite struct { - suite.Suite -} - -func TestSpentCaloriesSuite(t *testing.T) { - suite.Run(t, new(SpentCaloriesTestSuite)) -} - -func (suite *SpentCaloriesTestSuite) TestParseTraining() { - tests := []struct { - name string - input string - wantSteps int - wantDuration time.Duration - wantErr bool - }{ - { - name: "корректный ввод с часами и минутами", - input: "3456,Ходьба,3h00m", - wantSteps: 3456, - wantDuration: 3 * time.Hour, - wantErr: false, - }, - { - name: "корректный ввод с минутами", - input: "678,Бег,5m", - wantSteps: 678, - wantDuration: 5 * time.Minute, - wantErr: false, - }, - { - name: "положительное число с плюсом", - input: "+12345,Ходьба,1h30m", - wantSteps: 12345, - wantDuration: 90 * time.Minute, - wantErr: false, - }, - { - name: "продолжительность - только минуты", - input: "1000,Бег,30m", - wantSteps: 1000, - wantDuration: 30 * time.Minute, - wantErr: false, - }, - { - name: "продолжительность - только часы", - input: "1000,Ходьба,2h", - wantSteps: 1000, - wantDuration: 2 * time.Hour, - wantErr: false, - }, - { - name: "продолжительность - дробные часы", - input: "1000,Бег,1.5h", - wantSteps: 1000, - wantDuration: 90 * time.Minute, - wantErr: false, - }, - { - name: "продолжительность - дробные минуты", - input: "1000,Ходьба,30.5m", - wantSteps: 1000, - wantDuration: 30*time.Minute + 30*time.Second, - wantErr: false, - }, - { - name: "неверный формат - неправильное количество параметров", - input: "678,Ходьба", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверный формат - четыре параметра", - input: "678,Ходьба,1h30m,extra", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "пустой ввод", - input: "", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверные шаги - не числовое значение", - input: "abc,Ходьба,1h30m", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверные шаги - ноль", - input: "0,Ходьба,1h30m", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверные шаги - отрицательное значение", - input: "-100,Ходьба,1h30m", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверные шаги - только знак минус", - input: "-,Ходьба,1h30m", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверные шаги - только знак плюс", - input: "+,Ходьба,1h30m", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверный формат продолжительности", - input: "678,Ходьба,invalid", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверная продолжительность - ноль", - input: "678,Бег,0h0m", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверная продолжительность - отрицательное значение", - input: "678,Ходьба,-1h30m", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверная продолжительность - отрицательные минуты", - input: "678,Бег,1h-30m", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверная продолжительность - неверная единица измерения", - input: "678,Ходьба,1.5d", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверная продолжительность - пробел между числом и единицей", - input: "678,Бег,1 h30m", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - { - name: "неверная продолжительность - пропущена единица измерения", - input: "678,Ходьба,30", - wantSteps: 0, - wantDuration: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - gotSteps, _, gotDuration, err := parseTraining(tt.input) - - if tt.wantErr { - assert.Error(suite.T(), err) - assert.Equal(suite.T(), 0, gotSteps) - assert.Equal(suite.T(), time.Duration(0), gotDuration) - return - } - - assert.NoError(suite.T(), err) - assert.Equal(suite.T(), tt.wantSteps, gotSteps) - assert.Equal(suite.T(), tt.wantDuration, gotDuration) - }) - } -} - -func (suite *SpentCaloriesTestSuite) TestDistance() { - tests := []struct { - name string - steps int - height float64 - wantDist float64 - }{ - { - name: "нормальное количество шагов", - steps: 1000, - height: 1.75, - wantDist: 0.7875, - }, - { - name: "большое количество шагов", - steps: 10000, - height: 1.75, - wantDist: 7.875, - }, - { - name: "маленькое количество шагов", - steps: 100, - height: 1.75, - wantDist: 0.07875, - }, - { - name: "ноль шагов", - steps: 0, - height: 1.75, - wantDist: 0, - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - got := distance(tt.steps, tt.height) - assert.Equal(suite.T(), tt.wantDist, got) - }) - } -} - -func (suite *SpentCaloriesTestSuite) TestMeanSpeed() { - tests := []struct { - name string - steps int - height float64 - duration time.Duration - wantSpeed float64 - }{ - { - name: "нормальная скорость - один час", - steps: 6000, - height: 1.75, - duration: 1 * time.Hour, - wantSpeed: 4.725, - }, - { - name: "нормальная скорость - полчаса", - steps: 3000, - height: 1.75, - duration: 30 * time.Minute, - wantSpeed: 4.725, - }, - { - name: "нормальная скорость - два часа", - steps: 12000, - height: 1.75, - duration: 2 * time.Hour, - wantSpeed: 4.725, - }, - { - name: "маленькая скорость", - steps: 1000, - height: 1.75, - duration: 2 * time.Hour, - wantSpeed: 0.39375, - }, - { - name: "большая скорость", - steps: 20000, - height: 1.75, - duration: 1 * time.Hour, - wantSpeed: 15.75, - }, - { - name: "нулевая продолжительность", - steps: 1000, - height: 1.75, - duration: 0, - wantSpeed: 0, - }, - { - name: "отрицательная продолжительность", - steps: 1000, - height: 1.75, - duration: -1 * time.Hour, - wantSpeed: 0, - }, - { - name: "ноль шагов", - steps: 0, - height: 1.75, - duration: 1 * time.Hour, - wantSpeed: 0, - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - got := meanSpeed(tt.steps, tt.height, tt.duration) - assert.Equal(suite.T(), tt.wantSpeed, got) - }) - } -} - -func (suite *SpentCaloriesTestSuite) TestRunningSpentCalories() { - tests := []struct { - name string - steps int - weight float64 - height float64 - duration time.Duration - wantCal float64 - wantErr bool - }{ - { - name: "нормальная нагрузка - один час", - steps: 6000, - weight: 75.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 354.375, - wantErr: false, - }, - { - name: "нормальная нагрузка - полчаса", - steps: 3000, - weight: 75.0, - height: 1.75, - duration: 30 * time.Minute, - wantCal: 177.1875, - wantErr: false, - }, - { - name: "высокая скорость", - steps: 20000, - weight: 75.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 1181.25, - wantErr: false, - }, - { - name: "низкая скорость", - steps: 1000, - weight: 75.0, - height: 1.75, - duration: 2 * time.Hour, - wantCal: 59.0625, - wantErr: false, - }, - { - name: "другой вес", - steps: 6000, - weight: 60.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 283.5, - wantErr: false, - }, - { - name: "нулевая продолжительность", - steps: 1000, - weight: 75.0, - height: 1.75, - duration: 0, - wantCal: 0, - wantErr: true, - }, - { - name: "отрицательная продолжительность", - steps: 1000, - weight: 75.0, - height: 1.75, - duration: -1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - { - name: "ноль шагов", - steps: 0, - weight: 75.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - { - name: "отрицательные шаги", - steps: -1000, - weight: 75.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - { - name: "нулевой вес", - steps: 1000, - weight: 0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - { - name: "отрицательный вес", - steps: 1000, - weight: -75.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - gotCal, gotErr := RunningSpentCalories(tt.steps, tt.weight, tt.height, tt.duration) - - if tt.wantErr { - assert.Error(suite.T(), gotErr) - assert.Equal(suite.T(), 0.0, gotCal) - return - } - - assert.NoError(suite.T(), gotErr) - assert.InDelta(suite.T(), tt.wantCal, gotCal, 0.1) - }) - } -} - -func (suite *SpentCaloriesTestSuite) TestWalkingSpentCalories() { - tests := []struct { - name string - steps int - weight float64 - height float64 - duration time.Duration - wantCal float64 - wantErr bool - }{ - { - name: "нормальная нагрузка", - steps: 6000, - weight: 75.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 177.19, - wantErr: false, - }, - { - name: "меньше шагов", - steps: 3000, - weight: 75.0, - height: 1.75, - duration: 30 * time.Minute, - wantCal: 88.594, - wantErr: false, - }, - { - name: "больше шагов", - steps: 20000, - weight: 75.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 590.62, - wantErr: false, - }, - { - name: "другой вес", - steps: 6000, - weight: 60.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 141.75, - wantErr: false, - }, - { - name: "другой рост", - steps: 6000, - weight: 75.0, - height: 1.85, - duration: 1 * time.Hour, - wantCal: 187.313, - wantErr: false, - }, - { - name: "нулевые шаги", - steps: 0, - weight: 75.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - { - name: "отрицательные шаги", - steps: -1000, - weight: 75.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - { - name: "нулевой вес", - steps: 6000, - weight: 0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - { - name: "отрицательный вес", - steps: 6000, - weight: -75.0, - height: 1.75, - duration: 1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - { - name: "нулевой рост", - steps: 6000, - weight: 75.0, - height: 0, - duration: 1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - { - name: "отрицательный рост", - steps: 6000, - weight: 75.0, - height: -1.75, - duration: 1 * time.Hour, - wantCal: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - gotCal, gotErr := WalkingSpentCalories(tt.steps, tt.weight, tt.height, tt.duration) - - if tt.wantErr { - assert.Error(suite.T(), gotErr) - assert.Equal(suite.T(), 0.0, gotCal) - return - } - - assert.NoError(suite.T(), gotErr) - assert.InDelta(suite.T(), tt.wantCal, gotCal, 0.1) - }) - } -} - -func (suite *SpentCaloriesTestSuite) TestTrainingInfo() { - tests := []struct { - name string - input string - weight float64 - height float64 - want string - wantErr bool - }{ - { - name: "ходьба - нормальная нагрузка", - input: "6000,Ходьба,1h00m", - weight: 75.0, - height: 1.75, - want: "Тип тренировки: Ходьба\nДлительность: 1.00 ч.\nДистанция: 4.72 км.\nСкорость: 4.72 км/ч\nСожгли калорий: 177.19\n", - wantErr: false, - }, - { - name: "бег - нормальная нагрузка", - input: "6000,Бег,1h00m", - weight: 75.0, - height: 1.75, - want: "Тип тренировки: Бег\nДлительность: 1.00 ч.\nДистанция: 4.72 км.\nСкорость: 4.72 км/ч\nСожгли калорий: 354.38\n", - wantErr: false, - }, - { - name: "ходьба - высокая скорость", - input: "20000,Ходьба,1h00m", - weight: 75.0, - height: 1.75, - want: "Тип тренировки: Ходьба\nДлительность: 1.00 ч.\nДистанция: 15.75 км.\nСкорость: 15.75 км/ч\nСожгли калорий: 590.62\n", - wantErr: false, - }, - { - name: "бег - высокая скорость", - input: "20000,Бег,1h00m", - weight: 75.0, - height: 1.75, - want: "Тип тренировки: Бег\nДлительность: 1.00 ч.\nДистанция: 15.75 км.\nСкорость: 15.75 км/ч\nСожгли калорий: 1181.25\n", - wantErr: false, - }, - { - name: "ходьба - другой вес и рост", - input: "6000,Ходьба,1h00m", - weight: 60.0, - height: 1.85, - want: "Тип тренировки: Ходьба\nДлительность: 1.00 ч.\nДистанция: 5.00 км.\nСкорость: 5.00 км/ч\nСожгли калорий: 149.85\n", - wantErr: false, - }, - { - name: "бег - другой вес", - input: "6000,Бег,1h00m", - weight: 60.0, - height: 1.75, - want: "Тип тренировки: Бег\nДлительность: 1.00 ч.\nДистанция: 4.72 км.\nСкорость: 4.72 км/ч\nСожгли калорий: 283.50\n", - wantErr: false, - }, - { - name: "ходьба - полчаса", - input: "3000,Ходьба,30m", - weight: 75.0, - height: 1.75, - want: "Тип тренировки: Ходьба\nДлительность: 0.50 ч.\nДистанция: 2.36 км.\nСкорость: 4.72 км/ч\nСожгли калорий: 88.59\n", - wantErr: false, - }, - { - name: "бег - полчаса", - input: "3000,Бег,30m", - weight: 75.0, - height: 1.75, - want: "Тип тренировки: Бег\nДлительность: 0.50 ч.\nДистанция: 2.36 км.\nСкорость: 4.72 км/ч\nСожгли калорий: 177.19\n", - wantErr: false, - }, - { - name: "неизвестный тип тренировки", - input: "6000,Плавание,1h00m", - weight: 75.0, - height: 1.75, - want: "", - wantErr: true, - }, - { - name: "неизвестный тип тренировки - проверка текста ошибки", - input: "6000,Плавание,1h00m", - weight: 75.0, - height: 1.75, - want: "", - wantErr: true, - }, - { - name: "некорректный формат данных", - input: "6000,Ходьба", - weight: 75.0, - height: 1.75, - want: "", - wantErr: true, - }, - { - name: "некорректное количество шагов", - input: "0,Ходьба,1h00m", - weight: 75.0, - height: 1.75, - want: "", - wantErr: true, - }, - { - name: "некорректная продолжительность", - input: "6000,Ходьба,0h00m", - weight: 75.0, - height: 1.75, - want: "", - wantErr: true, - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - got, err := TrainingInfo(tt.input, tt.weight, tt.height) - - if tt.wantErr { - assert.Error(suite.T(), err) - assert.Empty(suite.T(), got) - if tt.name == "неизвестный тип тренировки - проверка текста ошибки" { - assert.Contains(suite.T(), err.Error(), "неизвестный тип тренировки") - } - return - } - - assert.NoError(suite.T(), err) - assert.Equal(suite.T(), tt.want, got) - }) - } -} diff --git a/internal/spentenergy/spentenergy.go b/internal/spentenergy/spentenergy.go new file mode 100644 index 0000000..e70bf69 --- /dev/null +++ b/internal/spentenergy/spentenergy.go @@ -0,0 +1,62 @@ +package spentenergy + +import ( + "fmt" + "time" + + "github.com/St-Ivanov/step-by-step/internal/errors" +) + +// Основные константы, необходимые для расчетов. +const ( + mInKm = 1000 // количество метров в километре. + minInH = 60 // количество минут в часе. + stepLengthCoefficient = 0.45 // коэффициент для расчета длины шага на основе роста. + walkingCaloriesCoefficient = 0.5 // коэффициент для расчета калорий при ходьбе. +) + +func isValidData(steps int, weight, height float64, duration time.Duration) error { + if steps <= 0 { + return fmt.Errorf("%w Exactly the steps.", errors.ErrNegativeValue) + } + if weight <= 0 { + return fmt.Errorf("%w Exactly the weight.", errors.ErrNegativeValue) + } + if height <= 0 { + return fmt.Errorf("%w Exactly the height.", errors.ErrNegativeValue) + } + if duration <= 0 { + return fmt.Errorf("%w Exactly the time.", errors.ErrNegativeValue) + } + return nil +} + +func WalkingSpentCalories(steps int, weight, height float64, duration time.Duration) (float64, error) { + err := isValidData(steps, weight, height, duration) + if err != nil { + return 0, err + } + averageSpeed := MeanSpeed(steps, height, duration) + return (weight * averageSpeed * duration.Minutes()) / minInH * walkingCaloriesCoefficient, nil +} + +func RunningSpentCalories(steps int, weight, height float64, duration time.Duration) (float64, error) { + err := isValidData(steps, weight, height, duration) + if err != nil { + return 0, err + } + averageSpeed := MeanSpeed(steps, height, duration) + return (weight * averageSpeed * duration.Minutes()) / minInH, nil +} + +func MeanSpeed(steps int, height float64, duration time.Duration) float64 { + if duration <= 0 || steps <= 0 { + return 0 + } + distance := Distance(steps, height) + return distance / duration.Hours() +} + +func Distance(steps int, height float64) float64 { + return height * stepLengthCoefficient * float64(steps) / mInKm +} diff --git a/internal/spentenergy/spentenergy_test.go b/internal/spentenergy/spentenergy_test.go new file mode 100644 index 0000000..15f219f --- /dev/null +++ b/internal/spentenergy/spentenergy_test.go @@ -0,0 +1,401 @@ +package spentenergy + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type SpentCaloriesTestSuite struct { + suite.Suite +} + +func TestSpentCaloriesSuite(t *testing.T) { + suite.Run(t, new(SpentCaloriesTestSuite)) +} + +func (suite *SpentCaloriesTestSuite) TestDistance() { + tests := []struct { + name string + steps int + height float64 + wantDist float64 + }{ + { + name: "нормальное количество шагов", + steps: 1000, + height: 1.75, + wantDist: 0.7875, + }, + { + name: "большое количество шагов", + steps: 10000, + height: 1.75, + wantDist: 7.875, + }, + { + name: "маленькое количество шагов", + steps: 100, + height: 1.75, + wantDist: 0.07875, + }, + { + name: "ноль шагов", + steps: 0, + height: 1.75, + wantDist: 0, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + got := Distance(tt.steps, tt.height) + assert.Equal(suite.T(), tt.wantDist, got, "Для тестового случая %q (шаги: %d, рост: %.2f) ожидалось значение %.2f, но получено: %.2f", + tt.name, tt.steps, tt.height, tt.wantDist, got) + }) + } +} + +func (suite *SpentCaloriesTestSuite) TestMeanSpeed() { + tests := []struct { + name string + steps int + height float64 + duration time.Duration + wantSpeed float64 + }{ + { + name: "нормальная скорость - один час", + steps: 6000, + height: 1.75, + duration: 1 * time.Hour, + wantSpeed: 4.725, + }, + { + name: "нормальная скорость - полчаса", + steps: 3000, + height: 1.75, + duration: 30 * time.Minute, + wantSpeed: 4.725, + }, + { + name: "нормальная скорость - два часа", + steps: 12000, + height: 1.75, + duration: 2 * time.Hour, + wantSpeed: 4.725, + }, + { + name: "маленькая скорость", + steps: 1000, + height: 1.75, + duration: 2 * time.Hour, + wantSpeed: 0.39375, + }, + { + name: "большая скорость", + steps: 20000, + height: 1.75, + duration: 1 * time.Hour, + wantSpeed: 15.75, + }, + { + name: "нулевая продолжительность", + steps: 1000, + height: 1.75, + duration: 0, + wantSpeed: 0, + }, + { + name: "отрицательная продолжительность", + steps: 1000, + height: 1.75, + duration: -1 * time.Hour, + wantSpeed: 0, + }, + { + name: "ноль шагов", + steps: 0, + height: 1.75, + duration: 1 * time.Hour, + wantSpeed: 0, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + got := MeanSpeed(tt.steps, tt.height, tt.duration) + assert.Equal(suite.T(), tt.wantSpeed, got, "Для тестового случая %q (шаги: %d, рост: %.2f, продолжительность: %v) ожидалось значение %.2f, но получено: %.2f", + tt.name, tt.steps, tt.height, tt.duration, tt.wantSpeed, got) + }) + } +} + +func (suite *SpentCaloriesTestSuite) TestRunningSpentCalories() { + tests := []struct { + name string + steps int + weight float64 + height float64 + duration time.Duration + wantCal float64 + wantErr bool + }{ + { + name: "нормальная нагрузка - один час", + steps: 6000, + weight: 75.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 354.375, + wantErr: false, + }, + { + name: "нормальная нагрузка - полчаса", + steps: 3000, + weight: 75.0, + height: 1.75, + duration: 30 * time.Minute, + wantCal: 177.1875, + wantErr: false, + }, + { + name: "высокая скорость", + steps: 20000, + weight: 75.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 1181.25, + wantErr: false, + }, + { + name: "низкая скорость", + steps: 1000, + weight: 75.0, + height: 1.75, + duration: 2 * time.Hour, + wantCal: 59.0625, + wantErr: false, + }, + { + name: "другой вес", + steps: 6000, + weight: 60.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 283.5, + wantErr: false, + }, + { + name: "нулевая продолжительность", + steps: 1000, + weight: 75.0, + height: 1.75, + duration: 0, + wantCal: 0, + wantErr: true, + }, + { + name: "отрицательная продолжительность", + steps: 1000, + weight: 75.0, + height: 1.75, + duration: -1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + { + name: "ноль шагов", + steps: 0, + weight: 75.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + { + name: "отрицательные шаги", + steps: -1000, + weight: 75.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + { + name: "нулевой вес", + steps: 1000, + weight: 0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + { + name: "отрицательный вес", + steps: 1000, + weight: -75.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + { + name: "отрицательный рост", + steps: 1000, + weight: 75.0, + height: -1.75, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + gotCal, gotErr := RunningSpentCalories(tt.steps, tt.weight, tt.height, tt.duration) + + if tt.wantErr { + require.Error(suite.T(), gotErr, "Для тестового случая %q (шаги: %d, вес: %.1f, рост: %.2f, продолжительность: %v) ожидалась ошибка, но её нет", + tt.name, tt.steps, tt.weight, tt.height, tt.duration) + assert.Equal(suite.T(), 0.0, gotCal, "Для тестового случая %q (шаги: %d, вес: %.1f, рост: %.2f, продолжительность: %v) ожидалось значение 0.0, но получено: %.2f", + tt.name, tt.steps, tt.weight, tt.height, tt.duration, gotCal) + return + } + + require.NoError(suite.T(), gotErr) + assert.InDelta(suite.T(), tt.wantCal, gotCal, 0.1, "Для тестового случая %q (шаги: %d, вес: %.1f, рост: %.2f, продолжительность: %v) ожидалось значение %.2f, но получено: %.2f", + tt.name, tt.steps, tt.weight, tt.height, tt.duration, tt.wantCal, gotCal) + }) + } +} + +func (suite *SpentCaloriesTestSuite) TestWalkingSpentCalories() { + tests := []struct { + name string + steps int + weight float64 + height float64 + duration time.Duration + wantCal float64 + wantErr bool + }{ + { + name: "нормальная нагрузка", + steps: 6000, + weight: 75.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 177.19, + wantErr: false, + }, + { + name: "меньше шагов", + steps: 3000, + weight: 75.0, + height: 1.75, + duration: 30 * time.Minute, + wantCal: 88.594, + wantErr: false, + }, + { + name: "больше шагов", + steps: 20000, + weight: 75.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 590.62, + wantErr: false, + }, + { + name: "другой вес", + steps: 6000, + weight: 60.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 141.75, + wantErr: false, + }, + { + name: "другой рост", + steps: 6000, + weight: 75.0, + height: 1.85, + duration: 1 * time.Hour, + wantCal: 187.313, + wantErr: false, + }, + { + name: "нулевые шаги", + steps: 0, + weight: 75.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + { + name: "отрицательные шаги", + steps: -1000, + weight: 75.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + { + name: "нулевой вес", + steps: 6000, + weight: 0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + { + name: "отрицательный вес", + steps: 6000, + weight: -75.0, + height: 1.75, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + { + name: "нулевой рост", + steps: 6000, + weight: 75.0, + height: 0, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + { + name: "отрицательный рост", + steps: 6000, + weight: 75.0, + height: -1.75, + duration: 1 * time.Hour, + wantCal: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + gotCal, gotErr := WalkingSpentCalories(tt.steps, tt.weight, tt.height, tt.duration) + + if tt.wantErr { + assert.Error(suite.T(), gotErr) + assert.Equal(suite.T(), 0.0, gotCal) + return + } + + assert.NoError(suite.T(), gotErr) + assert.InDelta(suite.T(), tt.wantCal, gotCal, 0.1) + }) + } +} diff --git a/internal/trainings/trainings.go b/internal/trainings/trainings.go new file mode 100644 index 0000000..f574e97 --- /dev/null +++ b/internal/trainings/trainings.go @@ -0,0 +1,74 @@ +package trainings + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/St-Ivanov/step-by-step/internal/errors" + "github.com/St-Ivanov/step-by-step/internal/personaldata" + "github.com/St-Ivanov/step-by-step/internal/spentenergy" +) + +type Training struct { + Steps int + TrainingType string + Duration time.Duration + personaldata.Personal +} + +func (t *Training) Parse(datastring string) (err error) { + data := strings.Split(datastring, ",") + if len(data) != 3 { + return errors.ErrIncDataEnt + } + steps, err := strconv.Atoi(data[0]) + if err != nil { + return errors.ErrConvToInt + } + if steps <= 0 { + return fmt.Errorf("%w Exactly the steps.", errors.ErrNegativeValue) + } + duration, err := time.ParseDuration(data[2]) + if err != nil { + return errors.ErrConvToTime + } + if duration <= 0 { + return fmt.Errorf("%w Exactly the time.", errors.ErrNegativeValue) + } + t.Steps = steps + t.TrainingType = data[1] + t.Duration = duration + return nil +} + +func (t Training) ActionInfo() (string, error) { + distance := spentenergy.Distance(t.Steps, t.Height) + averageSpeed := spentenergy.MeanSpeed(t.Steps, t.Height, t.Duration) + var ( + calories float64 + err error + ) + switch t.TrainingType { + case "Ходьба": + calories, err = spentenergy.WalkingSpentCalories(t.Steps, t.Weight, t.Height, t.Duration) + if err != nil { + return "", err + } + case "Бег": + calories, err = spentenergy.RunningSpentCalories(t.Steps, t.Weight, t.Height, t.Duration) + if err != nil { + return "", err + } + default: + return "", errors.ErrIncUndTraining + } + s := fmt.Sprintf(`Тип тренировки: %s +Длительность: %0.2f ч. +Дистанция: %0.2f км. +Скорость: %0.2f км/ч +Сожгли калорий: %0.2f +`, t.TrainingType, t.Duration.Hours(), distance, averageSpeed, calories) + return s, nil +} diff --git a/internal/trainings/trainings_test.go b/internal/trainings/trainings_test.go new file mode 100644 index 0000000..d28d975 --- /dev/null +++ b/internal/trainings/trainings_test.go @@ -0,0 +1,337 @@ +package trainings + +import ( + "testing" + "time" + + "github.com/St-Ivanov/step-by-step/internal/personaldata" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type SpentCaloriesTestSuite struct { + suite.Suite +} + +func TestSpentCaloriesSuite(t *testing.T) { + suite.Run(t, new(SpentCaloriesTestSuite)) +} + +func (suite *SpentCaloriesTestSuite) TestParseTraining() { + tests := []struct { + name string + input string + wantSteps int + wantType string + wantDuration time.Duration + wantErr bool + }{ + { + name: "корректный ввод с часами и минутами", + input: "3456,Ходьба,3h00m", + wantSteps: 3456, + wantType: "Ходьба", + wantDuration: 3 * time.Hour, + wantErr: false, + }, + { + name: "корректный ввод с минутами", + input: "678,Бег,5m", + wantSteps: 678, + wantType: "Бег", + wantDuration: 5 * time.Minute, + wantErr: false, + }, + { + name: "положительное число с плюсом", + input: "+12345,Ходьба,1h30m", + wantSteps: 12345, + wantType: "Ходьба", + wantDuration: 90 * time.Minute, + wantErr: false, + }, + { + name: "продолжительность - только минуты", + input: "1000,Бег,30m", + wantSteps: 1000, + wantType: "Бег", + wantDuration: 30 * time.Minute, + wantErr: false, + }, + { + name: "продолжительность - только часы", + input: "1000,Ходьба,2h", + wantSteps: 1000, + wantType: "Ходьба", + wantDuration: 2 * time.Hour, + wantErr: false, + }, + { + name: "продолжительность - дробные часы", + input: "1000,Бег,1.5h", + wantSteps: 1000, + wantType: "Бег", + wantDuration: 90 * time.Minute, + wantErr: false, + }, + { + name: "продолжительность - дробные минуты", + input: "1000,Ходьба,30.5m", + wantSteps: 1000, + wantType: "Ходьба", + wantDuration: 30*time.Minute + 30*time.Second, + wantErr: false, + }, + { + name: "неверный формат - неправильное количество параметров", + input: "678,Ходьба", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверный формат - четыре параметра", + input: "678,Ходьба,1h30m,extra", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "пустой ввод", + input: "", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверные шаги - не числовое значение", + input: "abc,Ходьба,1h30m", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверные шаги - ноль", + input: "0,Ходьба,1h30m", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверные шаги - отрицательное значение", + input: "-100,Ходьба,1h30m", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверные шаги - только знак минус", + input: "-,Ходьба,1h30m", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверные шаги - только знак плюс", + input: "+,Ходьба,1h30m", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверный формат продолжительности", + input: "678,Ходьба,invalid", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверная продолжительность - ноль", + input: "678,Бег,0h0m", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверная продолжительность - отрицательное значение", + input: "678,Ходьба,-1h30m", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверная продолжительность - отрицательные минуты", + input: "678,Бег,1h-30m", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверная продолжительность - неверная единица измерения", + input: "678,Ходьба,1.5d", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверная продолжительность - пробел между числом и единицей", + input: "678,Бег,1 h30m", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + { + name: "неверная продолжительность - пропущена единица измерения", + input: "678,Ходьба,30", + wantSteps: 0, + wantType: "", + wantDuration: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + training := &Training{} + err := training.Parse(tt.input) + + if tt.wantErr { + require.Error(suite.T(), err) + return + } + + require.NoError(suite.T(), err) + assert.Equal(suite.T(), tt.wantSteps, training.Steps) + assert.Equal(suite.T(), tt.wantType, training.TrainingType) + assert.Equal(suite.T(), tt.wantDuration, training.Duration) + }) + } +} + +func (suite *SpentCaloriesTestSuite) TestActionInfo() { + tests := []struct { + name string + input string + weight float64 + height float64 + want string + wantErr bool + }{ + { + name: "ходьба - нормальная нагрузка", + input: "6000,Ходьба,1h00m", + weight: 75.0, + height: 1.75, + want: "Тип тренировки: Ходьба\nДлительность: 1.00 ч.\nДистанция: 4.72 км.\nСкорость: 4.72 км/ч\nСожгли калорий: 177.19\n", + wantErr: false, + }, + { + name: "бег - нормальная нагрузка", + input: "6000,Бег,1h00m", + weight: 75.0, + height: 1.75, + want: "Тип тренировки: Бег\nДлительность: 1.00 ч.\nДистанция: 4.72 км.\nСкорость: 4.72 км/ч\nСожгли калорий: 354.38\n", + wantErr: false, + }, + { + name: "ходьба - высокая скорость", + input: "20000,Ходьба,1h00m", + weight: 75.0, + height: 1.75, + want: "Тип тренировки: Ходьба\nДлительность: 1.00 ч.\nДистанция: 15.75 км.\nСкорость: 15.75 км/ч\nСожгли калорий: 590.62\n", + wantErr: false, + }, + { + name: "бег - высокая скорость", + input: "20000,Бег,1h00m", + weight: 75.0, + height: 1.75, + want: "Тип тренировки: Бег\nДлительность: 1.00 ч.\nДистанция: 15.75 км.\nСкорость: 15.75 км/ч\nСожгли калорий: 1181.25\n", + wantErr: false, + }, + { + name: "ходьба - другой вес и рост", + input: "6000,Ходьба,1h00m", + weight: 60.0, + height: 1.85, + want: "Тип тренировки: Ходьба\nДлительность: 1.00 ч.\nДистанция: 5.00 км.\nСкорость: 5.00 км/ч\nСожгли калорий: 149.85\n", + wantErr: false, + }, + { + name: "бег - другой вес", + input: "6000,Бег,1h00m", + weight: 60.0, + height: 1.75, + want: "Тип тренировки: Бег\nДлительность: 1.00 ч.\nДистанция: 4.72 км.\nСкорость: 4.72 км/ч\nСожгли калорий: 283.50\n", + wantErr: false, + }, + { + name: "ходьба - полчаса", + input: "3000,Ходьба,30m", + weight: 75.0, + height: 1.75, + want: "Тип тренировки: Ходьба\nДлительность: 0.50 ч.\nДистанция: 2.36 км.\nСкорость: 4.72 км/ч\nСожгли калорий: 88.59\n", + wantErr: false, + }, + { + name: "бег - полчаса", + input: "3000,Бег,30m", + weight: 75.0, + height: 1.75, + want: "Тип тренировки: Бег\nДлительность: 0.50 ч.\nДистанция: 2.36 км.\nСкорость: 4.72 км/ч\nСожгли калорий: 177.19\n", + wantErr: false, + }, + { + name: "неизвестный тип тренировки", + input: "6000,Плавание,1h00m", + weight: 75.0, + height: 1.75, + want: "", + wantErr: true, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + training := &Training{ + Personal: personaldata.Personal{ + Weight: tt.weight, + Height: tt.height, + }, + } + + err := training.Parse(tt.input) + require.NoError(suite.T(), err, "Для тестового случая %q (ввод: %q, вес: %.1f, рост: %.2f) ожидалась, что парсер сможет распознать ввод, но он не смог", + tt.name, tt.input, tt.weight, tt.height) + + got, err := training.ActionInfo() + if tt.wantErr { + require.Error(suite.T(), err, "Для тестового случая %q (ввод: %q, вес: %.1f, рост: %.2f) ожидалась, что ActionInfo() вернет ошибку, но она не вернулась", + tt.name, tt.input, tt.weight, tt.height) + assert.Empty(suite.T(), got, "Для тестового случая %q (ввод: %q, вес: %.1f, рост: %.2f) ожидалась, что ActionInfo() вернет пустую строку, но вернулась: %q", + tt.name, tt.input, tt.weight, tt.height, got) + return + } + + require.NoError(suite.T(), err, "Для тестового случая %q (ввод: %q, вес: %.1f, рост: %.2f) ожидалась, что ActionInfo() вернет ошибку, но она не вернулась", + tt.name, tt.input, tt.weight, tt.height) + assert.Equal(suite.T(), tt.want, got, "Для тестового случая %q (ввод: %q, вес: %.1f, рост: %.2f) ожидалось: %q, но вернулось: %q", + tt.name, tt.input, tt.weight, tt.height, tt.want, got) + }) + } +} From 07e4856a2ce5f86c1f9c1547a596de75c5c6b31e Mon Sep 17 00:00:00 2001 From: "Ivanov, Stepan Ivanov" Date: Sun, 11 Jan 2026 01:51:12 +0300 Subject: [PATCH 6/6] sprint5 --- cmd/tracker/main.go | 49 ++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/cmd/tracker/main.go b/cmd/tracker/main.go index 4900b5f..689c846 100644 --- a/cmd/tracker/main.go +++ b/cmd/tracker/main.go @@ -2,15 +2,19 @@ package main import ( "fmt" - "log" + "github.com/St-Ivanov/step-by-step/internal/actioninfo" "github.com/St-Ivanov/step-by-step/internal/daysteps" - "github.com/St-Ivanov/step-by-step/internal/spentcalories" + "github.com/St-Ivanov/step-by-step/internal/personaldata" + "github.com/St-Ivanov/step-by-step/internal/trainings" ) func main() { - weight := 84.6 - height := 1.87 + person := personaldata.Personal{ + Name: "Витя", + Weight: 84.6, + Height: 1.87, + } // дневная активность input := []string{ @@ -25,22 +29,16 @@ func main() { fmt.Println("Активность в течение дня") - var ( - dayActionsInfo string - dayActionsLog []string - ) - - for _, v := range input { - dayActionsInfo = daysteps.DayActionInfo(v, weight, height) - dayActionsLog = append(dayActionsLog, dayActionsInfo) + daySteps := daysteps.DaySteps{ + Personal: person, } - for _, v := range dayActionsLog { - fmt.Println(v) - } + daySteps.Print() + + actioninfo.Info(input, &daySteps) - // тренировки - trainings := []string{ + // // тренировки + actions := []string{ "3456,Ходьба,3h00m", "something is wrong", "678,Бег,0h5m", @@ -50,20 +48,13 @@ func main() { "15392,Бег,0h45m", } - var trainingLog []string - - for _, v := range trainings { - trainingInfo, err := spentcalories.TrainingInfo(v, weight, height) - if err != nil { - log.Printf("не получилось получить информацию о тренировке: %v", err) - continue - } - trainingLog = append(trainingLog, trainingInfo) + trains := trainings.Training{ + Personal: person, } fmt.Println("Журнал тренировок") - for _, v := range trainingLog { - fmt.Println(v) - } + trains.Print() + + actioninfo.Info(actions, &trains) }