Skip to content

Latest commit

 

History

History
238 lines (163 loc) · 5.18 KB

approval-testing.md

File metadata and controls

238 lines (163 loc) · 5.18 KB
title author date css highlightTheme
Approval testing legacy code
Zeger Hendrikse
2023-09-29
css/custom.css
github-dark

Refactoring Legacy Code

Using approval tests

by Zeger Hendrikse

 

 

 

 


Goals

  • Learn about legacy code
  • Learn about approval testing
  • When to use it and when not

 

 

 


Michael Feathers

To me, legacy code is simply code without tests — Michael Feathers


Or even better...

Nicolas Carlo

Legacy Code is valuable code you’re afraid to change — Nicolas Carlo


😱 But we need to modify it 😱

Question


The naive: Edit and Pray 🙏

  1. Edit the code
  2. Test manually
  3. Pray you didn't brake anything

  Drawbacks: very risky & stressful


The ideal: Write the damn tests ✅

  1. Reverse engineer the specs from the code
  2. Write automated tests
  3. Refactor the code
  4. Add your feature

  Drawbacks: very costly and sl-o-o-o-w...


The pragmatic: Approval tests 💁

  1. Generate an output you can snapshot 📸
  2. Use test coverage to find all input combinations ✅
  3. Use mutations to verify your snapshots 👽

Also known as

  • Characterization Tests
  • Golden Master
  • Snapshot Tests
  • Locking Tests
  • Regression Tests

What we are going to test

Python version

class Calculator():
  @staticmethod
  def addNumbers(x: int, y: int) -> int:
    return x + y

Typescript version

export class Calculator {
  public add(x: number, y: number): number {
    return x + y
  }
}

How we verify

import unittest
from approvaltests.approvals import verify
from calculator import Calculator

class CalculatorTest(unittest.TestCase):

  def test_main(self):
    # ARRANGE
    x: int = 1
    y: int = 2;
    # ACT
    result = Calculator.addNumbers(x, y)
    # APPROVE
    verify(result)

Verification in Typescript

import { Calculator } from "../src/Calculator"
import { verify } from "approvals/lib/Providers/Jest/JestApprovals";

describe("A new Calculator", function() {
    it("adds two numbers", function () {
        const myCalculator  = new Calculator()
        const result = myCalculator.add(3, 4)
        // expect(result).toEqual(7)
        verify(result)
    })
})

Even with combinatoric tests 🤩

...
from approvaltests.combination_approvals import verify_all_combinations

class CalculatorTest(unittest.TestCase):

  def test_add_combinatorial(self):
    verify_all_combinations( Calculator.addNumbers, [[1,2], [4,3]])

Caveat

Not available in Typescript library 😱


One-click demo

Open in Gitpod

  • Navigate to the tools/approval-tests directory
  • Go to the language of choice

Approval testing use cases

  • Code without tests that needs to be changed
  • APIs that return JSON or XML
  • Complex return objects
  • Strings longer than one line

Problems:

  • Existing behavior is captured, bugs included
  • Tests will fail whenever behavior changes. Noisy!
  • People will get used to just update them
  • You can't read them to understand what the code does
  • Delete them or have a plan to replace them with unit tests.