#c_sharp #юнит_тесты #атрибуты #nunit
Есть несколько однотипных юнит-тестов nUnit, в которых проверяется парсинг строки
в decimal (именно этот тип рекомендуется в c# для денежных единиц):
[Test]
public void Test3()
{
// Arrange
var input = "1,234.56";
// Act
var result = MoneyParser.Parse(input);
// Assert
Assert.AreEqual(1234.56m, result);
}
Ну и я соответственно захотел всё это элегантно упаковать в один метод навесив атрибут
TestCase:
[TestCase("1234", 1234m)]
[TestCase("1234.56", 1234.56m)]
[TestCase("1234,56", 1234.56m)]
[TestCase("1 234.56", 1234.56m)]
[TestCase("1 234,56", 1234.56m)]
[TestCase("1,234.56", 1234.56m)]
public void Test(string input, decimal expected)
{
var result = MoneyParser.Parse(input);
Assert.AreEqual(expected, result);
}
Однако в атрибутах можно использовать только примитивные типы, а код с decimal даже
не компилируется — выдаёт ошибку:
Error CS0182 An attribute argument must be a constant expression,
typeof expression or array creation expression of an attribute
parameter type
Как можно обойти это препятствие и написать читаемый тест? На ум приходит только
вариант задавать на входе expected строкой, преобразовывать внутри теста - и это не
нравится ни тем, что легко поломать текст невалидными данными (нет типобезопасностм),
ни тем, что придётся загромождать код лишними преобразованиями.
Ответы
Ответ 1
Поскольку в C# тип double может неявно преобразовываться в тип decimal, можно просто
убрать постфикс m для всех decimal-значений в атрибуте TestCase. Потерь по точности
в данном случае не будет (это можно легко проверить, если нужно).
В конечном итоге тест будет выглядеть примерно так:
[TestFixture]
public class MoneyParserTests
{
[TestCase("1234", 1234)]
[TestCase("1234.56", 1234.56)]
[TestCase("1234,56", 1234.56)]
[TestCase("1 234.56", 1234.56)]
[TestCase("1 234,56", 1234.56)]
[TestCase("1,234.56", 1234.56)]
public void Test(string input, decimal expected)
{
var result = MoneyParser.Parse(input);
Assert.AreEqual(expected, result);
}
}
Единственный минус: глядя на эти тесткейсы не скажешь, что метод принимает именно
decimal параметры.
В других случаях Unit-тест будет обрастать лишними проверками, явными преобразованиями
и т.д.
Ответ 2
Можно попробовать передать тесты не через атрибуты, с помощью TestCaseSource. Получится
страшненько, но сработает:
private static readonly Tuple[] cases = new[]
{
Tuple.Create("1234", 1234m),
Tuple.Create("1234.56", 1234.56m),
Tuple.Create("1234,56", 1234.56m),
Tuple.Create("1 234.56", 1234.56m),
Tuple.Create("1 234,56", 1234.56m),
Tuple.Create("1,234.56", 1234.56m)
};
private static object[] TestCases()
{
return cases.Select(t => new object[] { t.Item1, t.Item2 }).ToArray();
}
[TestCaseSource(nameof(TestCases))]
public void Test(string input, decimal expected)
{
var result = MoneyParser.Parse(input);
Assert.AreEqual(expected, result);
}
Преимущество: до определенной степени сохранена типобезопасность при изменении тестов.
Недостатки:
читаемость упала;
из-за сигнатуры object[] сломать код достаточно легко;
появился код для преобразования данных.