Testing
Testing strategies for atom-based code.
Basic Testing
Round-Trip Tests
Verify that atomize/deatomize preserves data:
func TestUserRoundTrip(t *testing.T) {
atomizer, err := atom.Use[User]()
if err != nil {
t.Fatalf("Use failed: %v", err)
}
original := &User{
Name: "Alice",
Age: 30,
Active: true,
}
// Atomize
a := atomizer.Atomize(original)
// Deatomize
restored, err := atomizer.Deatomize(a)
if err != nil {
t.Fatalf("Deatomize failed: %v", err)
}
// Compare
if restored.Name != original.Name {
t.Errorf("Name: got %q, want %q", restored.Name, original.Name)
}
if restored.Age != original.Age {
t.Errorf("Age: got %d, want %d", restored.Age, original.Age)
}
if restored.Active != original.Active {
t.Errorf("Active: got %v, want %v", restored.Active, original.Active)
}
}
Using reflect.DeepEqual
For complex structs:
func TestComplexRoundTrip(t *testing.T) {
atomizer, _ := atom.Use[ComplexType]()
original := &ComplexType{
// ... many fields
}
a := atomizer.Atomize(original)
restored, err := atomizer.Deatomize(a)
if err != nil {
t.Fatalf("Deatomize failed: %v", err)
}
if !reflect.DeepEqual(original, restored) {
t.Errorf("round-trip failed:\noriginal: %+v\nrestored: %+v",
original, restored)
}
}
Testing Specific Fields
Verify Atomization Output
func TestUserAtomization(t *testing.T) {
atomizer, _ := atom.Use[User]()
user := &User{Name: "Alice", Age: 30}
a := atomizer.Atomize(user)
// Check specific fields
if got := a.Strings["Name"]; got != "Alice" {
t.Errorf("Strings[Name]: got %q, want %q", got, "Alice")
}
if got := a.Ints["Age"]; got != 30 {
t.Errorf("Ints[Age]: got %d, want %d", got, 30)
}
}
Verify Deatomization Input
func TestUserDeatomization(t *testing.T) {
atomizer, _ := atom.Use[User]()
a := &atom.Atom{
Strings: map[string]string{"Name": "Bob"},
Ints: map[string]int64{"Age": 25},
}
user, err := atomizer.Deatomize(a)
if err != nil {
t.Fatalf("Deatomize failed: %v", err)
}
if user.Name != "Bob" {
t.Errorf("Name: got %q, want %q", user.Name, "Bob")
}
if user.Age != 25 {
t.Errorf("Age: got %d, want %d", user.Age, 25)
}
}
Table-Driven Tests
Testing Multiple Cases
func TestUserRoundTrips(t *testing.T) {
atomizer, _ := atom.Use[User]()
tests := []struct {
name string
user User
}{
{"empty", User{}},
{"basic", User{Name: "Alice", Age: 30}},
{"all fields", User{Name: "Bob", Age: 25, Active: true}},
{"unicode", User{Name: "日本語", Age: 100}},
{"max age", User{Name: "Elder", Age: math.MaxInt64}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := atomizer.Atomize(&tt.user)
restored, err := atomizer.Deatomize(a)
if err != nil {
t.Fatalf("Deatomize failed: %v", err)
}
if !reflect.DeepEqual(&tt.user, restored) {
t.Errorf("mismatch:\noriginal: %+v\nrestored: %+v",
tt.user, restored)
}
})
}
}
Testing Error Cases
Overflow Detection
func TestOverflowDetection(t *testing.T) {
type Small struct {
Value int8
}
atomizer, _ := atom.Use[Small]()
tests := []struct {
name string
value int64
wantErr bool
}{
{"valid positive", 100, false},
{"valid negative", -100, false},
{"overflow positive", 200, true},
{"overflow negative", -200, true},
{"max", 127, false},
{"min", -128, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &atom.Atom{Ints: map[string]int64{"Value": tt.value}}
_, err := atomizer.Deatomize(a)
if (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr = %v", err, tt.wantErr)
}
})
}
}
Unsupported Types
func TestUnsupportedType(t *testing.T) {
type Invalid struct {
Data map[string]any
}
_, err := atom.Use[Invalid]()
if err == nil {
t.Fatal("expected error for unsupported type")
}
if !strings.Contains(err.Error(), "map types are not supported") {
t.Errorf("unexpected error: %v", err)
}
}
Testing Nested Structs
func TestNestedRoundTrip(t *testing.T) {
type Address struct {
Street string
City string
}
type User struct {
Name string
Address Address
}
atomizer, _ := atom.Use[User]()
original := &User{
Name: "Alice",
Address: Address{
Street: "123 Main St",
City: "Springfield",
},
}
a := atomizer.Atomize(original)
// Verify nested structure
if _, ok := a.Nested["Address"]; !ok {
t.Fatal("expected Address in Nested map")
}
restored, err := atomizer.Deatomize(a)
if err != nil {
t.Fatalf("Deatomize failed: %v", err)
}
if restored.Address.Street != original.Address.Street {
t.Errorf("Street: got %q, want %q",
restored.Address.Street, original.Address.Street)
}
}
Testing Custom Interfaces
type CustomUser struct {
Name string
}
func (u *CustomUser) Atomize(a *atom.Atom) {
a.Strings["Name"] = "custom:" + u.Name
}
func (u *CustomUser) Deatomize(a *atom.Atom) error {
name := a.Strings["Name"]
if !strings.HasPrefix(name, "custom:") {
return fmt.Errorf("invalid format")
}
u.Name = strings.TrimPrefix(name, "custom:")
return nil
}
func TestCustomInterface(t *testing.T) {
atomizer, _ := atom.Use[CustomUser]()
original := &CustomUser{Name: "Alice"}
a := atomizer.Atomize(original)
// Verify custom atomization was used
if got := a.Strings["Name"]; got != "custom:Alice" {
t.Errorf("expected custom format, got %q", got)
}
restored, err := atomizer.Deatomize(a)
if err != nil {
t.Fatalf("Deatomize failed: %v", err)
}
if restored.Name != original.Name {
t.Errorf("Name: got %q, want %q", restored.Name, original.Name)
}
}
Concurrent Testing
func TestConcurrentUse(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomizer, err := atom.Use[User]()
if err != nil {
t.Errorf("Use failed: %v", err)
return
}
_ = atomizer.Atomize(&User{Name: "test"})
}()
}
wg.Wait()
}
Benchmarking
func BenchmarkAtomize(b *testing.B) {
atomizer, _ := atom.Use[User]()
user := &User{Name: "Alice", Age: 30, Active: true}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = atomizer.Atomize(user)
}
}
func BenchmarkDeatomize(b *testing.B) {
atomizer, _ := atom.Use[User]()
a := atomizer.Atomize(&User{Name: "Alice", Age: 30, Active: true})
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = atomizer.Deatomize(a)
}
}
func BenchmarkRoundTrip(b *testing.B) {
atomizer, _ := atom.Use[User]()
user := &User{Name: "Alice", Age: 30, Active: true}
b.ResetTimer()
for i := 0; i < b.N; i++ {
a := atomizer.Atomize(user)
_, _ = atomizer.Deatomize(a)
}
}
Test Helpers
See Testing Reference for the testing package utilities:
AtomBuilder- Fluent atom constructionAtomMatcher- Deep comparisonRoundTripValidator- Automated round-trip testing
Best Practices
Test at Registration
func TestMain(m *testing.M) {
// Verify all types register successfully
types := []func() error{
func() error { _, err := atom.Use[User](); return err },
func() error { _, err := atom.Use[Order](); return err },
}
for _, register := range types {
if err := register(); err != nil {
log.Fatalf("registration failed: %v", err)
}
}
os.Exit(m.Run())
}
Test Edge Cases
- Empty structs
- Nil pointers
- Empty slices vs nil slices
- Maximum/minimum values
- Unicode strings
- Large byte slices
Next Steps
- Testing Reference - Testing package API
- API Reference - Complete API documentation