Files
QWERTYkez.Mensura/QWERTYkez.Mensura.Tests/AggregateUnitExtensionsTest.cs
2026-06-12 23:34:00 +07:00

170 lines
7.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Reflection;
namespace QWERTYkez.Mensura.Tests;
public class AggregateUnitExtensionsTest
{
// Вспомогательный метод для создания объекта 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
}