我想通过单元测试来测试一个类,以确保它在各种情况下都能正常工作。无论类在何处(物理机、虚拟机、容器……)运行,该类都应该生成唯一的结果。如果在同一环境中并行运行多次,它甚至应该生成独特的结果。
问题是。该类是静态的,并且使用 DateTime.Now 调用。如果我将使类成为非静态并使用 TimeProvider 结构,那么一切都会很好并且可测试,但是......在开始之前,这里有一个问题
namespace HighPerformance.Utils;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Security.Cryptography;
/// <summary>
/// <see cref="SequentialGuid" /> generates instances of <see cref="Guid" /> that are in ascending order.
/// </summary>
/// <remarks>
/// It is both unique and ordered.
/// </remarks>
public static class SequentialGuid
{
private const long Rfc4122TicksBase = 499163040000000000; //1582-10-15 00:00:00
private const long SequentialGuidVersion = 0x6000;
private const int Variant = 0xC000;
private const int VariantMask = 0xC000;
private const long VersionMask = ~0x0FFFL;
private static readonly MapperAb EmptyMapperAb = new();
private static readonly MapperC EmptyMapperC;
private static readonly int Randomizer;
private static long _lastTicks;
private static int _sequence;
private static SpinLock _spinLock;
static SequentialGuid()
{
_spinLock = new SpinLock(false);
Randomizer = RandomNumberGenerator.GetInt32(int.MaxValue);
int rnd = RandomNumberGenerator.GetInt32(int.MaxValue);
rnd ^= Environment.ProcessId;
EmptyMapperC.UniqueId = (ushort)((rnd & 0xFFFF) ^ (rnd >> 16));
}
public static Guid NewGuid()
{
var mapperAb = EmptyMapperAb;
var mapperC = EmptyMapperC;
mapperAb.Ticks = GetTicksAndSequence(out mapperC.Sequence);
long ht = mapperAb.Ticks & ~VersionMask;
mapperAb.Ticks &= VersionMask;
mapperAb.Ticks <<= 4;
mapperAb.Ticks |= SequentialGuidVersion | ht;
var d = (uint)(Randomizer ^ Environment.CurrentManagedThreadId);
Vector128<byte> vec = Vector128.Create(mapperAb.A, mapperAb.B, mapperC.C, d).AsByte();
if (BitConverter.IsLittleEndian)
{
Vector128<byte> result = Vector128.Shuffle(vec, Vector128.Create((byte)0, 1, 2, 3, 6, 7, 4, 5, 11, 10, 9, 8, 15, 14, 13, 12));
return Unsafe.As<Vector128<byte>, Guid>(ref result);
}
return Unsafe.As<Vector128<byte>, Guid>(ref vec);
}
private static long GetTicksAndSequence(out ushort sequence)
{
// In the year 5236-03-31 21:21:00 I have to think about a different solution.
long ticks = DateTime.UtcNow.Ticks - Rfc4122TicksBase;
var lockTaken = false;
_spinLock.Enter(ref lockTaken);
if (ticks > _lastTicks)
{
_sequence = 0;
_lastTicks = ticks;
}
else
{
if (_sequence == (VariantMask ^ 0xFFFF)) // rollover will happen, so we increase ticks
{
_sequence = 0;
++_lastTicks;
}
ticks = _lastTicks;
}
sequence = (ushort)(_sequence++ | Variant);
if (lockTaken) { _spinLock.Exit(); }
return ticks;
}
[StructLayout(LayoutKind.Explicit)]
private struct MapperAb(long ticks)
{
[FieldOffset(0)] public long Ticks = ticks;
[FieldOffset(0)] public uint B;
[FieldOffset(sizeof(uint))] public uint A;
}
[StructLayout(LayoutKind.Explicit)]
private struct MapperC
{
[FieldOffset(0)] public uint C;
// ReSharper disable once MemberHidesStaticFromOuterClass
[FieldOffset(0)] public ushort UniqueId;
[FieldOffset(sizeof(ushort))] public ushort Sequence;
}
}
也许只是您在测试中设置的静态字段?
internal static Func<DateTime> GetTime { get; set; } = () => DateTime.Now;
...
long ticks = GetTime().Ticks - Rfc4122TicksBase;
测试
[TestFixture]
[NonParallelizable]
public class MyClassTests
{
[TearDown]
public void TearDown()
{
SequentialGuid.GetTime = () => DateTime.Now;
}
[Test]
[NonParallelizable]
public void Test1()
{
int i = 1;
SequentialGuid.GetTime = () => new DateTime(2005, 1, 1).AddTicks(i++);
}
}