Added unit tests
This commit is contained in:
19
.github/workflows/tests.yml
vendored
Normal file
19
.github/workflows/tests.yml
vendored
Normal 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
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -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)
|
[](https://github.com/ceticamarco/zephyr/actions/workflows/docker.yml)
|
||||||
|
[](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
28
model/windModel_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
123
statistics/primitives_test.go
Normal file
123
statistics/primitives_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user