ditto

Pruebas de mutación lo bastante baratas como para ejecutarlas mientras todavía escribes el código.

Prepara un cambio y ditto lo rompe a propósito, un mutante a la vez, y te dice qué rupturas dejaron pasar tus pruebas. Solo paga por las líneas que tocaste.

~/projects/shopRecortado
$ ditto staged --threshold 0.8┃ price/price.go — 4 mutants… the mutant list, the baseline and the survivors summary, elided …┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╍┅┃ 🧬 Mutant survived: price/price.go:10:16 → Comparison┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃  func IsFree(price int) bool {┃ -    return price <= 0┃ +    return price < 0┃  }┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╍┅… the diff header and two more survivors, elided …┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃ • Total:        4                    ┃┃ • Killed:       1                    ┃┃ • Survived:     3                    ┃┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨┃ ⨯ Score:     0.25 (minimum: 0.80)    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ditto: the mutation score is below the configured minimum of 0.80
Salida real de ditto v0.10.0: IsFree devuelve price <= 0 y su prueba revisa -1 y 5, así que sobreviven tres mutantes del borde y la ejecución sale con 1. Los bloques señalados se omitieron.
El veredicto

Un puntaje sobre el que puedes actuar, o un rechazo que dice por qué

Si la suite ya estuviera en rojo, todos los mutantes parecerían muertos y el puntaje sería perfecto. Esto es lo que hay entre ese número y tú.

Muertes reportadas, por motivo50 de 78 mutantes reportados como muertos
Le acredita a tu suite
Aserción
Tu suite no recibe crédito22%
Fallo de compilaciónPlazo vencidoDesconocido
  • La suite se prueba en verde antes de puntuar nada

    Una ejecución base de tu comando sobre el código sin mutar abre cada release. Si sale en rojo, la ejecución termina ahí y te muestra la salida del propio comando.

  • Cada muerte llega con el motivo por el que ocurrió

    Aserción, fallo de compilación, plazo vencido o desconocido. Solo una aserción le da crédito a tu suite, como muestra la barra de arriba.

  • El puntaje se toma sobre los mutantes que compilaron

    Un mutante que nunca llegó a ser un programa sale del puntaje por completo, así que lees la proporción de mutantes ejecutables que tus pruebas atraparon.

  • Cada superviviente llega con una dirección

    ruta:línea:columna → Operador (lo que reemplazó), listado antes de cualquier diff, para saltar directo a él.

Cuánto cuesta una ejecución

Lo bastante rápido para correrlo en cada cambio

Cada mutante vuelve a ejecutar tu comando de pruebas, así que ditto recorta cuántas veces corre. Los mismos doce mutantes vuelven en 0.4 a 1.3 segundos frente a 10 u 11.

  • ditto staged

    Paga por las líneas que el cambio realmente tocó

    484ejecuciones de pruebas
    8% de la cuenta original

    Lee los rangos cambiados del índice de git, así que una función que modificaste cuesta solo los operadores que disparan en sus líneas.

  • --gated

    Una sola compilación sostiene todos los mutantes del archivo

    13538invocaciones del comando
    28% de la cuenta original

    Cada archivo se compila una vez y todos sus mutantes corren sobre esa compilación. Donde las dos rutas difieren, la de compuertas es la correcta.

Los operadores

Quince formas de romper tu código a propósito

Cada uno edita el árbol sintáctico como lo haría un error real: un operador invertido, una constante desplazada, un bucle interrumpido. Catorce están activos antes de que configures nada, desde el comando o desde `Release`; el decimoquinto se agrega nombrándolo.

  • Arithmetic
    • +↔-
    • *↔/
    • %↔*
  • Arithmetic Assignment
    • +=-=*=/=%=&=|=^=<<=>>=&^=→=
  • Arithmetic Assignment Invert
    • +=↔-=
    • *=↔/=
    • %=↔*=
  • Bitwise
    • &↔|
    • ^→&
    • &^→&
    • <<↔>>
  • Comparison
    • <↔<=
    • >↔>=
  • Comparison Invert
    • >↔<=
    • <↔>=
    • ==↔!=
  • Comparison Replace
    • &&operand→true
    • ||operand→false
  • Float Decrement
    • x→x-1.0
  • Float Increment
    • x→x+1.0
  • Integer Decrement
    • n→n-1
  • Integer Increment
    • n→n+1
  • Loop Break
    • break↔continue
  • Loop Condition
    • condition→false
  • Range Break
    • range→earlybreak
  • Cancel NilOpcional
    • context.CancelCauseFunc(err)→(nil)

Escribe uno para tu propio dominio

Un virus es cualquier struct que satisfaga la interfaz `viruses.Virus`, así que una mutación que solo significa algo en tu código es un tipo y una llamada a `WithViruses`. El paquete `dittotesting` trae los ayudantes con los que se prueban los quince que vienen incluidos, para que el tuyo reciba el mismo trato.

Agregarlo

De la instalación a un puntaje confiable, en cuatro ejecuciones

Instala el comando y recorre una ejecución real, desde una revisión en seco hasta un puntaje aprobado. Conectarlo a un binario de pruebas que ya ejecutas toma la ruta de librería de abajo.

  1. 01

    Instala el binario

    go install github.com/Disble/ditto/cmd/ditto@latest

    Un binario en tu PATH, listo para ditto staged desde la raíz de un repositorio.

  2. 02

    Mira qué va a mutar

    ditto staged --dry

    Lista los rangos preparados que juzgaría y no ejecuta nada, así conoces el costo antes.

    ~/projects/shopRecortado
    $ ditto staged --dryditto: 1 staged file(s) under ~/projects/shop  price/price.go: 150-251
  3. 03

    Mútalo con un mínimo

    ditto staged --threshold 0.8

    Cada sobreviviente vuelve con su línea y la ruptura que hizo, los mismos tres que se ven al inicio de la página, y un puntaje bajo el mínimo sale con 1.

  4. 04

    Cubre el borde y vuelve a correrlo

    ditto staged --threshold 0.8

    Dos aserciones, para 0 y 1, matan a los tres sobrevivientes, y la ejecución sale con 0.

    ~/projects/shopRecortado
    $ ditto staged --threshold 0.8… progress, elided …┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃ • Total:        4                    ┃┃ • Killed:       4                    ┃┃ • Survived:     0                    ┃┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨┃ ✓ Score:     1.00 (minimum: 0.80)    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Cada subcomando, tal como lo imprime -h
ditto -hSalida capturada
ditto — mutation testing for Go   ditto run [flags]       mutate a repository and report what survived  ditto staged [flags]    mutate only what a staged change justifies  ditto changed [flags]   mutate only what a committed change justifies  ditto version           the module version this binary was built from                          (also -v, --version) Run `ditto run -h` for its flags.
Desde un binario de pruebas

Una importación y una etiqueta de compilación ponen el mismo motor dentro de la suite que ya ejecutas, y con `WithTestCommand` indicas otro comando de pruebas.

go get github.com/Disble/ditto
Después ejecuta
go test -v -tags=mutation
mutation_test.go
//go:build mutation package main_test import (	"testing" 	"github.com/Disble/ditto") func TestMutation(t *testing.T) {	ditto.Release(t)}

ditto es un fork de gtramontina/ooze, de Guilherme J. Tramontina. Todas las buenas ideas de aquí son suyas, y la licencia y el copyright siguen siendo de él; es MIT, igual que el original.