Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/rdrdr/hamcrest

Hamcrest matchers for the Go programming language
https://github.com/rdrdr/hamcrest

Last synced: about 2 months ago
JSON representation

Hamcrest matchers for the Go programming language

Awesome Lists containing this project

README

        

Note:
=====
This has not been maintained and/or updated since 2011.
Perhaps consider [corbym/gocrest](https://github.com/corbym/gocrest), instead.

Introduction
============

Hamcrest is a fluent framework for declarative Matcher objects
that, when applied to input values, produce self-describing
results.

Installation:
========

To install, run `make install` from the same directory as this
`README.md` file.

Packages
========

`hamcrest.go` comes in several packages that you assemble to fit your needs:

* `hamcrest/base`: Defines the types `Matcher`, `Result` and `SelfDescribing`
and provides factory functions to create them. (Unless you want to define
your own custom Matchers, you'll only use this indirectly.)

* `hamcrest/core`: Defines a set of Matchers for doing basic comparisons,
equality testing, nil checking, and grouping/composition matchers.

* `hamcrest/slices`: Matchers on slices, such as `EachElem`, `AnyElem`,
`ToLen`, `Empty`.

* `hamcrest/reflect`: Matchers using type reflection, such as `ToType`,
`SameTypeAs`, `SliceOf`, `MapOf`, etc.

* `hamcrest/strings`: Matchers for strings.

* `hamcrest/asserter`: Defines an `Asserter` that can be used in conjunction
with Hamcrest Matchers to produce helpful logging messages at runtime
(to stdout, stderr, or any object that implements io.Writer) or in
unit tests (using `testing.T` from Go's standard `testing` package).

Note: the `asserter` package isn't *really* part of Hamcrest: it's just
a handy way of using the Hamcrest results in conjunction with the
standard Go testing package.

You may also choose to write your own Matchers (see *Custom matchers*, below).

How to use hamcrest for testing:
================================

To use Hamcrest matchers, create an `Asserter` and use it to
`Check` or `Assert` that values meet the criteria of those
matchers:

func TestPoint(t *testing.T) {
p := Point(3, 4)
we := asserter.Using(t)
we.AssertThat(p.X, EqualTo(3).Comment("x coord"))
we.AssertThat(p.Y, EqualTo(4).Comment("y coord"))
we.CheckThat(p, ToString(EqualTo("[3, 4]")))
we.CheckThat(p.Magnitude(), EqualTo(5).Comment("magnitude"))
}

(`Assert` methods fail immediately, as `testing.T.FailNow()` does,
while `Check` methods defer failure, as `testing.T.Fail()` does.)

The `AssertThat` and `CheckThat` functions are designed to create
conditional checks that read fluently as self-commenting code, and
are self-describing when failures occur. For example, the above
test might fail with this message:

FAILURE on input &Point{X:3, Y:4}
Did not match ToString(EqualTo([3, 4]))
Because: String() was "[4, 3]"
Did not match EqualTo[[3, 4]]
Because: "[4, 3]" was not equal to "[3, 4]"

Or:
FAILURE on input 5
Did not match EqualTo(5)
Because: uint 5 could not be compared to int 5
Comment: magnitude

Note that the majority of the text descriptions are generated
automatically by the matchers. For typical uses of Hamcrest
matchers, the code is largely self-documenting, and the error
messages are detailed.

Effort invested in good self-describing matchers can be leveraged
across many tests.

Examples of using Hamcrest at runtime:
======================================

Just as in the testing example, create an `Asserter`, but use a factory
method such as `UsingStderr()`, which returns an `Asserter` that logs
problems to stderr and calls `panic` on `FailNow`:

import (
"github.com/rdrdr/hamcrest/asserter"
)

var we = asserter.UsingStderr()

Use that asserter during init() to make sure globals are properly
initialized:

import (
"github.com/rdrdr/hamcrest/asserter"
"github.com/rdrdr/hamcrest/slices"
. "github.com/rdrdr/hamcrest/core"
"github.com/rdrdr/hamcrest/strings"
)
var we = asserter.UsingStderr()

type Server struct {
hostname string
port uint16
}
var servers = []Server {
{ "news.foo.com", 8000 },
{ "news.bar.com", 8888 },
}

func init() {
ToHostname := Applying(func(s Server) string {
return s.hostname
}, "TransformServerToHostname")
IsInOneOfOurDomains := AnyOf(strings.HasSuffix(".foo.com"),
strings.HasSuffix(".bar.com"))
we.FailNowUnless(servers,
slices.EachElem(ToHostname(IsInOneOfOurDomains)))
}

Or use the asserter at runtime to guarantee that a method's
preconditions are met:

var IsValidFilenameForTextFile = AllOf(
strings.HasSuffix(".txt").Comment("Must have .txt extension."),
Not(strings.HasPattern("[ \\t\\n\\r]")).Comment("whitespace not permitted"),
Not(strings.HasPattern("[:////\\\\]")).Comment("path separators not permitted"))

func WriteTo(filename string) bool {
we.AssertThat(filename, IsValidFilenameForTextFile)
// Use filename here.
}

Or use the asserter with `defer` to program by contract, knowing that you
can later swap out your typical `Asserter` with an
`asserter.ThatDoesNothing()` to bypass those checks:

IsWellFormedMD5 := AllOf(
ToLen(EqualTo(32)).Comment("MD5 hashes have length 32"),
strings.HasPattern("^([0-9][A-F][a-f])+$").Comment("hex digits")))

func MD5(data []byte) (result string) {
we.AssertNonNil(data)
defer we.AssertThat(result, IsWellFormedMD5)

// calculate MD5 of data
}

Or use it during development to write your tests in the same file as your code:

func EncodePigLatin(input string) string {
...implementation...
}

func init() {
we := asserter.UsingStderr()
ToEncoded := Applying(EncodePigLatin, "in PigLatin form")
we.AssertThat("simple", ToEncoded(EqualTo("imple-say")))
we.AssertThat("Capital"), ToEncoded(EqualTo("Apital-Cay")))
we.AssertThat("prefix"), ToEncoded(EqualTo("efix-pray")))
we.AssertThat("oops"), ToEncoded(EqualTo("oops-ay")))
we.AssertThat("your psychotic y!"),
ToEncoded(EqualTo("our-yay ychotic-psay y-ay!")))
}

