marker

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2024 License: MIT Imports: 5 Imported by: 0

README

Marker

Go Reference

This package provides a slog.Handler and an associated API for implementing explicit code coverage marks for linking source code and tests together.

In production code, you use your logger as normal, and can use it to say "this should be covered by a test". In test code, you can then assert that a specific test covers a specific log line.

The purpose of this is to help with test maintenance over time in larger projects. Large projects often have a lot of tests. Finding the tests for a specific piece of code, and vice versa, can be a challenge. This package provides a simple solution to that problem by leveraging your existing logger, and simply enabling the use of grep to search for a corresponding test. For example, if you see logger.Debug("request sent, waiting on response") in the code, you can grep for that log message and immediately find the test that goes with that code path.

The blog post that inspired this package goes over this testing technique and why it's useful in much more detail. https://ferrous-systems.com/blog/coverage-marks/

This is not for "coverage". Coverage is the answer to the question "Is this tested?". Marks answer "Why does this code need to exist?".

Inspired by:

Implementations of this concept in other languages:

Example:
package main

import (
    "fmt"
    "io"
    "log/slog"

    "github.com/KasonBraley/marker"
)

func main() {
    run()
}

func run() {
    logger := slog.New(marker.NewHandler(slog.NewTextHandler(io.Discard, nil)))
    svc := newService(logger)
    svc.isEven(2)
}

type service struct {
    logger *slog.Logger
}

func newService(logger *slog.Logger) *service {
    return &service{logger: logger}
}

func (s *service) isEven(x int) {
    if x%2 == 0 {
        s.logger.Info(fmt.Sprintf("x is even (x=%v)", x))
    }
    s.logger.Info(fmt.Sprintf("x is odd (x=%v)", x))
}

Corresponding test:

func TestIsEven(t *testing.T) {
    svc := newService()

    t.Run("even", func(t *testing.T) {
        mark := marker.Check("x is even")
        svc.isEven(2)
        if err := mark.ExpectHit(); err != nil {
            t.Error(err)
        }
    })

    t.Run("odd", func(t *testing.T) {
        mark := marker.Check("x is even") // If we change this to "x is odd", it will pass.
        svc.isEven(3) // Odd number passed to show that we don't hit the expected mark.
        if err := mark.ExpectHit(); err != nil {
            t.Error(err) // The error `mark "x is even" not hit` is returned.
        }
    })
}

Documentation

Overview

Package marker provides a slog.Handler and an associated API for implementing explicit code coverage marks for *linking* source code and tests together.

In production code, you use your logger as normal, and can use it to say "this should be covered by a test". In test code, you can then assert that a _specific_ test covers a specific log line.

The purpose of this is to help with test maintenance over time in larger projects. Large projects often have a lot of tests. Finding the tests for a specific piece of code, and vice versa, can be a challenge. This package provides a simple solution to that problem by leveraging your existing logger, and simply enabling the use of `grep` to search for a corresponding test. For example, if you see `logger.Debug("request sent, waiting on response")` in the code, you can grep for that log message and immediately find the test that goes with that code path.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewHandler

func NewHandler(h slog.Handler) *handler

NewHandler returns a slog.Handler implementation to help trace tests to source code. In a test environment, reported by testing.Testing, the slog.Handler returned records that a log message was hit.

In a test, Check is used to say that the code under test should log a specific message. It returns a Mark where Mark.ExpectHit is expected to be called after the code under test is ran.

In non-tests(i.e. normal code operation), this recording of Mark's is a no-op.

Types

type Mark

type Mark struct {
	// contains filtered or unexported fields
}

func Check

func Check(name string) Mark

Check stores the given mark name in global state to be subsequently asserted it was hit with Mark.ExpectHit.

Check will panic if not used in a testing environment, as reported by testing.Testing.

func (Mark) ExpectHit

func (m Mark) ExpectHit() error

ExpectHit returns an error if the stored name on Mark was not hit. ExpectHit requires Check to have been called first with the mark name that you expect to have been logged in the function under test.

ExpectHit will panic if not used in a testing environment, as reported by testing.Testing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL