Я хочу протестировать класс с помощью модульных тестов, чтобы убедиться, что он будет работать правильно в любых обстоятельствах. Класс должен генерировать уникальные результаты независимо от того, где (физическая машина, виртуальная машина, контейнер и т. д.) выполняется класс. Он даже должен генерировать уникальные результаты, если его запускать несколько раз параллельно в одной и той же среде.
Проблема в следующем. Класс является статическим и использует вызов DateTime.Now. Если бы я сделал класс нестатическим и использовал структуру TimeProvider, все было бы хорошо и тестируемо, но... прежде чем начать, вот подвох
Класс будет вызываться много тысяч раз в секунду. Производственная машина на данный момент является рекордсменом: 151964235 вызовов в секунду за 2 минуты. Статика была выбрана, чтобы не нагружать сборщик мусора.
Каждое предлагаемое решение должно быть по крайней мере таким же быстрым, как и текущая реализация. Сюда входит фактическое время выполнения решения, время создания объекта и нагрузка на GC.
Решения должны быть переносимыми и способными работать как на Windows, Linux, так и на Mac.
Решения должны быть переносимыми и способными работать как на Windows, Linux, так и на Mac.
li>
Другие идеи, как сделать это еще быстрее, конечно, очень приветствуются.
Основное беспокойство вызывает DateTime.Now. О UniqueId и Environment.CurrentManagedThreadId я подумаю позже.
namespace My.Utils;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Security.Cryptography;
using System.Threading;
///
/// generates instances of that are in ascending order.
///
///
/// It is both unique and ordered.
///
public static class SequentialGuid
{
private const uint GuidVersion8 = 0x8000;
private const uint GuidVersion7 = 0x7000;
private const uint Variant = 0x80000000;
private const uint VariantMask = 0xC0000000;
private const uint SequenceRollover = 0x1000;
private static readonly MapperAb EmptyMapperAb = new();
private static readonly MapperCd BaseMapperCd;
private static readonly uint C;
private static long _lastMilliseconds;
private static uint _sequence;
private static SpinLock _spinLock;
static SequentialGuid()
{
_spinLock = new SpinLock(false);
int rnd = RandomNumberGenerator.GetInt32(int.MaxValue);
rnd ^= Environment.MachineName.GetHashCode();
BaseMapperCd.C = (uint)rnd;
BaseMapperCd.C &= ~VariantMask;
BaseMapperCd.C |= Variant;
BaseMapperCd.D = (uint)(Environment.ProcessId ^ rnd);
C = (uint)rnd;
C &= ~VariantMask;
C |= Variant;
}
public static Guid CreateVersion7()
{
return CreateVersion7(DateTimeOffset.UtcNow);
}
public static Guid CreateVersion7(in DateTimeOffset timestamp)
{
var mapperAb = EmptyMapperAb;
GetTicksAndSequence(timestamp, ref mapperAb);
mapperAb.B |= GuidVersion7;
var d = (uint)RandomNumberGenerator.GetInt32(int.MaxValue);
Vector128 vec = Vector128.Create(mapperAb.A, mapperAb.B, C, d).AsByte();
if (BitConverter.IsLittleEndian)
{
Vector128 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(ref result);
}
return Unsafe.As(ref vec);
}
public static Guid CreateVersion8()
{
return CreateVersion8(DateTimeOffset.UtcNow);
}
public static Guid CreateVersion8(in DateTimeOffset timestamp)
{
var mapperAb = EmptyMapperAb;
GetTicksAndSequence(timestamp, ref mapperAb);
mapperAb.B |= GuidVersion8;
Vector128 vec = Vector128.Create(mapperAb.A, mapperAb.B, BaseMapperCd.C, BaseMapperCd.D).AsByte();
if (BitConverter.IsLittleEndian)
{
Vector128 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(ref result);
}
return Unsafe.As(ref vec);
}
private static void GetTicksAndSequence(in DateTimeOffset timestamp, ref MapperAb mapperAb)
{
mapperAb.Ticks = timestamp.ToUnixTimeMilliseconds();
ArgumentOutOfRangeException.ThrowIfNegative(mapperAb.Ticks, nameof(timestamp));
var lockTaken = false;
_spinLock.Enter(ref lockTaken);
if (mapperAb.Ticks > _lastMilliseconds)
{
_sequence = 0;
_lastMilliseconds = mapperAb.Ticks;
}
else
{
if (_sequence == SequenceRollover) // rollover will happen, so we increase ticks
{
_sequence = 0;
++_lastMilliseconds;
}
mapperAb.Ticks = _lastMilliseconds;
}
uint b = _sequence++;
if (lockTaken) { _spinLock.Exit(); }
mapperAb.Ticks Guid.CreateVersion7(); // UUIDv7
[Benchmark]
public Guid BenchmarkUuidV7Custom() => SequentialGuid.CreateVersion7(); // UUIDv7
[Benchmark]
public Guid BenchmarkUuidV8Custom() => SequentialGuid.CreateVersion8(); // UUIDv7
}
Обновление: я изучил UUID v7 в .net9, упомянутый @Fildor. Однако UUID v7 делает почти то же самое, что и я, но значительно медленнее, чем моя реализация.
Обновление: я взглянул на реализацию EF Core 8, упомянутую @PanagiotisKanavos. В этой реализации используется не компонент реального времени, а время инициализации объекта в качестве основы для всех других созданных идентификаторов. Это может быть проблематично в сценарии, когда процессы, использующие этот класс, будут остановлены и запущены снова. Также нет ничего другого, что могло бы гарантировать уникальность.
Обновление: я обновил код своей окончательной реализацией. Эта версия доступна для тестирования в модульных тестах и соответствует реализации Guid.CreateVersion7. Я также создаю CreateVersion8, который может быть предсказуемым, но при этом работает в два раза быстрее.
Я хочу протестировать класс с помощью модульных тестов, чтобы убедиться, что он будет работать правильно в любых обстоятельствах. Класс должен генерировать уникальные результаты независимо от того, где (физическая машина, виртуальная машина, контейнер и т. д.) выполняется класс. Он даже должен генерировать уникальные результаты, если его запускать несколько раз параллельно в одной и той же среде. Проблема в следующем. Класс является статическим и использует вызов DateTime.Now. Если бы я сделал класс нестатическим и использовал структуру TimeProvider, все было бы хорошо и тестируемо, но... прежде чем начать, вот подвох [list] [*]Класс будет вызываться много тысяч раз в секунду. Производственная машина на данный момент является рекордсменом: 151964235 вызовов в секунду за 2 минуты. Статика была выбрана, чтобы не нагружать сборщик мусора. [*]Каждое предлагаемое решение должно быть по крайней мере таким же быстрым, как и текущая реализация. Сюда входит фактическое время выполнения решения, время создания объекта и нагрузка на GC. [*]Решения должны быть переносимыми и способными работать как на Windows, Linux, так и на Mac. [*]Решения должны быть переносимыми и способными работать как на Windows, Linux, так и на Mac. [*] li> Другие идеи, как сделать это еще быстрее, конечно, очень приветствуются. [*]Основное беспокойство вызывает DateTime.Now. О UniqueId и Environment.CurrentManagedThreadId я подумаю позже. [/list] [code]namespace My.Utils;
using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Security.Cryptography; using System.Threading;
/// /// generates instances of that are in ascending order. /// /// /// It is both unique and ordered. /// public static class SequentialGuid { private const uint GuidVersion8 = 0x8000; private const uint GuidVersion7 = 0x7000; private const uint Variant = 0x80000000; private const uint VariantMask = 0xC0000000; private const uint SequenceRollover = 0x1000; private static readonly MapperAb EmptyMapperAb = new(); private static readonly MapperCd BaseMapperCd; private static readonly uint C; private static long _lastMilliseconds; private static uint _sequence; private static SpinLock _spinLock;
static SequentialGuid() { _spinLock = new SpinLock(false); int rnd = RandomNumberGenerator.GetInt32(int.MaxValue); rnd ^= Environment.MachineName.GetHashCode(); BaseMapperCd.C = (uint)rnd; BaseMapperCd.C &= ~VariantMask; BaseMapperCd.C |= Variant; BaseMapperCd.D = (uint)(Environment.ProcessId ^ rnd); C = (uint)rnd; C &= ~VariantMask; C |= Variant; }
public static Guid CreateVersion7() { return CreateVersion7(DateTimeOffset.UtcNow); }
public static Guid CreateVersion7(in DateTimeOffset timestamp) { var mapperAb = EmptyMapperAb; GetTicksAndSequence(timestamp, ref mapperAb); mapperAb.B |= GuidVersion7;
var d = (uint)RandomNumberGenerator.GetInt32(int.MaxValue); Vector128 vec = Vector128.Create(mapperAb.A, mapperAb.B, C, d).AsByte(); if (BitConverter.IsLittleEndian) { Vector128 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(ref result); }
return Unsafe.As(ref vec); }
public static Guid CreateVersion8() { return CreateVersion8(DateTimeOffset.UtcNow); }
public static Guid CreateVersion8(in DateTimeOffset timestamp) { var mapperAb = EmptyMapperAb; GetTicksAndSequence(timestamp, ref mapperAb); mapperAb.B |= GuidVersion8;
var lockTaken = false; _spinLock.Enter(ref lockTaken); if (mapperAb.Ticks > _lastMilliseconds) { _sequence = 0; _lastMilliseconds = mapperAb.Ticks; } else { if (_sequence == SequenceRollover) // rollover will happen, so we increase ticks { _sequence = 0; ++_lastMilliseconds; }
mapperAb.Ticks = _lastMilliseconds; }
uint b = _sequence++; if (lockTaken) { _spinLock.Exit(); }
mapperAb.Ticks Guid.CreateVersion7(); // UUIDv7
[Benchmark] public Guid BenchmarkUuidV7Custom() => SequentialGuid.CreateVersion7(); // UUIDv7
[Benchmark] public Guid BenchmarkUuidV8Custom() => SequentialGuid.CreateVersion8(); // UUIDv7 } [/code] Обновление: я изучил UUID v7 в .net9, упомянутый @Fildor. Однако UUID v7 делает почти то же самое, что и я, но значительно медленнее, чем моя реализация. Обновление: я взглянул на реализацию EF Core 8, упомянутую @PanagiotisKanavos. В этой реализации используется не компонент реального времени, а время инициализации объекта в качестве основы для всех других созданных идентификаторов. Это может быть проблематично в сценарии, когда процессы, использующие этот класс, будут остановлены и запущены снова. Также нет ничего другого, что могло бы гарантировать уникальность. Обновление: я обновил код своей окончательной реализацией. Эта версия доступна для тестирования в модульных тестах и соответствует реализации Guid.CreateVersion7. Я также создаю CreateVersion8, который может быть предсказуемым, но при этом работает в два раза быстрее.
Я хочу протестировать класс с помощью модульных тестов, чтобы убедиться, что он будет работать правильно в любых обстоятельствах. Класс должен генерировать уникальные результаты независимо от того, где (физическая машина, виртуальная машина,...
У меня есть таблица, содержащая уникальный идентификатор, который по умолчанию имеет значение 00000000-0000-0000-0000-000000000000 (Guid.Empty) через значение по умолчанию в SQL Server, когда значение не указано.
С помощью PetaPoco я создаю GUID и...
У меня есть таблица, содержащая уникальный идентификатор, который по умолчанию имеет значение 00000000-0000-0000-0000-000000000000 (Guid.Empty) через значение по умолчанию в SQL Server, когда значение не указано.
С помощью PetaPoco я создаю GUID и...
ниже приводится оригинальный текст книги 《Искусство модульного тестирования, второе издание》: Извлечение интерфейса, позволяющего заменить базовую реализацию
В этом методе вам нужно расшифровать код, который касается файловую систему в отдельный...
Предположим, у меня есть интерфейс и класс реализации, который его реализует, и я хочу написать для этого модульный тест. Что мне следует протестировать: интерфейс или Impl?