如何让这个静态类服从TDD而不使其变慢

问题描述 投票:0回答:1

我想通过单元测试来测试一个类,以确保它在各种情况下都能正常工作。无论类在何处(物理机、虚拟机、容器……)运行,该类都应该生成唯一的结果。如果在同一环境中并行运行多次,它甚至应该生成独特的结果。

问题是。该类是静态的,并且使用 DateTime.Now 调用。如果我将使类成为非静态并使用 TimeProvider 结构,那么一切都会很好并且可测试,但是......在开始之前,这里有一个问题

  1. 该类每秒将被调用 1000 次。目前,生产机器保持着 2 分钟内每秒 151964235 次调用的记录。选择静态是为了不对 GC 造成压力。
  2. 提供的每个解决方案都需要至少与当前实施一样快。这包括对象创建的求解时间的实际运行时间和 GC 上的压力。
  3. 解决方案需要可移植并且能够在 Windows、Linux 和 Mac 上运行。
  4. 当然非常欢迎其他使其更快的想法。
  5. 主要关注的是 DateTime.Now。稍后我会考虑UniqueId和Environment.CurrentManagedThreadId。
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;
    }
}
c# unit-testing datetime
1个回答
0
投票

也许只是您在测试中设置的静态字段?

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++);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.