Enhance Ginkgo unit test execution by specifying directories for tests in Makefile and Taskfile. Update README to reflect changes in test command usage and clarify the structure of unit tests.

This commit is contained in:
windyboy
2025-11-16 10:18:40 +08:00
parent 69fc8cf337
commit e440c057b5
4 changed files with 56 additions and 16 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ run-local: ## Run receiver directly via go run
test: ## Run unit tests (Ginkgo, verbose)
@command -v ginkgo >/dev/null || (echo "Please install ginkgo (go install github.com/onsi/ginkgo/v2/ginkgo@latest)"; exit 1)
@echo "Running Ginkgo unit test suites (verbose)..."
@ginkgo -r -v ./...
@ginkgo -r -v ./cmd ./internal
.PHONY: test-int
test-int: ## Run integration tests (requires Docker)
+2 -2
View File
@@ -318,7 +318,7 @@ Dependencies are managed using Google Wire. To add a new dependency:
The project keeps tests close to the code that they exercise:
- **Domain/adapter/app unit tests** live under `internal/**` and cover parsing, validation, orchestration, and adapters. Run them all with `task test` (or `make test`), which now uses the Ginkgo CLI to run unit test suites in verbose mode (`ginkgo -r -v ./...`).
- **Domain/adapter/app unit tests** live under `internal/**` and cover parsing, validation, orchestration, and adapters. Run them all with `task test` (or `make test`), which now uses the Ginkgo CLI to run unit test suites in verbose mode (`ginkgo -r -v ./cmd ./internal`).
- **Integration tests** under `test/integration` spin up disposable TimescaleDB and NATS JetStream instances (via `testcontainers-go`) and execute a full ingestion flow. Use `task test-int` after ensuring Docker is running.
- **Coverage goals** are tracked via `task coverage`, which produces both a coverage profile and an HTML report under `coverage/coverage.html`.
@@ -330,7 +330,7 @@ The project keeps tests close to the code that they exercise:
| Generate coverage html | `make coverage` | `task coverage` |
| Lint (golangci-lint) | `make lint` | `task lint` |
> Integration tests need Docker available on the host. Ginkgo-based unit tests or lint targets require the respective binaries (`go install github.com/onsi/ginkgo/v2/ginkgo@latest`, [golangci-lint install guide](https://golangci-lint.run/)). Use `task install-test` to bootstrap Ginkgo tooling before running `task test`, `task test-all`, or their `make` equivalents. `make test` / `task test` run Ginkgo in verbose mode (`-v`), showing each spec for easier debugging.
> Integration tests need Docker available on the host. Ginkgo-based unit tests or lint targets require the respective binaries (`go install github.com/onsi/ginkgo/v2/ginkgo@latest`, [golangci-lint install guide](https://golangci-lint.run/)). Use `task install-test` to bootstrap Ginkgo tooling before running `task test`, `task test-all`, or their `make` equivalents. `make test` / `task test` run Ginkgo in verbose mode (`-v`) over `./cmd` and `./internal`, showing each spec for easier debugging.
## Message Flow
+1 -1
View File
@@ -63,7 +63,7 @@ tasks:
exit 1
fi
- echo "Running Ginkgo unit test suites (verbose)..."
- ginkgo -r -v ./...
- ginkgo -r -v ./cmd ./internal
test-int:
desc: Run integration tests (requires Docker)
+51 -11
View File
@@ -76,8 +76,30 @@ func main() {
for i := 0; i < *count; i++ {
cat := categories[rand.Intn(len(categories))]
payload := buildTelegram(cat)
payload, intentionallyInvalid := buildTelegram(cat)
// 当状态为 random 时,根据报文是否合法来倾向选择 parsed 或 body_error
if strings.ToLower(*status) == "random" {
if intentionallyInvalid {
// 故意非法的报文:大概率标记为 body_error
if rand.Intn(100) < 80 {
payload.Status = "body_error"
} else {
payload.Status = statusValues[rand.Intn(len(statusValues))]
}
} else {
// 合法报文:大概率标记为 parsed
if rand.Intn(100) < 70 {
payload.Status = "parsed"
} else {
payload.Status = statusValues[rand.Intn(len(statusValues))]
}
}
} else {
// 非 random 模式下沿用原有逻辑
payload.Status = statuses[rand.Intn(len(statuses))]
}
payload.ErrorReason = *errorReason
payload.Metadata = map[string]string{
"message_id": payload.MessageID,
@@ -140,7 +162,7 @@ type telegram struct {
Metadata map[string]string `json:"metadata"`
}
func buildTelegram(category string) *telegram {
func buildTelegram(category string) (*telegram, bool) {
now := time.Now().UTC()
messageID := fmt.Sprintf("%s%04d", category, rand.Intn(9000)+1000)
headerTime := now.Format("020304")
@@ -149,7 +171,7 @@ func buildTelegram(category string) *telegram {
originLine := originatorLines[rand.Intn(len(originatorLines))]
originator := originators[rand.Intn(len(originators))]
body := buildBody(category)
body, intentionallyInvalid := buildBody(category)
content := strings.Join([]string{
fmt.Sprintf("ZCZC %s %s", messageID, headerTime),
@@ -166,10 +188,10 @@ func buildTelegram(category string) *telegram {
Category: category,
Content: content,
ReceivedAt: now,
}
}, intentionallyInvalid
}
func buildBody(category string) string {
func buildBody(category string) (string, bool) {
flight := fmt.Sprintf("%s%04d", []string{"CCA", "SWA", "DLH", "AAL", "JAE"}[rand.Intn(5)], rand.Intn(9000)+1000)
dep := airports[rand.Intn(len(airports))]
arr := airports[rand.Intn(len(airports))]
@@ -179,15 +201,23 @@ func buildBody(category string) string {
switch category {
case "ARR":
if rand.Intn(2) == 0 {
return fmt.Sprintf("(ARR-%s-%s-%s%s)", flight, dep, arr, arrTime)
return fmt.Sprintf("(ARR-%s-%s-%s%s)", flight, dep, arr, arrTime), false
}
return fmt.Sprintf("(ARR-%s/%s-%s-%s%s)", flight, randomSSR(), dep, arr, arrTime)
// 70% 使用简单 SSR(合法),30% 使用复杂 SSR(当前正则下非法)
if rand.Intn(100) < 30 {
return fmt.Sprintf("(ARR-%s/%s-%s-%s%s)", flight, randomComplexSSR(), dep, arr, arrTime), true
}
return fmt.Sprintf("(ARR-%s/%s-%s-%s%s)", flight, randomSimpleSSR(), dep, arr, arrTime), false
case "DEP":
return fmt.Sprintf("(DEP-%s/%s-%s%s-%s)", flight, randomSSR(), dep, depTime, arr)
// 70% 使用简单 SSR(合法),30% 使用复杂 SSR(当前正则下非法)
if rand.Intn(100) < 30 {
return fmt.Sprintf("(DEP-%s/%s-%s%s-%s)", flight, randomComplexSSR(), dep, depTime, arr), true
}
return fmt.Sprintf("(DEP-%s/%s-%s%s-%s)", flight, randomSimpleSSR(), dep, depTime, arr), false
case "CNL":
return fmt.Sprintf("(CNL-%s-%s-%s)", flight, dep, arr)
return fmt.Sprintf("(CNL-%s-%s-%s)", flight, dep, arr), false
case "DLA":
return fmt.Sprintf("(DLA-%s-%s%s-%s)", flight, dep, depTime, arr)
return fmt.Sprintf("(DLA-%s-%s%s-%s)", flight, dep, depTime, arr), false
default: // FPL
return fmt.Sprintf(`(FPL-%s-IS
-%s/H
@@ -208,7 +238,7 @@ func buildBody(category string) string {
arrTime,
randomAirportPair(),
randomOtherInfo(),
)
), false
}
}
@@ -216,6 +246,16 @@ func randomSSR() string {
return []string{"A0132", "A5633", "SXIRPZJWY/LB101", "SHID/C"}[rand.Intn(4)]
}
// 与当前 ARR/DEP 正则匹配的简单 SSR
func randomSimpleSSR() string {
return []string{"A0132", "A5633"}[rand.Intn(2)]
}
// 故意构造为当前 ARR/DEP 正则无法解析的复杂 SSR
func randomComplexSSR() string {
return []string{"SXIRPZJWY/LB101", "SHID/C"}[rand.Intn(2)]
}
func randomAircraft() string {
return []string{"A332", "B788", "MA60", "A359"}[rand.Intn(4)]
}