Skip to content

Commit

Permalink
more or less done with Readme
Browse files Browse the repository at this point in the history
  • Loading branch information
dharmaturtle committed Jul 3, 2023
1 parent b04d42f commit 9915cfd
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 131 deletions.
216 changes: 89 additions & 127 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# Hedgehog.Xunit

> **Note**
> ## This readme is for F#. Go [here for C# documentation.](/readmeCSharp.md)
[![][nuget-shield]][nuget] [![][workflow-shield]][workflow] [![Coverage Status](https://coveralls.io/repos/github/dharmaturtle/fsharp-hedgehog-xunit/badge.svg?branch=main)](https://coveralls.io/github/dharmaturtle/fsharp-hedgehog-xunit?branch=main)

[Hedgehog][hedgehog] with convenience attributes for [xUnit.net][xunit].
Expand All @@ -11,11 +8,15 @@

## Features

- Test method arguments can be generated [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation).
- Test method arguments generated with a custom [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation)...
- ...or with a custom [Generator](https://hedgehogqa.github.io/fsharp-hedgehog/index.html#Generators).
- `Property.check` called for each test.
- Attribute based generator definitions

## Getting Started
## Getting Started in C#

This readme is for F#. Go [here for C# documentation.](/readmeCSharp.md)

## Getting Started in F#

Install the _Hedgehog.Xunit_ [package][nuget] from Visual Studio's Package Manager Console:

Expand Down Expand Up @@ -48,18 +49,18 @@ let ``Reversing a list twice yields the original list, with Hedgehog.Xunit`` (xs
## Documentation

`Hedgehog.Xunit` provides the following attributes:
* [Property](#properties-attribute)
Converts an XUnit `Fact` into a property, allowing you to easily configure property parameters.
* [Properties](#properties-attribute)
Allows the easy configuration of all properties in a test suite.
* [Recheck](#recheck-attribute)
Rerun a particular test case.
* [GenAttribute](#GenAttribute)
Control what generator is used on a parameter by parameter basis.

### `Property` attribute
---
Methods with the `Property` attribute have their arguments automatically generated by [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation).
* [`Property`](#-property)
Extends xUnit's `Fact` to call Hedgehog's `property`.
* [`Properties`](#-properties)
Configures all [`Property`](#-property) tagged tests in a module or class.
* [`GenAttribute`](#-genattribute)
Set a parameter's generator.
* [`Recheck`](#-recheck)
Run a test with a specific `Size` and `Seed`.

### 🔖 `[<Property>]`

Methods with `[<Property>]` have their arguments generated by [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation), unless the argument is decorated with a subclass of [`GenAttribute`](#-genattribute).

```f#
type ``class with a test`` (output: Xunit.Abstractions.ITestOutputHelper) =
Expand All @@ -77,7 +78,7 @@ Test input: 404306656
Test input: 1550509078
```

`Property.check` is called automatically.
`Property.check` is called.

```f#
[<Property>]
Expand Down Expand Up @@ -135,47 +136,50 @@ System.Exception: *** Failed! Falsifiable (after 23 tests and 5 shrinks):
50
```

### `Property` Configuration
---
The `Property` attribute's constructor may take several arguments:
* [`AutoGenConfig` and `AutoGenConfigArgs`](#autogenconfig-and-autogenconfigargs): Allow the manual control of generators
* [`Tests`](#tests-count): Specifies the number of tests to run
* [`Shrinks`](#shrinks-count): Specifies the number of shrinks
#### ⚙️ `[<Property>]` Configuration

The `Property` attribute extends `Xunit.FactAttribute`, so it may also take `DisplayName`, `Skip`, and `Timeout`.
`[<Property>]`'s constructor may take several arguments:
* [`AutoGenConfig` and `AutoGenConfigArgs`](#-autogenconfig-and-autogenconfigargs): Set an `AutoGenConfig` to use when generating arguments.
* [`Tests`](#-tests): Specifies the number of tests to be run.
* [`Shrinks`](#-shrinks): Specifies the maximal number of shrinks that may run.
* [`Size`](#-size): Sets the `Size` to a value for all runs.

#### `AutoGenConfig` and `AutoGenConfigArgs`
---
The default generator used to create arguments is `GenX.defaults`. To specify different generators:
* Create a class with a single static property or method that returns an instance of `AutoGenConfig`.
* Provide the type of this class as an argument to the `Property` attribute. This works around the constraint that [`Attribute` parameters must be a constant.](https://stackoverflow.com/a/33007272)
The `Property` attribute extends `Xunit.FactAttribute`, so it may also take `DisplayName`, `Skip`, and `Timeout`.

```f#
type AutoGenConfigContainer =
static member __ =
GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 13)
#### 🧰 `AutoGenConfig` and `AutoGenConfigArgs`

[<Property(typeof<AutoGenConfigContainer>)>]
let ``This test passes`` (i: int) =
i = 13
```
[`GenX.defaults`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/blob/6d8719aaa6568c51478a39e2ad76afbcd43d5b8e/src/Hedgehog.Experimental/Gen.fs#L356) is the [`AutoGenConfig`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/blob/6d8719aaa6568c51478a39e2ad76afbcd43d5b8e/src/Hedgehog.Experimental/Gen.fs#L17) used by default.

Here's how to add your own generators:
1. Create a class with a single static property or method that returns an instance of `AutoGenConfig`.
2. Provide the type of this class as an argument to `[<Property>]`. (This works around the constraint that [`Attribute` parameters must be a constant.](https://stackoverflow.com/a/33007272))

```f#
type AutoGenConfigContainer =
static member __ =
GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 13)
[<Property(typeof<AutoGenConfigContainer>)>]
let ``This test passes`` (i: int) =
i = 13
```

* If the method takes arguments, you must provide them using `AutoGenConfigArgs`.
If the method takes arguments, you must provide them using `AutoGenConfigArgs`.

```f#
type ConfigWithArgs =
static member __ a b =
GenX.defaults
|> AutoGenConfig.addGenerator (Gen.constant a)
|> AutoGenConfig.addGenerator (Gen.constant b)
```f#
type ConfigWithArgs =
static member __ a b =
GenX.defaults
|> AutoGenConfig.addGenerator (Gen.constant a)
|> AutoGenConfig.addGenerator (Gen.constant b)
[<Property(AutoGenConfig = typeof<ConfigWithArgs>, AutoGenConfigArgs = [|"foo"; 13|])>]
let ``This also passes`` s i =
s = "foo" && i = 13
```
[<Property(AutoGenConfig = typeof<ConfigWithArgs>, AutoGenConfigArgs = [|"foo"; 13|])>]
let ``This also passes`` s i =
s = "foo" && i = 13
```

#### 🧰 `Tests`

#### `Tests` (count)
---
Specifies the number of tests to be run, though more or less may occur due to shrinking or early failure.

```f#
Expand All @@ -184,8 +188,8 @@ let ``This runs 3 times`` () =
()
```

#### `Shrinks` (count)
---
#### 🧰 `Shrinks`

Specifies the maximal number of shrinks that may run.

```f#
Expand All @@ -194,8 +198,8 @@ let ``No shrinks occur`` i =
if i > 50 then failwith "oops"
```

##### `Size`
---
#### 🧰 `Size`

Sets the `Size` to a value for all runs.

```f#
Expand All @@ -204,9 +208,9 @@ let ``"i" mostly ranges between -1 and 1`` i =
printfn "%i" i
```

#### Properties attribute
---
This optional attribute can decorate modules or classes. It sets default arguments for [`AutoGenConfig`, `AutoGenConfigArgs`](#autogenconfig-and-autogenconfigargs), [`Tests`](#tests-count), [`Shrinks`](#shrinks-count), and [`Size`](#size). These will be overridden by any arguments provided by the `Property` attribute.
### 🔖 `[<Properties>]`

This optional attribute can decorate modules or classes. It sets default arguments for [`AutoGenConfig`, `AutoGenConfigArgs`](#-autogenconfig-and-autogenconfigargs), [`Tests`](#-tests), [`Shrinks`](#-shrinks), and [`Size`](#-size). These will be overridden by any explicit arguments on `[<Property>]`.

```f#
type Int13 = static member __ = GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 13)
Expand All @@ -224,90 +228,48 @@ module ``Module with <Properties> tests`` =
i = 2718
```

### Recheck attribute
---
This optional method attribute invokes `Property.recheck` with the given `Size` and `Seed`, it must be used with `Property`.
### 🔖 `GenAttribute`

To assign a generator to a test's parameter, extend `GenAttribute` and override `Generator`:

```f#
type Int5() =
inherit GenAttribute<int>()
override _.Generator = Gen.constant 5
[<Property>]
[<Recheck("44_13097736474433561873_6153509253234735533_")>]
let ``this passes`` i =
i = 12345
let ``can set parameter as 5`` ([<Int5>] i) =
Assert.StrictEqual(5, i)
```

### GenAttribute
---
This is the base type of an attribute that can applied to property function arguments. It allows you to provide a generator on a argument by argument basis.

Lets look at the a property where we want to specify the generator to be used for two integer arguments.
Using a `Property` attribute on this turns out to be quite tricky as we require two different generators for the same type, we may end up with something like this, which wraps the integer value an a containing record.

```F#
open Xunit
open Hedgehog
let positiveInt() = Range.constant 0 System.Int32.MaxValue |> Gen.int32
let negativeInt() = Range.constant System.Int32.MinValue 0 |> Gen.int32
type PositiveInt = {value : int}
type NegativeInt = {value : int}
let positiveInt() = positiveInt() |> Gen.map(fun x-> {PositiveInt.value=x})
let negativeInt() = negativeInt() |> Gen.map(fun x -> {NegativeInt.value=x})
Here's a more complex example of `GenAttribute` that takes a parameter and overrides `Property`'s `AutoGenConfig`:

```f#
type AutoGenConfigContainer =
static member __ =
GenX.defaults
|> AutoGenConfig.addGenerator (positiveInt())
|> AutoGenConfig.addGenerator (negativeInt())
[<Property(typeof<AutoGenConfigContainer>)>]
let ``Positive + Negative <= Positive`` (positive:PositiveInt) (negative:NegativeInt) =
positive.value + negative.value <= positive.value
```

Using the `GenAttribute` attribute is would look like as below:

```F#
type Posint() =
inherit GenAttribute<int>()
override this.Generator = positiveInt()
GenX.defaults |> AutoGenConfig.addGenerator (Gen.constant 1)
type NegInt() =
type ConstInt(i: int)=
inherit GenAttribute<int>()
override this.Generator = negativeInt()
[<Property>]
let ``Positive + Negative <= Positive xunit`` ([<Posint>] positive) ([<NegInt>] negative) =
positive + negative <= positive
override _.Generator = Gen.constant i
[<Property(typeof<AutoGenConfigContainer>)>]
let ``GenAttribute overrides Property's AutoGenConfig`` (one, [<ConstInt 2>] two) =
Assert.StrictEqual(1, one)
Assert.StrictEqual(2, two)
```

We can also supply parameters to the generator like so:

```F#
//Using a parameterised attribute to configure the generators
//Using attributes to configure what generator the property should use
type IntRange(minimum:int32, maximum:int32) =
inherit GenAttribute<int32>()
override this.Generator = Range.constant minimum maximum |> Gen.int32
### 🔖 `[<Recheck>]`

This optional method attribute invokes `Property.recheck` with the given `Size` and `Seed`. It must be used with `Property`.

```f#
[<Property>]
let ``Positive + Negative <= Positive attribute parameterised``
([<IntRange(0, System.Int32.MaxValue)>] positive)
([<IntRange(System.Int32.MinValue, 0)>] negative) =
positive + negative <= positive
[<Recheck("44_13097736474433561873_6153509253234735533_")>]
let ``this passes`` i =
i = 12345
```

> The code above is in [Examples](/examples/fsharp-examples/attribute-based-parameter-comparison.fs)
The attribute fulfils a very similar function to specifying the [`AutoGenConfig`](#autogenconfig-and-autogenconfigargs) parameter on the property. If all your tests use the same generators it may be more less code to use [`AutoGenConfig`](#autogenconfig-and-autogenconfigargs), which can be applied to the [`Properties`](#properties-attribute) attribute.
Parameter generator attributes allow you to easily configure the generator per argument, useful if:
* A single property has multiple arguments of the same type which require different generators.
* You want to easily configure your generators a per property basis.
* Different properties require different generators.
* You want to be able to see per argument what generator was used.

## Tips

Use named arguments to select the desired constructor overload.
Expand Down
10 changes: 7 additions & 3 deletions readmeCSharp.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# fsharp-hedgehog-xunit
# Hedgehog.Xunit

[![][nuget-shield]][nuget] [![][workflow-shield]][workflow] [![Coverage Status](https://coveralls.io/repos/github/dharmaturtle/fsharp-hedgehog-xunit/badge.svg?branch=main)](https://coveralls.io/github/dharmaturtle/fsharp-hedgehog-xunit?branch=main)

Expand All @@ -8,11 +8,15 @@

## Features

- Test method arguments generated by the customizable [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation).
- Test method arguments generated with a custom [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation)...
- ...or with a custom [Generator](https://hedgehogqa.github.io/fsharp-hedgehog/index.html#Generators).
- `Property.check` called for each test.

## Getting Started in F#

## Getting Started
This readme is for C#. Go [here for F# documentation.](/readme.md)

## Getting Started in C#

Install the _Hedgehog.Xunit_ [package][nuget] from Visual Studio's Package Manager Console:

Expand Down
2 changes: 1 addition & 1 deletion tests/Hedgehog.Xunit.Tests.FSharp/PropertyTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@ module ``returning a property runs it`` =
module ``GenAttribute Tests`` =

[<Property>]
let ``can define parameter as 5`` ([<Int5>] i) =
let ``can set parameter as 5`` ([<Int5>] i) =
Assert.StrictEqual(5, i)

[<Property(typeof<Int13>)>]
Expand Down

0 comments on commit 9915cfd

Please sign in to comment.