At some future point, this structure will make it easier to cut-and-paste
each `init()` block into your testing suite. While moving the block over,
replace:

func init() {
we := asserter.UsingStderr()
...

With an `Asserter` that uses the testing infrastructure, instead:

func Test_EncodePigLatin(t *testing.T) {
we := asserter.Using(t)
...

A note on library design:
=========================

Hamcrest is designed to be a fluent library. The Go syntax requires external
symbols be preceded by their package name, which can lead to occasionally
awkward constructions:
we.CheckThat("foobar", core.AllOf(strings.HasPrefix("foo"), strings.HasSuffix("bar")))

To avoid this clunkiness, Hamcrest matchers are generated by functions that
you can assign to local names:
AllOf := core.AllOf
StartsWith := strings.HasPrefix
EndsWith := strings.HasSuffix
we.CheckThat("foobar", AllOf(StartsWith("foo"), EndsWith("bar")))

Performance note:
=================
Note: Hamcrest matchers allocate Description and Result objects to
explain in great detail why they did or did not match. However, these
objects are lazily evaluated. Users should be generally aware that
there is an *object allocation* cost to using Hamcrest matchers, but
there is (generally) no *string construction* cost unless a Hamcrest
Matcher or Result is explicitly asked to self-describe.

Still, users who are particularly sensitive to performance concerns
may wish to think carefully before using Hamcrest matchers in
performance-critical bottlenecks.

A tour of common matchers
=========================

Hamcrest comes with a library of useful matchers. Here are some of the most
common ones.

* `Anything` - matches any input.
* `True` - only matches bool `true`.
* `False` - only matches bool `false`.
* `Not(matcher)` - logical not of `matcher`.
* `If(m1).Then(m2)` - checks that whenever `m1` matches, so does `m2`.
* `IfAndOnlyIf(m1).Then(m2)` - checks that `m1` and `m2` both match/don't match.
* `EqualTo(y)` - matches any value `x` where `x == y` is legal and true.
* `NotEqualTo(y)` - matches any value `x` where `x != y` is legal and true.
* `DeepEqualTo(y)` - matches any value `x` where `reflect.DeepEquals(x, y)` is true.
* `GreaterThan(y)` - matches any value `x` where `x > y` is legal and true.
* `GreaterThanOrEqualTo(y)` - matches any value `x` where `x <= y` is legal and true.
* `LessThan(y)` - matches any value `x` where `x < y` is legal and true.
* `LessThanOrEqualTo(y)` - matches any value `x` where `x <= y` is legal and true.
* `Nil` - matches values of any type with an `IsNil() bool` method that returns true for the given object.
* `NonNil` - matches values of any type with an `IsNil() bool` method that returns false for the given object.
* `AnyOf(matchers...)` - short-circuiting n-ary logical Or.
* `AllOf(matchers...)` - short-circuiting n-ary logical And.

Syntactic sugar
===============

Hamcrest strives to make your tests as readable as possible. For example,
the `Is` matcher is a wrapper that doesn't add any extra behavior to the
underlying matcher. The following assertions are equivalent:

we.AssertThat(x, EqualTo(y));
we.AssertThat(x, Is(EqualTo(y)))

Similarly, it is possible to simulate common logical conditions using the `logic`
package for readability:

* `Is(matcher)` - equivalent to `matcher`
* `Both(matcher1).And(matcher2)` - short-circuiting logical `And`, equivalent to `AllOf(matcher1, matcher2)`
* `Either(matcher1).Or(matcher2)` - short-circuiting logical `Or`, equivalent to `AnyOf(matcher1, matcher2)`
* `Neither(matcher1).Nor(matcher2)` - short-circuiting logical `Nor`
* `If(matcher1).Then(matcher2)` - short-circuiting logical `If/Then`
* `Iff(matcher1).Then(matcher2)` - logical `IfAndOnlyIf` (note: iff never short-circuits)
* `Either(matcher1).Xor(matcher)` - logical `Xor` (note: xor never short-circuits)


Custom matchers
===============

Example:

func IsMultipleOf(k int) *base.Matcher {
match := func(n int) bool {
return n % k == 0
}
return base.NewMatcherf(match, "multiple of %v", k)
}

And used:

we.CheckThat(recordSize, IsMultipleOf(8).Comment(
"profiling suggests better performance than multiple of 4"))

Or, if you want more control over the error messages:

func IsMultipleOf(k int) *base.Matcher {
match := func(actual interface{}) *base.Result {
if n, ok := actual.(int); ok {
if remainder := n % k; remainder != 0 {
return base.NewResultf(false,
"was not a multiple of %v (remainder %v)", k, remainder)
}
return base.NewResultf(true, "was a multiple of %v", k)
}
return base.NewResultf(false, "was a %T, expected an int!", actual)
}
return base.NewMatcherf(match, "multiple of %v", k)
}