// cmd/testrunner/runner.go - Дополнительные утилиты package main import ( "encoding/json" "encoding/xml" "fmt" "os" "os/exec" "strings" "time" ) // Сохранение результатов в JSON func saveResultsToJSON(results []TestResult, filename string) error { type JSONResult struct { Timestamp string `json:"timestamp"` Results []TestResult `json:"results"` Summary struct { Total int `json:"total"` Passed int `json:"passed"` Failed int `json:"failed"` PassRate float64 `json:"pass_rate"` } `json:"summary"` } var jsonResult JSONResult jsonResult.Timestamp = time.Now().Format(time.RFC3339) jsonResult.Results = results for _, r := range results { jsonResult.Summary.Total++ if r.Passed { jsonResult.Summary.Passed++ } else { jsonResult.Summary.Failed++ } } if jsonResult.Summary.Total > 0 { jsonResult.Summary.PassRate = float64(jsonResult.Summary.Passed) / float64(jsonResult.Summary.Total) * 100 } data, err := json.MarshalIndent(jsonResult, "", " ") if err != nil { return err } return os.WriteFile(filename, data, 0644) } // Создание отчета в формате JUnit XML (для CI/CD) func generateJUnitReport(results []TestResult, filename string) error { type TestCase struct { Name string `xml:"name,attr"` Classname string `xml:"classname,attr"` Time float64 `xml:"time,attr"` Failure *string `xml:"failure,omitempty"` } type TestSuite struct { Name string `xml:"name,attr"` Tests int `xml:"tests,attr"` Failures int `xml:"failures,attr"` Time float64 `xml:"time,attr"` TestCases []TestCase `xml:"testcase"` } type TestSuites struct { XMLName string `xml:"testsuites"` TestSuites []TestSuite `xml:"testsuite"` } var suites TestSuites for _, result := range results { suite := TestSuite{ Name: result.Suite, Tests: 1, Time: result.Duration.Seconds(), } testCase := TestCase{ Name: fmt.Sprintf("%s Suite", result.Suite), Classname: "api.tests", Time: result.Duration.Seconds(), } if !result.Passed { suite.Failures = 1 failureMsg := result.Error.Error() testCase.Failure = &failureMsg } suite.TestCases = append(suite.TestCases, testCase) suites.TestSuites = append(suites.TestSuites, suite) } xmlData, err := xml.MarshalIndent(suites, "", " ") if err != nil { return err } return os.WriteFile(filename, append([]byte(xml.Header), xmlData...), 0644) } // Функция для запуска определенных тестов по тегам func runTestsByTag(tag string, config *Config) []TestResult { fmt.Printf("%s🏷️ Running tests with tag: %s%s\n", colors.Yellow, tag, colors.Reset) cmd := exec.Command("go", "test", "-v", "-tags", tag, "./tests/integration/...") output, err := cmd.CombinedOutput() result := TestResult{ Suite: fmt.Sprintf("Tag: %s", tag), Output: string(output), Passed: err == nil, Error: err, } if result.Passed { fmt.Printf("%s✅ Tests with tag '%s' passed%s\n", colors.Green, tag, colors.Reset) } else { fmt.Printf("%s❌ Tests with tag '%s' failed%s\n", colors.Red, tag, colors.Reset) } return []TestResult{result} } // Создание скрипта для CI/CD func generateCIScript() { ciScript := `#!/bin/bash # CI/CD script for API tests set -e echo "Starting API Tests in CI environment" # Запуск сервера в фоне go run cmd/main.go & SERVER_PID=$! # Ждем запуска сервера sleep 5 # Запуск тестов go run cmd/testrunner/main.go -suite all -coverage # Остановка сервера kill $SERVER_PID echo "Tests completed" ` if err := os.WriteFile("scripts/ci_test.sh", []byte(ciScript), 0755); err != nil { fmt.Printf("Failed to generate CI script: %v\n", err) } } // Функция для сравнения результатов тестов func compareResults(current, previous []TestResult) { fmt.Printf("%s📊 Test Result Comparison%s\n", colors.Cyan, colors.Reset) fmt.Println(strings.Repeat("-", 50)) currentMap := make(map[string]TestResult) for _, r := range current { currentMap[r.Suite] = r } prevMap := make(map[string]TestResult) for _, r := range previous { prevMap[r.Suite] = r } for name, currentResult := range currentMap { prevResult, exists := prevMap[name] if !exists { fmt.Printf("%s➕ New test suite: %s%s\n", colors.Green, name, colors.Reset) continue } if currentResult.Passed != prevResult.Passed { if currentResult.Passed { fmt.Printf("%s✅ Fixed: %s (was failing)%s\n", colors.Green, name, colors.Reset) } else { fmt.Printf("%s❌ Regression: %s (was passing)%s\n", colors.Red, name, colors.Reset) } } else if currentResult.Passed { // Сравнение времени выполнения diff := currentResult.Duration - prevResult.Duration if diff > 0 { fmt.Printf("%s⚠️ Slower: %s (+%v)%s\n", colors.Yellow, name, diff.Round(time.Millisecond), colors.Reset) } else if diff < 0 { fmt.Printf("%s⚡ Faster: %s (%v)%s\n", colors.Green, name, (-diff).Round(time.Millisecond), colors.Reset) } } } }