Background
我有两个扩展方法,DateOnly.BeginningOfDay
&DateOnly.EndOfDay
,它们应该有助于将DateOnly
对象转换为DateTime
对象。这些方法的测试是用 FsCheck 编写的。FsCheck 没有用于DateOnly
和TimeOnly
类型的内置生成器,因此我为这些测试制作了自己的生成器。将我的代码签入我们的 CI 管道后,我最终遇到了一个DateOnly.BeginningOfDay
问题,即测试没有考虑 TimeOnly 值生成为 12:00:00 AM 的边缘情况,相当于TimeOnly.MinValue
.我调整了测试并重新运行了 CI 管道,并笨拙地使用这些边缘情况,以便使用一些常量生成与原始TimeOnly
生成Gen.OneOf
组合来更频繁地运行。我知道 FsCheck 会对其默认值进行一些特殊的边缘情况检查。我想知道是否有办法为在每次运行测试期间自动生成的类型定义一组边缘情况,而无需将它们视为Gen.OneOf
选项(毕竟,我们只希望其中一些边缘情况运行一次,再多就是浪费时间和功率)。
《守则》
日期仅扩展.cs
public static class DateOnlyExtensions
{
public static DateTime BeginningOfDay(this DateOnly date) =>
date.ToDateTime(TimeOnly.MinValue);
public static DateTime EndOfDay(this DateOnly date) =>
date.ToDateTime(TimeOnly.MaxValue);
}
DateOnlyExtensionTests.cs
注意
将参数分组到单个参数生成器中的唯一原因是因为我的项目无法识别诸如Prop.ForAll(OneArb, TwoArb, RedArb, BlueArb, (one, two, red, blue) => ...)
之类的类型。
添加显式边缘情况之前
public class DateOnlyExtensionTests
{
static Gen<int> MakeIntInRangeGen(int start, int end) =>
from @int in Arb.Generate<int>()
where @int >= start && @int < end
select @int;
static Gen<DateOnly> DateOnlyGen =>
from dateTime in Arb.Generate<DateTime>()
select DateOnly.FromDateTime(dateTime);
static Gen<TimeOnly> TimeOnlyGen =>
from hour in MakeIntInRangeGen(0, 24)
from minute in MakeIntInRangeGen(0, 60)
from second in MakeIntInRangeGen(0, 60)
from millisecond in MakeIntInRangeGen(0, 1000)
select new TimeOnly(hour, minute, second, millisecond);
public class BeginningOfDay
{
[Property]
public Property ShouldReturnTheEarliestTimeInTheDay()
{
var argsGen =
from time in TimeOnlyGen
from date in DateOnlyGen
select new { date, time };
return Prop.ForAll(
argsGen.ToArbitrary(),
args => args.date.BeginningOfDay() <= args.date.ToDateTime(args.time));
}
}
public class EndOfDay
{
[Property]
public Property ShouldReturnTheLatestTimeInTheDay()
{
var argsGen =
from time in TimeOnlyGen
from date in DateOnlyGen
select new { date, time };
return Prop.ForAll(
argsGen.ToArbitrary(),
args => args.date.EndOfDay() >= args.date.ToDateTime(args.time));
}
}
}
}
添加显式边缘情况后
using System;
using Core.Dates;
using FluentAssertions;
using FsCheck;
using FsCheck.Xunit;
using TestUtilities;
using static Core.Math.MathHelpers;
namespace Core.Tests.Dates
{
public class DateOnlyExtensionTests
{
static Gen<int> MakeIntInRangeGen(int start, int end) =>
from @int in Arb.Generate<int>()
where @int >= start && @int < end
select @int;
static Gen<DateOnly> DateOnlyGen =>
from dateTime in Arb.Generate<DateTime>()
select DateOnly.FromDateTime(dateTime);
static Gen<TimeOnly> TimeOnlyGen =>
Gen.OneOf(TimeOnlyEdgeCaseGen, TimeOnlyRandomGen);
static Gen<TimeOnly> TimeOnlyEdgeCaseGen =>
Gen.OneOf(
Gen.Constant(TimeOnly.MinValue),
Gen.Constant(TimeOnly.MaxValue));
static Gen<TimeOnly> TimeOnlyRandomGen =>
from hour in MakeIntInRangeGen(0, 24)
from minute in MakeIntInRangeGen(0, 60)
from second in MakeIntInRangeGen(0, 60)
from millisecond in MakeIntInRangeGen(0, 1000)
select new TimeOnly(hour, minute, second, millisecond);
public class BeginningOfDay
{
[Property]
public Property ShouldReturnTheEarliestTimeInTheDay()
{
var argsGen =
from time in TimeOnlyGen
from date in DateOnlyGen
select new { date, time };
return Prop.ForAll(
argsGen.ToArbitrary(),
args => args.date.BeginningOfDay() <= args.date.ToDateTime(args.time));
}
}
public class EndOfDay
{
[Property]
public Property ShouldReturnTheLatestTimeInTheDay()
{
var argsGen =
from time in TimeOnlyGen
from date in DateOnlyGen
select new { date, time };
return Prop.ForAll(
argsGen.ToArbitrary(),
args => args.date.EndOfDay() >= args.date.ToDateTime(args.time));
}
}
}
}
感谢@MarkSeemann的回答。为了完成问题,我将在这里记录答复。
由于 FsCheck 生成测试数据的方式,无法保证提供特定数据来验证属性。在这种情况下,完成我正在寻找的最佳方法是进行两个测试,一个用于使用 FsCheck 验证属性,另一个用于使用特定边缘情况的基本 xUnit 验证属性。