Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@ jobs:
test:
strategy:
matrix:
go-version: [1.18.x, 1.19.x]
go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x, 1.23.x, 1.24.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Test
run: |
run: |
go test -v -race -covermode=atomic
coverage:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.19.x
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: |
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: stable
- name: Checkout code
uses: actions/checkout@v4
- name: Test
run: |
go test -v -race -coverprofile=coverage.txt -covermode=atomic
- name: Coverage
uses: codecov/codecov-action@v2
- name: Coverage
uses: codecov/codecov-action@v4
136 changes: 65 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
# Trial - Prove the Innocence of your code

[![GoDoc](https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://godoc.org/github.com/hydronica/trial)
[![GoDoc](https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/hydronica/trial)
![Build Status](https://github.com/hydronica/trial/actions/workflows/test.yml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/jbsmith7741/trial)](https://goreportcard.com/report/github.com/hydronica/trial)
[![codecov](https://codecov.io/gh/jbsmith7741/trial/branch/master/graph/badge.svg)](https://codecov.io/gh/hydronica/trial)
[![Go Report Card](https://goreportcard.com/badge/github.com/hydronica/trial)](https://goreportcard.com/report/github.com/hydronica/trial)
[![codecov](https://codecov.io/gh/hydronica/trial/branch/master/graph/badge.svg)](https://codecov.io/gh/hydronica/trial)

Go testing framework to make tests easier to create, maintain and debug.

## Documentation

See [wiki](https://github.com/hydronica/trial/wiki) for tips and guides

| [Examples](https://github.com/hydronica/trial/wiki) | [Compare Functions](https://github.com/hydronica/trial/wiki/Comparers) | [Helper Functions](https://github.com/hydronica/trial/wiki/Helpers) |
|-|-|-|
| [Examples & Patterns](docs/examples.md) | [API Reference](docs/api.md) | [Comparers](docs/comparers.md) | [Helpers](docs/helpers.md) |
|-|-|-|-|

## Philosophy

Expand All @@ -29,7 +28,7 @@ See [wiki](https://github.com/hydronica/trial/wiki) for tips and guides
- Test for observable behavior

## Features
- function verification.
- Function verification
- Check results for exact matches including private values (default behavior)
- Check results for values contained in others (see Contains function)
- Allows for custom compare functions
Expand All @@ -38,50 +37,50 @@ See [wiki](https://github.com/hydronica/trial/wiki) for tips and guides
- check for expected panic cases with `ShouldPanic`
- Test error cases
- Check that a function returns an error: `ShouldErr`
- Check that an error strings contains expected string: `ExpectedErr`
- Check that an error string contains expected string: `ExpectedErr`
- Check that an error is of expected type: `ExpectedErr: ErrType(err)`
- Fail tests that take too long to complete
- `trial.New(fn,cases).Timeout(time.Second)`

## Getting Started


## Getting Starting

``` go
```go
go get github.com/hydronica/trial
```

Each test has 3 parts to it. A **function** to test against, a set of test **cases** to validate with and **trial** that sets up the table driven tests

### **Test Function**
``` go
### Test Function

```go
testFunc[In any, Out any] func(in In) (result Out, err error)
```
a generic function that has a single input and returns a result and an error. Wrap your function to test more complex functions or methods.

*Example*
``` go
fn := func(i int) (string, error) {
return strconv.ItoA(i), nil
}
A generic function that has a single input and returns a result and an error. Wrap your function to test more complex functions or methods.

*Example*
```go
fn := func(i int) (string, error) {
return strconv.Itoa(i), nil
}
```

### Cases

### **Cases**
a collection (map) of test cases that have a unique title and defined *input* to be passed to the test function. The expected behavior is described by providing an *output*, setting an *ExpectedErr* or saying it *ShouldErr*. *ShouldPanic* can be used to test when function panic condition.
``` go
type Case[In any, Out any] struct {
Input In
Expected Out
A collection (map) of test cases that have a unique title and defined *input* to be passed to the test function. The expected behavior is described by providing an *output*, setting an *ExpectedErr* or saying it *ShouldErr*. *ShouldPanic* can be used to test when function panic condition.

```go
type Case[In any, Out any] struct {
Input In
Expected Out

ShouldErr bool // is an error expected
ExpectedErr error // the error that was expected (nil is no error expected)
ShouldPanic bool // is a panic expected
ShouldErr bool // is an error expected
ExpectedErr error // the error that was expected (nil is no error expected)
ShouldPanic bool // is a panic expected
}
```
Each

Each case field is described below:

- **Input** *generic* - a convenience structure to handle input more dynamically
- **Expected** *generic* - the expected output of the method being tested.
Expand All @@ -93,26 +92,26 @@ Each
- use *ErrType* to test that the error is the same type as expected.
- **ShouldPanic** *bool* - indicates the method should panic


### Trial Setup

Run the test cases either within a single test function or as subtests. The *input* and *output* values must match between the test function and cases.

``` go
```go
trial.New(fn,cases).Test(t)
// or
trial.New(fn,cases).SubTest(t)
```

By default trial uses a strict matching values and uses cmp.Equal to compare values. *Compare* Functions can be customized to ignore certain fields or are contained withing maps, slices or strings. See **Compare Functions** for more details. A timeout can be added onto the trial builder with `.Timeout(time.Second)`
By default trial uses strict matching values and uses cmp.Equal to compare values. *Compare* functions can be customized to ignore certain fields or are contained within maps, slices or strings. See [Comparers](docs/comparers.md) for more details. A timeout can be added onto the trial builder with `.Timeout(time.Second)`

### Getting Started Template
``` go

```go
fn := func(in any) (any, error) {
// TODO: setup test case, call routine and return result
return nil, nil
}
cases := trail.Cases{
cases := trial.Cases[any, any]{
"default": {
Input: 123,
Expected: 1,
Expand All @@ -121,62 +120,57 @@ cases := trail.Cases{
trial.New(fn,cases).Test(t)
```

For more examples see the [wiki](https://github.com/hydronica/trial/wiki)
## Compare Functions

# Compare Functions
used to compare two values to determine equality and displayed a detailed string describing any differences.
Used to compare two values to determine equality and display a detailed string describing any differences.

``` go
```go
func(actual, expected any) (equal bool, differences string)
```

override the default
Override the default:

``` go
```go
trial.New(fn, cases).Comparer(myComparer).Test(t)
```

## Equal
The default comparer used, it is a wrapping for cmp.Equal with the AllowUnexported option set for all structs. This causes all fields (public and private) in a struct to be compared. (see https://github.com/google/go-cmp)
### Equal

The default comparer used, it is a wrapper for cmp.Equal with the AllowUnexported option set for all structs. This causes all fields (public and private) in a struct to be compared. (see https://github.com/google/go-cmp)

### EqualOpt

Customize the use of cmp.Equal with the following supported options:
- `AllowAllUnexported` - **[default: Equal]** compare all unexported (private) variables within a struct. This is useful when testing a struct inside its own package.
- `IgnoreAllUnexported` - ignore all unexported (private) variables within a struct. This is useful when dealing with a struct outside the project.
- `IgnoreFields(fields ...string)` - define a list of variables to exclude for the comparer, the field name are case sensitize and can be dot-delimited ("Field", "Parent.child")
- `IgnoreFields(fields ...string)` - define a list of variables to exclude for the comparer, the field names are case sensitive and can be dot-delimited ("Field", "Parent.child")
- `EquateEmpty`- **[default: Equal]** a nil map or slice is equal to an empty one (len is zero)
- `IgnoreTypes(values ...interface{})` - ignore all types of the values passed in. Ex: IgnoreTypes(int64(0), float32(0.0)) ignore int64 and float32
- `ApproxTime(d time.Duration)` - approximates time values to to the nearest duration.
- `ApproxTime(d time.Duration)` - approximates time values to the nearest duration.

``` go
```go
// example struct that is compared to expected
type Example struct {
Field1 int
Field2 int
ignoreMe string // ignored unexported
Timestamp time.Time // ignored
LastUpdate time.Time // ignored
Field1 int
Field2 int
ignoreMe string // ignored unexported
Timestamp time.Time // ignored
LastUpdate time.Time // ignored
}

trial.New(fn, cases).Comparer(
trial.EqualOpt(
trial.IgnoreAllUnexported,
trial.IgnoreFields("Timestamp", "LastUpdate")),
).SubTest(t)
trial.New(fn, cases).Comparer(
trial.EqualOpt(
trial.IgnoreAllUnexported,
trial.IgnoreFields("Timestamp", "LastUpdate")),
).SubTest(t)
```

## Contains ⊇

Checks if the expected value is *contained* in the actual value. The symbol ⊇ is used to donate a subset. ∈ is used to show that a value exists in a slice. Contains checks the following relationships

- **string ⊇ string**
- is the expected string contained in the actual string (strings.Contains)
- **string ⊇ []string**
- are the expected substrings contained in the actual string
- **[]interface{} ⊇ interface{}**
- is the expected value found in the slice or array
- **[]interface{} ⊇ []interface{}**
- is the expected slice a subset of the actual slice. all values in expected exist and are contained in actual.
- **map[key]interface{} ⊇ map[key]interface{}**
- is the expected map a subset of the actual map. all keys in expected are in actual and all values under that key are contained in actual
### Contains ⊇

Checks if the expected value is *contained* in the actual value. The symbol ⊇ is used to denote a subset. ∈ is used to show that a value exists in a slice. Contains checks the following relationships:

- **string ⊇ string** - is the expected string contained in the actual string (strings.Contains)
- **string ⊇ []string** - are the expected substrings contained in the actual string
- **[]interface{} ⊇ interface{}** - is the expected value found in the slice or array
- **[]interface{} ⊇ []interface{}** - is the expected slice a subset of the actual slice. all values in expected exist and are contained in actual.
- **map[key]interface{} ⊇ map[key]interface{}** - is the expected map a subset of the actual map. all keys in expected are in actual and all values under that key are contained in actual
112 changes: 112 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Trial API Reference

Quick reference for the `hydronica/trial` testing framework. See linked docs for detailed explanations and examples.

## Core Types

```go
// Main test runner
type Trial[In any, Out any] struct { /* ... */ }

// Map of named test cases
type Cases[In any, Out any] map[string]Case[In, Out]

// Single test case
type Case[In any, Out any] struct {
Input In
Expected Out
ShouldErr bool // expect any error
ExpectedErr error // expect error containing this string (or use ErrType for type check)
ShouldPanic bool // expect panic
}

// Dynamic input wrapper (use with Args)
type Input struct { /* ... */ }

// Custom comparer signature
type CompareFunc func(actual, expected interface{}) (equal bool, differences string)
```

## Core Functions

| Function | Description |
|----------|-------------|
| `New(fn, cases)` | Create Trial instance |
| `ErrType(err)` | Wrap error for type-based comparison |

## Trial Methods

| Method | Description |
|--------|-------------|
| `.Test(t)` | Run all cases in single test |
| `.SubTest(t)` | Run each case as subtest (recommended) |
| `.Comparer(fn)` | Set custom comparison function |
| `.Timeout(d)` | Set max duration per case |

## Case Fields

| Field | Type | Description |
|-------|------|-------------|
| `Input` | `In` | Value passed to test function |
| `Expected` | `Out` | Expected return value |
| `ShouldErr` | `bool` | Expect any error |
| `ExpectedErr` | `error` | Expect error containing string (uses `strings.Contains`) |
| `ShouldPanic` | `bool` | Expect panic |

**Notes:**
- `ExpectedErr` implies `ShouldErr`, no need to set both
- Use `ErrType(MyError{})` with `ExpectedErr` to check error types

## Input Methods

For use with `trial.Args()`. See [helpers.md](helpers.md#input-helpers) for details.

| Method | Return | Description |
|--------|--------|-------------|
| `String()` | `string` | Get as string |
| `Int()` | `int` | Get as int (parses strings) |
| `Uint()` | `uint` | Get as uint |
| `Float64()` | `float64` | Get as float64 |
| `Bool()` | `bool` | Get as bool |
| `Slice(i)` | `Input` | Get element at index |
| `Map(key)` | `Input` | Get value for key |
| `Interface()` | `interface{}` | Get raw value |

## Comparers

See [comparers.md](comparers.md) for detailed documentation.

| Comparer | Description |
|----------|-------------|
| `Equal` | Default. Strict equality via `cmp.Equal` |
| `Contains` | Subset/substring matching |
| `EqualOpt(opts...)` | Customizable equality |
| `CmpFuncs` | Compare function pointers |

### EqualOpt Options

| Option | Description |
|--------|-------------|
| `AllowAllUnexported` | Compare private fields (default in `Equal`) |
| `IgnoreAllUnexported` | Skip private fields |
| `IgnoreFields(names...)` | Skip specific fields |
| `IgnoreTypes(values...)` | Skip specific types |
| `ApproxTime(d)` | Fuzzy time comparison |
| `EquateEmpty` | nil == empty slice/map (default in `Equal`) |

## Helpers

See [helpers.md](helpers.md) for detailed documentation.

| Function | Description |
|----------|-------------|
| `Args(values...)` | Create Input from multiple args |
| `Pointer[T](v)` | Create pointer to primitive |
| `Time(layout, value)` | Parse time (panics on error) |
| `Day(value)` | Parse `2006-01-02` |
| `Hour(value)` | Parse `2006-01-02T15` |
| `Times(layout, values...)` | Parse multiple times |
| `TimeP(layout, value)` | Parse time, return pointer |
| `CaptureLog()` | Capture log output |
| `CaptureStdOut()` | Capture stdout |
| `CaptureStdErr()` | Capture stderr |
Loading
Loading