tests
This commit is contained in:
170
QWERTYkez.Mensura.Tests/AggregateUnitExtensions.cs
Normal file
170
QWERTYkez.Mensura.Tests/AggregateUnitExtensions.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace QWERTYkez.Mensura.Tests;
|
||||
|
||||
public class AggregateUnitExtensions
|
||||
{
|
||||
// Вспомогательный метод для создания объекта Length.
|
||||
// Если у вас используется фабричный метод (например, Length.FromMeters), замените код внутри.
|
||||
private static Length CreateLength(double value) => value * Length._MilliMeter;
|
||||
|
||||
#region Инфраструктура Рефлексии (Invoker)
|
||||
|
||||
private delegate Length SpanDelegate(ReadOnlySpan<Length> units);
|
||||
private delegate Length SpanNullableDelegate(ReadOnlySpan<Length?> units);
|
||||
|
||||
private static class Invoker
|
||||
{
|
||||
private static readonly Type ExtType;
|
||||
|
||||
static Invoker()
|
||||
{
|
||||
// Находим внутренний класс AggregateUnitExtensions в целевой сборке
|
||||
ExtType = typeof(Length).Assembly.GetType("QWERTYkez.Mensura.Extensions.AggregateUnitExtensions")
|
||||
?? throw new InvalidOperationException("Не удалось найти класс AggregateUnitExtensions через рефлексию.");
|
||||
}
|
||||
|
||||
public static Length InvokeSpan(string methodName, ReadOnlySpan<Length> data)
|
||||
{
|
||||
var method = FindMethod(methodName, typeof(ReadOnlySpan<>), isNullable: false);
|
||||
var closedMethod = method.MakeGenericMethod(typeof(Length));
|
||||
var del = closedMethod.CreateDelegate<SpanDelegate>();
|
||||
return del(data);
|
||||
}
|
||||
|
||||
public static Length InvokeSpanNullable(string methodName, ReadOnlySpan<Length?> data)
|
||||
{
|
||||
var method = FindMethod(methodName, typeof(ReadOnlySpan<>), isNullable: true);
|
||||
var closedMethod = method.MakeGenericMethod(typeof(Length));
|
||||
var del = closedMethod.CreateDelegate<SpanNullableDelegate>();
|
||||
return del(data);
|
||||
}
|
||||
|
||||
public static Length InvokeCollection(string methodName, Type genericContainer, object data, bool isNullable)
|
||||
{
|
||||
var method = FindMethod(methodName, genericContainer, isNullable);
|
||||
var closedMethod = method.MakeGenericMethod(typeof(Length));
|
||||
return (Length)closedMethod.Invoke(null, [data])!;
|
||||
}
|
||||
|
||||
private static MethodInfo FindMethod(string name, Type genericContainerType, bool isNullable)
|
||||
{
|
||||
var methods = ExtType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
|
||||
.Where(m => m.Name == name);
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
if (parameters.Length != 1) continue;
|
||||
|
||||
var paramType = parameters[0].ParameterType;
|
||||
if (!paramType.IsGenericType) continue;
|
||||
|
||||
// Проверяем базовый контейнер (List<>, IEnumerable<>, ReadOnlySpan<>)
|
||||
if (paramType.GetGenericTypeDefinition() != genericContainerType) continue;
|
||||
|
||||
// Проверяем внутренний аргумент типа на Nullable
|
||||
var genericArgument = paramType.GetGenericArguments()[0];
|
||||
bool currentIsNullable = genericArgument.IsGenericType &&
|
||||
genericArgument.GetGenericTypeDefinition() == typeof(Nullable<>);
|
||||
|
||||
if (currentIsNullable == isNullable)
|
||||
{
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
throw new MethodAccessException($"Метод {name} для контейнера {genericContainerType.Name} (Nullable: {isNullable}) не найден.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Тесты для НЕ-выделяющих Nullable типов (Обычные структуры)
|
||||
|
||||
private readonly Length[] _standardData = [CreateLength(10), CreateLength(20), CreateLength(30)];
|
||||
private readonly Length _expectedSum = CreateLength(60);
|
||||
private readonly Length _expectedAvg = CreateLength(20);
|
||||
private readonly Length _expectedMax = CreateLength(30);
|
||||
private readonly Length _expectedMin = CreateLength(10);
|
||||
|
||||
[Theory]
|
||||
[InlineData("Sum")]
|
||||
[InlineData("Avg")]
|
||||
[InlineData("Max")]
|
||||
[InlineData("Min")]
|
||||
public void StandardContainers_ShouldCalculateCorrectly(string operation)
|
||||
{
|
||||
// Набор ожидаемых значений
|
||||
Length expected = operation switch
|
||||
{
|
||||
"Sum" => _expectedSum,
|
||||
"Avg" => _expectedAvg,
|
||||
"Max" => _expectedMax,
|
||||
"Min" => _expectedMin,
|
||||
_ => throw new ArgumentException(operation)
|
||||
};
|
||||
|
||||
// 1. Тест ReadOnlySpan<T>
|
||||
ReadOnlySpan<Length> span = _standardData;
|
||||
Assert.Equal(expected, Invoker.InvokeSpan(operation, span));
|
||||
|
||||
// 2. Тест List<T>
|
||||
var list = _standardData.ToList();
|
||||
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(List<>), list, isNullable: false));
|
||||
|
||||
// 3. Тест IReadOnlyCollection<T>
|
||||
IReadOnlyCollection<Length> readOnlyCollection = _standardData;
|
||||
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(IReadOnlyCollection<>), readOnlyCollection, isNullable: false));
|
||||
|
||||
// 4. Тест IEnumerable<T>
|
||||
IEnumerable<Length> enumerable = _standardData.Select(x => x);
|
||||
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(IEnumerable<>), enumerable, isNullable: false));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Тесты для Nullable типов (T?)
|
||||
|
||||
// Тестируем смесь значений с null-элементами
|
||||
private readonly Length?[] _nullableData = [CreateLength(10), null, CreateLength(30)];
|
||||
private readonly Length _expectedNullSum = CreateLength(40);
|
||||
private readonly Length _expectedNullAvg = CreateLength(20); // 40 / 2 значения
|
||||
private readonly Length _expectedNullMax = CreateLength(30);
|
||||
private readonly Length _expectedNullMin = CreateLength(10);
|
||||
|
||||
[Theory]
|
||||
[InlineData("Sum")]
|
||||
[InlineData("Avg")]
|
||||
[InlineData("Max")]
|
||||
[InlineData("Min")]
|
||||
public void NullableContainers_ShouldCalculateCorrectly(string operation)
|
||||
{
|
||||
Length expected = operation switch
|
||||
{
|
||||
"Sum" => _expectedNullSum,
|
||||
"Avg" => _expectedNullAvg,
|
||||
"Max" => _expectedNullMax,
|
||||
"Min" => _expectedNullMin,
|
||||
_ => throw new ArgumentException(operation)
|
||||
};
|
||||
|
||||
// 1. Тест ReadOnlySpan<T?>
|
||||
ReadOnlySpan<Length?> span = _nullableData;
|
||||
Assert.Equal(expected, Invoker.InvokeSpanNullable(operation, span));
|
||||
|
||||
// 2. Тест List<T?>
|
||||
var list = _nullableData.ToList();
|
||||
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(List<>), list, isNullable: true));
|
||||
|
||||
// 3. Тест IReadOnlyCollection<T?>
|
||||
IReadOnlyCollection<Length?> readOnlyCollection = _nullableData;
|
||||
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(IReadOnlyCollection<>), readOnlyCollection, isNullable: true));
|
||||
|
||||
// 4. Тест IEnumerable<T?>
|
||||
IEnumerable<Length?> enumerable = _nullableData.Select(x => x);
|
||||
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(IEnumerable<>), enumerable, isNullable: true));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user