typestar

store_test.go en Go

Un archivo de pruebas completo: tablas, subtests, un fake, un benchmark y fuzzing.

package store

import (
    "errors"
    "fmt"
    "strings"
    "testing"
)

// ErrBadStars significa que el valor quedó fuera de cero a tres.
var ErrBadStars = errors.New("stars out of range")

// Saver persiste un intento. Las pruebas aportan el suyo.
type Saver interface {
    Save(lang string, stars int) error
}

// Record valida un intento y se lo entrega al saver.
func Record(s Saver, lang string, stars int) error {
    if stars < 0 || stars > 3 {
        return fmt.Errorf("record %s: %w", lang, ErrBadStars)
    }
    return s.Save(strings.ToLower(strings.TrimSpace(lang)), stars)
}

type fakeSaver struct {
    calls []string
    err   error
}

func (f *fakeSaver) Save(lang string, stars int) error {
    f.calls = append(f.calls, fmt.Sprintf("%s=%d", lang, stars))
    return f.err
}

func TestRecordValidates(t *testing.T) {
    cases := []struct {
        name    string
        lang    string
        stars   int
        wantErr error
        wantCall string
    }{
        {"clean", " Go ", 3, nil, "go=3"},
        {"zero is fine", "rust", 0, nil, "rust=0"},
        {"too many", "sql", 9, ErrBadStars, ""},
    }

    for _, c := range cases {
        t.Run(c.name, func(t *testing.T) {
            fake := &fakeSaver{}
            err := Record(fake, c.lang, c.stars)

            if !errors.Is(err, c.wantErr) {
                t.Fatalf("err = %v, want %v", err, c.wantErr)
            }
            if c.wantCall == "" {
                if len(fake.calls) != 0 {
                    t.Errorf("saved despite the error: %v", fake.calls)
                }
                return
            }
            if len(fake.calls) != 1 || fake.calls[0] != c.wantCall {
                t.Errorf("calls = %v, want [%s]", fake.calls, c.wantCall)
            }
        })
    }
}

func TestRecordPropagatesSaverError(t *testing.T) {
    boom := errors.New("disk full")
    if err := Record(&fakeSaver{err: boom}, "go", 2); !errors.Is(err, boom) {
        t.Errorf("err = %v, want %v", err, boom)
    }
}

func BenchmarkRecord(b *testing.B) {
    fake := &fakeSaver{}
    b.ReportAllocs()
    for b.Loop() {
        fake.calls = fake.calls[:0]
        _ = Record(fake, " Go ", 3)
    }
}

func FuzzRecord(f *testing.F) {
    f.Add("go", 3)

    f.Fuzz(func(t *testing.T, lang string, stars int) {
        fake := &fakeSaver{}
        err := Record(fake, lang, stars)
        inRange := stars >= 0 && stars <= 3
        if inRange && err != nil {
            t.Errorf("Record(%q, %d) = %v, want nil", lang, stars, err)
        }
        if !inRange && !errors.Is(err, ErrBadStars) {
            t.Errorf("Record(%q, %d) = %v, want ErrBadStars", lang, stars, err)
        }
    })
}

Cómo funciona

  1. El fake implementa la interfaz de un método de la que depende el código.
  2. Los casos viven en un slice, así que agregar uno es una sola línea.
  3. Los errores se comparan con errors.Is, nunca por mensaje.

Palabras clave y builtins usados aquí

El intento, en números

Líneas
100
Caracteres a escribir
2194
Tokens
570
Ritmo de tres estrellas
110 tpm

Al ritmo de tres estrellas de 110 tokens por minuto, este intento toma unos 311 segundos.

Escribe este fragmento

Paso 1 de 1 en Bis; paso 13 de 13 en Pruebas.

← Anterior