How to create null strings for FsCheck tests

Using FsCheck , the F # version of the Haskell QuickCheck test library, to generate tests from C # , I found that the random string generator does not generate a null string.

using FsCheck.Fluent; Spec.ForAny<string>(s => s != null).QuickCheck(); // always pass 

Also, apparently, the design does not handle null strings, but I was not able to bind it to the documentation . For example, simply choosing between two lines, one of which is null, will not work:

 var strings = Any.ValueIn<string>(null, "non-null string"); Spec.For(strings, s => true).QuickCheck(); // throws null ref exception 

And the strings seem to be a special case as it handles user-created objects such as

 class Thing {} 

when mixing with zero values:

 var objects = Any.ValueIn(null, new Thing()); Spec.For(objects, s => true).QuickCheck(); // pass 
+7
c # c # -to-f # fscheck
source share
2 answers

I tried to figure this out a bit, and it seems like you found an error in FsCheck.

It seems that the problem is in the Arbitrary.fs file and is really only related to the string. I had to replace this where they called ToCharArray on the line

  static member String() = { new Arbitrary<string>() with override x.Generator = Gen.map (fun chars -> new String(List.toArray chars)) generate override x.Shrinker s = s.ToCharArray() |> Array.toList |> shrink |> Seq.map (fun chars -> new String(List.toArray chars)) } 

with this

  static member String() = { new Arbitrary<string>() with override x.Generator = Gen.map (fun chars -> new String(List.toArray chars)) generate override x.Shrinker s = match s with | null -> seq {yield null;} | _ -> s.ToCharArray() |> Array.toList |> shrink |> Seq.map (fun chars -> new String(List.toArray chars)) } 

You might want to bring this up with the help of fscheck developers here , and also check if my fix works well - maybe the best way to implement it, but it would be easier for someone who already knows the code.

+4
source share

For FsCheck 1.x, I found a solution that includes modifying the default random string generator:

 public class MyArbitraries { public static Arbitrary<string> String() { var nulls = Any.Value<string>(null); var nonnulls = Arb.Default.String().Generator; return Any.GeneratorIn(nulls, nonnulls).ToArbitrary; } } 

and then initialize it with

 DefaultArbitraries.Add<MyArbitraries>(); 

Then the test in question does not fit, as intended:

 Spec.ForAny<string>(s => s != null).QuickCheck() // now fails, which is good 

This will create about 50% zeros and 50% random strings, the scales can be adjusted:

 Spec.ForAny<string>(s => true) .Classify(s => s==null, "null") .Classify(s => s!=null, "not null") .QuickCheck(); // displays percentages 

However, effectively overlapping the default line generator may not be practical if the decision to include a default value of zero was intentional and not an error in the library. And, if it were a mistake, it would distort the distribution when fixed.

+1
source share

All Articles