Страницы

Поиск по вопросам

вторник, 10 декабря 2019 г.

Деньги в юнит-тестах: использование decimal в аттрибутах

#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[] сломать код достаточно легко; появился код для преобразования данных.

Комментариев нет:

Отправить комментарий