Local Testing
Testing modules locally before deployment helps catch issues early. This guide covers local development and testing strategies.
Running Locally
Basic Run
bash
cd my-module
tinysystems runWith Debug Output
bash
tinysystems run --debugWatch Mode
Automatically restart on file changes:
bash
tinysystems run --watchForce Leader Mode
Test leader-only functionality:
bash
tinysystems run --leaderConnecting to Kubernetes
With Minikube
bash
# Start minikube
minikube start
# Run module connected to cluster
tinysystems run --kubeconfig ~/.kube/configWith Kind
bash
# Create cluster
kind create cluster --name tinysystems-dev
# Run module
tinysystems run --kubeconfig ~/.kube/config --namespace devPort Forwarding
Access cluster services locally:
bash
# Forward another module's gRPC port
kubectl port-forward svc/common-module-v1 50052:50051 -n tinysystems
# Your module can now reach common-module at localhost:50052Testing Components
Unit Tests
Run component tests:
bash
# All tests
go test ./...
# Specific component
go test ./components/transformer/...
# With coverage
go test -cover ./...
# With race detection
go test -race ./...Using CLI
bash
tinysystems test --verbose --coverageIntegration Tests
Test with real Kubernetes:
go
// integration_test.go
//go:build integration
package integration
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func TestComponentIntegration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
// Create test client
k8sClient, err := client.New(config, client.Options{})
require.NoError(t, err)
// Create test TinyNode
node := &v1alpha1.TinyNode{
ObjectMeta: metav1.ObjectMeta{
Name: "test-node",
Namespace: "tinysystems-test",
},
Spec: v1alpha1.TinyNodeSpec{
Component: "my-component",
Module: "my-module",
},
}
err = k8sClient.Create(context.Background(), node)
require.NoError(t, err)
// Wait for processing
time.Sleep(5 * time.Second)
// Verify status
err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(node), node)
require.NoError(t, err)
require.Equal(t, "Ready", node.Status.State)
}Run with:
bash
go test -tags=integration ./...Manual Testing
Send Test Messages
Create a test TinySignal:
yaml
apiVersion: operator.tinysystems.io/v1alpha1
kind: TinySignal
metadata:
name: test-signal
namespace: tinysystems
spec:
node: my-node
port: input
data:
text: "Hello, World!"Apply:
bash
kubectl apply -f test-signal.yamlTest HTTP Endpoints
If your component exposes HTTP:
bash
# Port forward to the pod
kubectl port-forward svc/my-node 8080:8080 -n tinysystems
# Send test request
curl -X POST http://localhost:8080/api/test \
-H "Content-Type: application/json" \
-d '{"message": "test"}'Development Workflow
1. Write Component
go
// components/processor/component.go
func (c *Component) Handle(ctx context.Context, output module.Handler, port string, msg any) error {
if port == "input" {
input := msg.(Input)
result := c.process(input)
return output(ctx, "output", result)
}
return nil
}2. Write Tests
go
// components/processor/component_test.go
func TestProcessor_Handle(t *testing.T) {
component := &Component{}
var result Output
handler := func(ctx context.Context, port string, msg any) error {
if port == "output" {
result = msg.(Output)
}
return nil
}
err := component.Handle(context.Background(), handler, "input", Input{
Data: "test",
})
assert.NoError(t, err)
assert.Equal(t, "processed: test", result.Data)
}3. Run Tests
bash
go test ./components/processor/...4. Run Locally
bash
tinysystems run --watch --debug5. Test with Kubernetes
bash
# Apply test resources
kubectl apply -f test-fixtures/
# Check logs
tinysystems run --debug
# Verify results
kubectl get tinynodes -n tinysystemsTest Fixtures
Directory Structure
test-fixtures/
├── nodes/
│ ├── basic-node.yaml
│ └── complex-node.yaml
├── signals/
│ ├── test-input.yaml
│ └── test-control.yaml
└── expected/
├── basic-output.yaml
└── complex-output.yamlSample Fixture
yaml
# test-fixtures/nodes/basic-node.yaml
apiVersion: operator.tinysystems.io/v1alpha1
kind: TinyNode
metadata:
name: test-processor
namespace: tinysystems-test
spec:
component: github.com/myorg/my-module/processor
module: my-module
edges:
- from: _settings
data:
mode: "test"
debug: trueDebugging Locally
Enable Verbose Logging
go
import "sigs.k8s.io/controller-runtime/pkg/log/zap"
func init() {
opts := zap.Options{
Development: true,
Level: zapcore.DebugLevel,
}
log.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
}Add Debug Endpoints
go
// Start debug HTTP server
go func() {
http.HandleFunc("/debug/state", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]any{
"settings": c.settings,
"isRunning": c.isRunning,
"nodeCount": len(c.nodes),
})
})
http.ListenAndServe(":6060", nil)
}()Use Delve
bash
# Install delve
go install github.com/go-delve/delve/cmd/dlv@latest
# Debug run
dlv debug ./cmd/main.goCI/CD Testing
GitHub Actions
yaml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Run tests
run: go test -v -race -coverprofile=coverage.out ./...
- name: Upload coverage
uses: codecov/codecov-action@v3Integration Tests in CI
yaml
integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Create Kind cluster
uses: helm/kind-action@v1
- name: Install CRDs
run: |
kubectl apply -f charts/crds/
- name: Run integration tests
run: go test -tags=integration -v ./...Best Practices
1. Test All Code Paths
go
func TestComponent_AllPorts(t *testing.T) {
t.Run("settings", testSettings)
t.Run("control", testControl)
t.Run("input", testInput)
t.Run("error handling", testErrors)
}2. Use Table-Driven Tests
go
func TestProcessor(t *testing.T) {
tests := []struct {
name string
input Input
expected Output
}{
{"basic", Input{Data: "a"}, Output{Result: "A"}},
{"empty", Input{Data: ""}, Output{Result: ""}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test
})
}
}3. Mock External Dependencies
go
type mockClient struct {
response any
err error
}
func (m *mockClient) Do(req *http.Request) (*http.Response, error) {
// Return mock response
}4. Test with Real Context
go
ctx := context.Background()
ctx = utils.WithLeader(ctx, true) // Simulate leaderNext Steps
- Testing Components - Advanced testing
- Debugging - Debug issues
- Building Modules - Production builds