diff --git a/examples/Hedgehog.Xunit.Examples.CSharp/DocumentationSamples.cs b/examples/Hedgehog.Xunit.Examples.CSharp/DocumentationSamples.cs index f820d9b..092b1c0 100644 --- a/examples/Hedgehog.Xunit.Examples.CSharp/DocumentationSamples.cs +++ b/examples/Hedgehog.Xunit.Examples.CSharp/DocumentationSamples.cs @@ -5,12 +5,29 @@ using Hedgehog.Linq; using Hedgehog.Xunit; using Microsoft.FSharp.Core; -using static Hedgehog.Linq.Property; using Gen = Linq.Gen; +using Property = Linq.Property; using Range = Linq.Range; public class DocumentationSamples { + [Fact] + public void Reversing_a_list_twice_yields_the_original_list() + { + var gen = GenX.auto>(); + var prop = from xs in Property.ForAll(gen) + let testList = Enumerable.Reverse(xs).Reverse().ToList() + select Assert.Equal(xs, testList); + prop.Check(); + } + + [Property] + public void Reversing_a_list_twice_yields_the_original_list_with_xunit(List xs) + { + var testList = Enumerable.Reverse(xs).Reverse().ToList(); + Assert.Equal(xs, testList); + } + private readonly ITestOutputHelper _output; public DocumentationSamples(ITestOutputHelper output) @@ -29,49 +46,35 @@ public void Can_generate_an_int( [Property(Skip = "For documentation purposes")] public bool Will_fail(bool value) => value; - //[Property] - [Property(Skip = "For documentation purposes")] - public void Will_fail_by_assertion( - bool value) => - Assert.True(value, "All booleans values should be true!"); - //[Property] [Property(Skip = "Problem with async method")] - public async Task Async_with_exception_shrinks(int i) + public async Task Task_with_exception_shrinks(int i) { await Task.Delay(100); - //Assert.True(i +i == 1); - return true; + if (i > 10) throw new Exception("whoops"); } + //[Property] + [Property(Skip = "Problem with Result")] + public FSharpResult Result_with_Error_shrinks(int i) => + i < 10 + ? FSharpResult.NewOk(i) + : FSharpResult.NewError("humbug!"); + + //[Property] + [Property(Skip = "For documentation purposes")] + public Property Returning_a_failing_property_bool_with_an_external_number_gen_fails_and_shrinks(int i) => + from fifty in Property.ForAll(Hedgehog.Gen.constant(50)) + select i <= fifty; + public class AutoGenConfigContainer { public static AutoGenConfig _ => GenX.defaults.WithGenerator(Gen.Int32(Range.FromValue(13))); } - [Property(tests: 3)] - public void This_runs_3_times() => _output.WriteLine($"Test run"); - - [Property(Shrinks = 0, Skip = "For documentation purposes")] - public void No_shrinks_occur(int i) - { - if (i > 50) - { - throw new Exception("oops"); - } - } - - [Fact] - public void Can_create_some() - { - var t = FSharpOption.Some(5); - } - [Property(typeof(AutoGenConfigContainer))] - public bool This_test_passes_because_always_13( - int i) => - i == 13; + public bool This_test_passes_because_always_13(int i) => i == 13; public class ConfigWithArgs { @@ -79,70 +82,90 @@ public static AutoGenConfig _( string word, int number) => GenX.defaults - .WithGenerator(Hedgehog.Gen.constant(word)) - .WithGenerator(Hedgehog.Gen.constant(number)); + .WithGenerator(Hedgehog.Gen.constant(word)) + .WithGenerator(Hedgehog.Gen.constant(number)); } [Property(AutoGenConfig = typeof(ConfigWithArgs), AutoGenConfigArgs = new object[] { "foo", 13 })] public bool This_also_passes(string s, int i) => - s == "foo" && i == 13; + s == "foo" && i == 13; - internal static Task FooAsync() + [Property(tests: 3)] + public void This_runs_3_times() => _output.WriteLine($"Test run"); + + [Property(Shrinks = 0, Skip = "For documentation purposes")] + public void No_shrinks_occur(int i) { - return Task.Delay(100); + if (i > 50) + { + throw new Exception("oops"); + } } + [Property(Size = 2)] + public void i_mostly_ranges_between_neg_1_and_1(int i) => _output.WriteLine(i.ToString()); + [Property] - public async Task Async_Task_property( - int i) + [Recheck("44_13097736474433561873_6153509253234735533_")] + public bool this_passes(int i) => i == 12345; + + public class Five : GenAttribute { - await FooAsync(); - Assert.StrictEqual(i, i); + public override Gen Generator => Gen.Int32(Range.FromValue(5)); } [Property] - public Task Task_property( - int i) + public bool Can_set_parameter_as_5( + [Five] int five) => + five == 5; + + public class ConstInt : GenAttribute { - Assert.StrictEqual(i, i); - return Task.Delay(100); + private readonly int _i; + public ConstInt(int i) + { + _i = i; + } + public override Gen Generator => Gen.Int32(Range.FromValue(_i)); } - [Property] - public async Task Async_boolean(bool i) + [Property(typeof(AutoGenConfigContainer))] + public bool GenAttribute_overrides_Property_AutoGenConfig(int thirteen, [ConstInt(6)] int six) => + thirteen == 13 && six == 6; + + [Properties(Tests = 13, AutoGenConfig = typeof(AutoGenConfigContainer))] + public class __ { - await FooAsync(); - return i || !i; + + [Property(AutoGenConfig = typeof(AutoGenConfigContainer), Tests = 2718, Skip = "just because")] + public void Not_sure_why_youd_do_this_but_okay() { } } - [Property] - public async Task Task_boolean(bool i) + public class Int13 { - await Task.Delay(100); - return i || !i; + public static AutoGenConfig _ => + GenX.defaults.WithGenerator(Gen.Int32(Range.FromValue(13))); } - [Fact] - public void Reversing_a_list_twice_yields_the_original_list() + public class PropertyInt13Attribute : PropertyAttribute { - var gen = GenX.auto>(); - var prop = from data in ForAll(gen) - let testList = Enumerable.Reverse(data).Reverse().ToList() - select Assert.Equivalent(data, testList, true); - prop.Check(); + public PropertyInt13Attribute() : base(typeof(Int13)) { } } - [Property] - public void Reversing_a_list_twice_yields_the_original_list_with_xunit(List xs) + [PropertyInt13] + public bool This_passes(int i) => i == 13; + + public class PropertiesInt13Attribute : PropertiesAttribute { - var testList = Enumerable.Reverse(xs).ToList(); - Assert.Equivalent(xs, testList, true); + public PropertiesInt13Attribute() : base(typeof(Int13)) { } } - [Property] - [Recheck("44_13097736474433561873_6153509253234735533_")] - public bool this_passes_now(int i) => - i == 12345; + [PropertiesInt13] + public class ___ + { + [Property] + public bool This_also_passes(int i) => i == 13; + } } @@ -169,5 +192,3 @@ public bool this_passes_passes_and_runs_twice( int i) => i == 2718; } - - diff --git a/readme.md b/readme.md index 8489e68..ccba702 100644 --- a/readme.md +++ b/readme.md @@ -49,13 +49,13 @@ let ``Reversing a list twice yields the original list, with Hedgehog.Xunit`` (xs ## Documentation `Hedgehog.Xunit` provides the following attributes: -* [`Property`](#-property) +* [`[]`](#-property) Extends xUnit's `Fact` to call Hedgehog's `property`. -* [`Properties`](#-properties) +* [`[]`](#-properties) Configures all [`Property`](#-property) tagged tests in a module or class. * [`GenAttribute`](#-genattribute) Set a parameter's generator. -* [`Recheck`](#-recheck) +* [`[]`](#-recheck) Run a test with a specific `Size` and `Seed`. ### 🔖 `[]` diff --git a/readmeCSharp.md b/readmeCSharp.md index 564b893..34d17e2 100644 --- a/readmeCSharp.md +++ b/readmeCSharp.md @@ -34,15 +34,15 @@ using Property = Hedgehog.Linq.Property; public class DocumentationSamples { - [Fact] - public void Reversing_a_list_twice_yields_the_original_list() - { - var gen = GenX.auto>(); - var prop = from data in Property.ForAll(gen) - let testList = Enumerable.Reverse(data).Reverse().ToList() - select Assert.Equivalent(data, testList, true); - prop.Check(); - } + [Fact] + public void Reversing_a_list_twice_yields_the_original_list() + { + var gen = GenX.auto>(); + var prop = from xs in Property.ForAll(gen) + let testList = Enumerable.Reverse(xs).Reverse().ToList() + select Assert.Equal(xs, testList); + prop.Check(); + } } ``` @@ -52,298 +52,331 @@ Then using Hedgehog.Xunit, you can simplify the above test to [Property] public void Reversing_a_list_twice_yields_the_original_list_with_xunit(List xs) { - var testList = Enumerable.Reverse(xs).ToList(); - Assert.Equivalent(xs, testList, true); + var testList = Enumerable.Reverse(xs).Reverse().ToList(); + Assert.Equal(xs, testList); } ``` ## 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. configure +* [`[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`. > All code in the below is available [here](/examples/Hedgehog.Xunit.Examples.CSharp/DocumentationSamples.cs) -### `Property` attribute ---- -* Methods with the `Property` attribute have their arguments automatically generated by [`GenX.auto`](https://github.com/hedgehogqa/fsharp-hedgehog-experimental/#auto-generation); the generator used can be customised by either [AutoGenConfig](#autogenconfig) or [GenAttribute](#GenAttribute) - - ```C# - [Property] - public void Can_generate_an_int( - int i) - { - _output.WriteLine($"Test input: {i}"); - } - ``` - --- - ``` - === Output === - Test input: 0 - Test input: -1 - Test input: 1 - ... - Test input: 522317518 - Test input: 404306656 - Test input: 1550509078 - ``` - -* If the method return a Boolean then that is considered a property - - ```CSharp - [Property(Skip = "For documentation purposes")] - public bool Will_fail( - bool value) => value; - ``` - ``` - System.Exception - *** Failed! Falsifiable (after 4 tests): - [false] - Hedgehog.Xunit.TestReturnedFalseException: Test returned `false`. - ... - ``` - -* Async and not async `Task` are also considered properties - - ```CSharp - internal static Task FooAsync() - { - return Task.Delay(100); - } - - [Property] - public async Task Async_boolean(bool i) - { - await FooAsync(); - return i || !i; - } +### 🔖 `[Property]` - [Property] - public async Task Task_boolean(bool i) - { - await Task.Delay(100); - return i || !i; - } - ``` +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). -* If the property returns void then it should throw on failure like a normal unit test +```C# +public class DocumentationSamples +{ + private readonly ITestOutputHelper _output; - ```CSharp - [Property] - public void Will_fail_by_assertion( - bool value) => Assert.True(value, "All booleans values should be true!"); - ``` - ``` - System.Exception - *** Failed! Falsifiable (after 2 tests): - [false] - System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. - ---> Xunit.Sdk.TrueException: All booleans values should be true! - Expected: True - Actual: False - ``` - -* If the property returns task it is considered a property and should throw an exception if the test fails - - ```CSharp - internal static Task FooAsync() + public DocumentationSamples(ITestOutputHelper output) { - return Task.Delay(100); + _output = output; } [Property] - public async Task Async_Task_property( + public void Can_generate_an_int( int i) { - await FooAsync(); - Assert.True(i == i); + _output.WriteLine($"Test input: {i}"); } +} + +=== Output === +Test input: 0 +Test input: -1 +Test input: 1 +... +Test input: 522317518 +Test input: 404306656 +Test input: 1550509078 +``` - [Property] - public Task Task_property( - int i) - { - Assert.True(i == i); - return Task.Delay(100); - } - ``` - -### `Property` Configuration ---- -The `Property` attribute's constructor may take several arguments: -* [`AutoGenConfig`](#autogenconfig): Allow the manual control of generators -* [`Tests`](#tests-count): Specifies the number of tests to run -* [`Shrinks`](#shrinks-count): Specifies the number of shrinks - -The `Property` attribute extends `Xunit.FactAttribute`, so it may also take `DisplayName`, `Skip`, and `Timeout`. - -#### `AutoGenConfig` ---- -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) - - ```CSharp - public class AutoGenConfigContainer - { +`Property.check` is called. + +```C# +[Property] +public bool Will_fail(bool value) => value; +``` +``` +System.Exception: *** Failed! Falsifiable (after 5 tests): +[false] +Hedgehog.Xunit.TestReturnedFalseException: Test returned `false`. +... +``` + +If the test returns [`FSharp.Control.Async`] or `Task`, then `Async.RunSynchronously` is called, _which blocks the thread._ This may have significant performance implications as tests run 100 times by default. + +```C# +[Property] +public async Task Task_with_exception_shrinks(int i) +{ + await Task.Delay(100); + if (i > 10) throw new Exception(); +} +``` +``` +System.Exception: *** Failed! Falsifiable (after 14 tests and 1 shrink): +[11] +System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. + ---> System.AggregateException: One or more errors occurred. (whoops) + ---> System.Exception: whoops +``` +A test returning an [`FSharp.Core.Result`](https://fsharp.github.io/fsharp-core-docs/reference/fsharp-core-resultmodule.html) in an `Error` state will be treated as a failure. + +```C# +[Property] +public FSharpResult Result_with_Error_shrinks(int i) => + i < 10 + ? FSharpResult.NewOk(i) + : FSharpResult.NewError("humbug!"); +``` +``` +System.Exception: *** Failed! Falsifiable (after 15 tests and 1 shrink): +[10] +System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. + ---> System.Exception: Result is in the Error case with the following value: +"humbug!" +``` + +Tests returning `Async>` or `Task>` are run synchronously and are expected to be in the `Ok` state. + +Tests returning a `Property` or `Property` will have `Property.check` automatically called: + +```C# +[Property] +public Property Returning_a_failing_property_bool_with_an_external_number_gen_fails_and_shrinks(int i) => + from fifty in Property.ForAll(Hedgehog.Gen.constant(50)) + select i <= fifty; +``` +``` +System.Exception: *** Failed! Falsifiable (after 25 tests and 4 shrinks): +[51] +50 +``` + +#### ⚙️ `[Property]` Configuration + +`[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. + +The `Property` attribute extends `Xunit.FactAttribute`, so it may also take `DisplayName`, `Skip`, and `Timeout`. + +#### 🧰 `AutoGenConfig` and `AutoGenConfigArgs` + +[`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)) + +```C# +public class AutoGenConfigContainer +{ public static AutoGenConfig _ => - GenX.defaults.WithGenerator(Gen.Int32(Range.FromValue(13))); - } + GenX.defaults.WithGenerator(Gen.Int32(Range.FromValue(13))); +} + +[Property(typeof(AutoGenConfigContainer))] +public bool This_test_passes_because_always_13(int i) => i == 13; +``` + +If the method takes arguments, you must provide them using `AutoGenConfigArgs`. + +```C# +public class ConfigWithArgs +{ + public static AutoGenConfig _( + string word, + int number) => + GenX.defaults + .WithGenerator(Hedgehog.Gen.constant(word)) + .WithGenerator(Hedgehog.Gen.constant(number)); +} + +[Property(AutoGenConfig = typeof(ConfigWithArgs), AutoGenConfigArgs = new object[] { "foo", 13 })] +public bool This_also_passes(string s, int i) => + s == "foo" && i == 13; +``` - [Property(typeof(AutoGenConfigContainer))] - public bool This_test_passes_because_always_13( - int i) => - 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. -```CSharp +```C# [Property(tests: 3)] public void This_runs_3_times() => _output.WriteLine($"Test run"); ``` -##### `Size` ---- +#### 🧰 `Shrinks` + +Specifies the maximal number of shrinks that may run. + +```C# +[Property(Shrinks = 0] +public void No_shrinks_occur(int i) +{ + if (i > 50) + { + throw new Exception("oops"); + } +} +``` + +#### 🧰 `Size` + Sets the `Size` to a value for all runs. -```f# -[] -let ``"i" mostly ranges between -1 and 1`` i = - printfn "%i" i +```C# +[Property(Size = 2)] +public void i_mostly_ranges_between_neg_1_and_1(int i) => _output.WriteLine(i.ToString()); ``` -#### Properties attribute ---- -This optional attribute can decorate modules or classes. It sets default arguments for [`AutoGenConfig`](#autogenconfig), [`Tests`](#tests-count), [`Shrinks`](#shrinks-count), and [`Size`](#size). These will be overridden by any arguments provided by the `Property` attribute. +### 🔖 `[Properties]` -```CSharp +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]`. + +```C# public class Int13 { - public static AutoGenConfig _ => GenX.defaults.WithGenerator(Hedgehog.Gen.constant(13)); + public static AutoGenConfig _ => GenX.defaults.WithGenerator(Hedgehog.Gen.constant(13)); } public class Int2718 { - public static AutoGenConfig _ => GenX.defaults.WithGenerator(Hedgehog.Gen.constant(2718)); + public static AutoGenConfig _ => GenX.defaults.WithGenerator(Hedgehog.Gen.constant(2718)); } -[Properties(typeof(Int13),1)] +[Properties(typeof(Int13), 1)] public class PropertiesSample { - [Property] - public bool this_passes_and_runs_1( - int i) => - i == 13; - - [Property(typeof(Int2718), 2)] - public bool this_passes_passes_and_runs_twice( - int i) => - i == 2718; + [Property] + public bool this_passes_and_runs_once( + int i) => + i == 13; + + [Property(typeof(Int2718), 2)] + public bool this_passes_passes_and_runs_twice( + int i) => + 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`: + +```C# +public class Five : GenAttribute +{ + public override Gen Generator => Gen.Int32(Range.FromValue(5)); +} -```CSharp [Property] -[Recheck("44_13097736474433561873_6153509253234735533_")] -public bool this_passes_now(int i) => - i == 12345; +public bool Can_set_parameter_as_5( + [Five] int five) => + five == 5; ``` -### GenAttribute ---- -This is the base type of an attribute that can applied property function arguments. It allows you to provide a generator on a parameter by parameter basis. +Here's a more complex example of `GenAttribute` that takes a parameter and overrides `Property`'s `AutoGenConfig`: + +```C# +public class AutoGenConfigContainer +{ + public static AutoGenConfig _ => + GenX.defaults.WithGenerator(Gen.Int32(Range.FromValue(13))); +} + +public class ConstInt : GenAttribute +{ + private readonly int _i; + public ConstInt(int i) + { + _i = i; + } + public override Gen Generator => Gen.Int32(Range.FromValue(_i)); +} -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](/examples/Hedgehog.Xunit.Examples.CSharp/attribute-based-parameter-comparison/PositiveAndNegativeGeneratorContainerTypes.cs). +[Property(typeof(AutoGenConfigContainer))] +public bool GenAttribute_overrides_Property_AutoGenConfig(int thirteen, [ConstInt(6)] int six) => + thirteen == 13 && six == 6;, +``` +### 🔖 `[Recheck]` -```CSharp -public record PositiveInt(int Value); -public record NegativeInt( int Value ); +This optional method attribute invokes `Property.recheck` with the given `Size` and `Seed`. It must be used with `Property`. -public class Generators -{ - public static Gen GenPositiveInt => - from x in Gen.Int32(Range.Constant(1, Int32.MaxValue)) - select new PositiveInt(x); +```C# +[Property] +[Recheck("44_13097736474433561873_6153509253234735533_")] +public bool this_passes(int i) => i == 12345; +``` - public static Gen GenNegativeInt => - from x in Gen.Int32(Range.Constant(Int32.MinValue, -1)) - select new NegativeInt(x); +## Tips - public static AutoGenConfig _ => GenX.defaults - .WithGenerator(GenPositiveInt) - .WithGenerator(GenNegativeInt); -} +Use named arguments to select the desired constructor overload. -public class PositiveAndNegativeGeneratorContainerTypes +```C# +[Properties(Tests = 13, AutoGenConfig = typeof(AutoGenConfigContainer))] +public class __ { - [Property(typeof(Generators))] - public bool ResultOfAddingPositiveAndNegativeLessThanPositive( - PositiveInt positive, - NegativeInt negative) - => positive.Value + negative.Value < positive.Value; + [Property(AutoGenConfig = typeof(AutoGenConfigContainer), Tests = 2718, Skip = "just because")] + public void Not_sure_why_youd_do_this_but_okay() + { + + } } ``` - Using the `GenAttribute` attribute is would look like [this](/examples/Hedgehog.Xunit.Examples.CSharp/attribute-based-parameter-comparison/PositiveAndNegativeSimpleAttribute.cs): +Consider extending `PropertyAttribute` or `PropertiesAttribute` to hardcode commonly used arguments. - ```CSharp -public class Negative : GenAttribute +```C# +public class Int13 { - public override Gen Generator => Gen.Int32(Range.Constant(Int32.MinValue, -1)); + public static AutoGenConfig _ => + GenX.defaults.WithGenerator(Gen.Int32(Range.FromValue(13))); } -public class Positive : GenAttribute + +public class PropertyInt13Attribute : PropertyAttribute { - public override Gen Generator => Gen.Int32(Range.Constant(1, Int32.MaxValue)); + public PropertyInt13Attribute() : base(typeof(Int13)) { } } -public class PositiveAndNegativeUtilizingIntegerRangeAttribute +[PropertyInt13] +public bool This_passes(int i) => i == 13; + +public class PropertiesInt13Attribute : PropertiesAttribute { - [Property] - public bool ResultOfAddingPositiveAndNegativeLessThanPositive( - [Positive] int positive, - [Negative] int negative) - => positive + negative < positive; + public PropertiesInt13Attribute() : base(typeof(Int13)) { } } -``` -We can also supply parameters to the generator like [so](/examples/Hedgehog.Xunit.Examples.CSharp/attribute-based-parameter-comparison/PositiveAndNegativeUtilizingIntegerRangeAttribute.cs): - -```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() - override this.Generator = Range.constant minimum maximum |> Gen.int32 +[PropertiesInt13] +public class ___ +{ + [Property] + public bool This_also_passes(int i) => i == 13; +} +``` +[hedgehog]: https://github.com/hedgehogqa/fsharp-hedgehog +[xunit]: https://xunit.net/ -[] -let ``Positive + Negative <= Positive attribute parameterised`` - ([] positive) - ([] negative) = - positive + negative <= positive -``` - - -The attribute fulfils a very similar function to specifying the [`AutoGenConfig`](#autogenconfig) parameter on the property. If all your tests use the same generators it may be more less code to use [`AutoGenConfig`](#autogenconfig), 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. - +[nuget]: https://www.nuget.org/packages/Hedgehog.Xunit/ +[nuget-shield]: https://img.shields.io/nuget/v/Hedgehog.Xunit.svg +[workflow]: https://github.com/dharmaturtle/fsharp-hedgehog-xunit/actions?query=workflow%3AMain +[workflow-shield]: https://github.com/dharmaturtle/fsharp-hedgehog-xunit/workflows/Main/badge.svg