Added unit tests

This commit is contained in:
2025-06-20 09:47:04 +02:00
parent 0f487e3f93
commit a987c0fb34
5 changed files with 182 additions and 1 deletions

19
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Tests
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go stable
uses: actions/setup-go@v5
with:
go-version: 'stable'
- name: "Running unit tests"
run: go test ./... -v

View File

@@ -8,6 +8,9 @@ WORKDIR /app
# Copy source files # Copy source files
COPY . . COPY . .
# Run unit tests
RUN go test ./... -v
# Build the application # Build the application
RUN go build -ldflags="-s -w" -o zephyr RUN go build -ldflags="-s -w" -o zephyr

View File

@@ -4,6 +4,7 @@
<h6><i>real-time weather forecast service</i></h6> <h6><i>real-time weather forecast service</i></h6>
[![](https://github.com/ceticamarco/zephyr/actions/workflows/docker.yml/badge.svg)](https://github.com/ceticamarco/zephyr/actions/workflows/docker.yml) [![](https://github.com/ceticamarco/zephyr/actions/workflows/docker.yml/badge.svg)](https://github.com/ceticamarco/zephyr/actions/workflows/docker.yml)
[![](https://github.com/ceticamarco/zephyr/actions/workflows/tests.yml/badge.svg)](https://github.com/ceticamarco/zephyr/actions/workflows/tests.yml)
</div> </div>
@@ -163,7 +164,7 @@ will yield
} }
``` ```
> [!INFO] > [!NOTE]
> To convert OpenWeatherMap's moon phase value to the illumination percentage, > To convert OpenWeatherMap's moon phase value to the illumination percentage,
> I've used the following formula: > I've used the following formula:
> >
@@ -333,5 +334,12 @@ This will build the container image and start the service in detached mode. By d
the service will be available at `http://127.0.0.1:3000`, but you can easily change this property the service will be available at `http://127.0.0.1:3000`, but you can easily change this property
but editing the `compose.yml` as stated above. but editing the `compose.yml` as stated above.
## Unit tests
You can run the unit tests by issuing the following command:
```sh
go test ./... -v
```
## License ## License
This software is released under the GPLv3 license. You can find a copy of the license with this repository or by visiting the [following page](https://choosealicense.com/licenses/gpl-3.0/). This software is released under the GPLv3 license. You can find a copy of the license with this repository or by visiting the [following page](https://choosealicense.com/licenses/gpl-3.0/).

28
model/windModel_test.go Normal file
View File

@@ -0,0 +1,28 @@
package model
import (
"testing"
)
type TestEntry struct {
Name string
Input float64
Expected string
}
func TestGetCardinalDir(t *testing.T) {
tests := []TestEntry{
{"Bounded value", 65.4, "ENE"},
{"Out of bound value", 450.3, "E"},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
got, _ := GetCardinalDir(test.Input)
if got != test.Expected {
t.Errorf("Got %s, wanted %s", got, test.Expected)
}
})
}
}

View File

@@ -0,0 +1,123 @@
package statistics
import (
"math"
"testing"
)
type TestEntry struct {
Name string
Input []float64
Expected float64
}
func cmpVal(x, y float64) bool {
const epsilon = 1e-9
return math.Abs(x-y) < epsilon
}
func TestMean(t *testing.T) {
tests := []TestEntry{
{"Empty list", []float64{}, 0},
{"Single element", []float64{5.0}, 5.0},
{"Multiple elements", []float64{2.3, 6.4, -2.2, 8.4}, 3.725},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
got := Mean(test.Input)
if !cmpVal(got, test.Expected) {
t.Errorf("Got %v, wanted %v", got, test.Expected)
}
})
}
}
func TestStdDev(t *testing.T) {
tests := []TestEntry{
{"Empty list", []float64{}, 0},
{"Single element", []float64{5.0}, 0},
{"Multiple elements", []float64{5.0, -4.2, 3.4, 7.2}, 4.288064831599448},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
got := StdDev(test.Input)
if !cmpVal(got, test.Expected) {
t.Errorf("Got %v, wanted %v", got, test.Expected)
}
})
}
}
func TestMedian(t *testing.T) {
tests := []TestEntry{
{"Empty list", []float64{}, 0},
{"Single element", []float64{5.0}, 5.0},
{"Multiple elements (even)", []float64{5.0, -4.2, 3.4, 7.2}, 4.2},
{"Multiple elements (odd)", []float64{5.0, -4.2, 1.4, 3.4, 7.2}, 3.4},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
got := Median(test.Input)
if !cmpVal(got, test.Expected) {
t.Errorf("Got %v, wanted %v", got, test.Expected)
}
})
}
}
func TestMode(t *testing.T) {
tests := []TestEntry{
{"Empty list", []float64{}, 0},
{"Single element", []float64{5.0}, 5.0},
{"Unique modes", []float64{1.0, 2.0, 2.0, 3.0}, 2.0},
{"Multi-modal", []float64{1.0, 1.0, 2.0, 3.0, 3.0}, 3.0},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
got := Mode(test.Input)
if !cmpVal(got, test.Expected) {
t.Errorf("Got %v, wanted %v", got, test.Expected)
}
})
}
}
func TestRobustZScore(t *testing.T) {
// Gaussian distributed dataset representing "normal"
// temperatures; that is, without anomalies
normalTemps := []float64{
18.0, 19.0, 19.0, 20.0, 20.0,
20.0, 21.0, 21.0, 21.0, 21.0,
22.0, 22.0, 22.0, 22.0, 22.0,
23.0, 23.0, 23.0, 24.0, 24.0,
}
tests := []TestEntry{
{"Empty list", []float64{}, 0},
{"Single element", []float64{20.0}, 0},
{"Temperatures without anomalies", normalTemps, 0},
{"High anomaly", append(normalTemps, 30.0), 30.0},
{"Low anomaly", append(normalTemps, 5.0), 5.0},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
got := RobustZScore(test.Input)
if len(got) != 0 {
if !cmpVal(got[0].Value, test.Expected) {
t.Errorf("Got %v, wanted %v", got, test.Expected)
}
} else {
if test.Expected != 0 {
t.Errorf("Got [], wanted %v", test.Expected)
}
}
})
}
}