Conversation
| .SetName("IsValidNumber_ReturnsFalse_WhenSymbolWithNumberLengthGreaterThanPrecision"); | ||
| yield return new TestCaseData(3, 2, true, "00.00").Returns(false) | ||
| .SetName("IsValidNumber_ReturnsFalse_WhenIntPartWithFracPartGreaterThanPrecision"); | ||
| yield return new TestCaseData(17, 2, true, "0").Returns(true) |
There was a problem hiding this comment.
Было бы неплохо разделить пустой строкой кейсы с false и true
| yield return new TestCaseData(4, 2, false, "-1.23").Returns(true) | ||
| .SetName("IsValidNumber_ReturnsTrue_WhenNegativeSymbolWithNumberLengthNotGreaterThanPrecision"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Было бы неплохо разделить пустой строкой источники данных
| throw new ArgumentException("precision must be a non-negative number less or equal than precision"); | ||
| numberRegex = new Regex(@"^([+-]?)(\d+)([.,](\d+))?$", RegexOptions.IgnoreCase); | ||
| } | ||
| [Test, TestCaseSource(nameof(IsValidNumberPositivityTests)), |
There was a problem hiding this comment.
Атрибут Test необязателен, если имеются TestCaseSource. Лучше несколько TestCaseSource указывать раздельно, не через запятую - оно так смотрится банально симпатичнее.
| TestCaseSource(nameof(IsValidNumberSymbolsTests))] | ||
| public bool IsValidNumber_Returns(int precision, int scale, bool onlyPositive, string number) | ||
| { | ||
| return new NumberValidator(precision, scale, onlyPositive).IsValidNumber(number); |
| [Test, TestCaseSource(nameof(ConstructorArgumentExceptions))] | ||
| public void Constructor_ThrowsArgumentException(int precision, int scale, bool onlyPositive) | ||
| { | ||
| Invoking(() => new NumberValidator(precision, scale, onlyPositive)).Should().Throw<ArgumentException>(); |
There was a problem hiding this comment.
Применение using static только ради вызова Invoking необоснованно.
| [TestFixture] | ||
| public class NumberValidatorTests | ||
| { | ||
| public static IEnumerable IsValidNumberPrecisionTests |
| { | ||
| get | ||
| { | ||
| yield return new TestCaseData(3, 2, true, "+0.00").Returns(false) |
There was a problem hiding this comment.
Зачем здесь yield return? Можно обойтись без него, выйдет лаконичнее.
| get | ||
| { | ||
| yield return new TestCaseData(3, 2, true, "+0.00").Returns(false) | ||
| .SetName("IsValidNumber_ReturnsFalse_WhenSymbolWithNumberLengthGreaterThanPrecision"); |
There was a problem hiding this comment.
Нет нужды и в названии теста, и в названии сценарии теста дублировать информацию.
Тест у тебя называется IsValidNumber_Returns. В дереве тестов у тебя в итоге будет что-то навроде
IsValidNumber_Returns
IsValidNumber_ReturnsFalse_WhenSymbolWithNumberLengthGreaterThanPrecision
IsValidNumber_ReturnsFalse_WhenIntPartWithFracPartGreaterThanPrecision
Можно и нужно упростить, оставив только нужную для понимания теста информацию.
| Assert.AreEqual(actualTsar.Age, expectedTsar.Age); | ||
| Assert.AreEqual(actualTsar.Height, expectedTsar.Height); | ||
| Assert.AreEqual(actualTsar.Weight, expectedTsar.Weight); | ||
| // сравнение родителей такое же как и в коде, который нужно было отрефакторить - сравнивает по ссылке, |
| { | ||
| private static IEnumerable<TestCaseData> IsValidNumberPrecisionTests | ||
| { | ||
| get |
There was a problem hiding this comment.
Не совсем то, что я имел в виду.
Про разделение false / true - кейсы с false друг за дружкой без разрывов, {пустая строка}, затем кейсы с true так же без разрывов.
new TestCaseData(1).Returns(false).SetName("a"),
new TestCaseData(2).Returns(false).SetName("b"),
// пустая строка
new TestCaseData(3).Returns(true).SetName("c"),
new TestCaseData(4).Returns(true).SetName("d"),
Каждый новый кейс начинать отделять пустой строкой имеет смысл только, если кейсы сами по себе большие, но это не тот случай.
Про разделение источников данных пустой строкой - это что после объявления IsValidNumberPrecisionTests нужно сделать пустую строку. Затем объявлять следующий источник данных.
Так что придется ещё немного поправить)
| { | ||
| get | ||
| { | ||
| return new List<TestCaseData>() |
There was a problem hiding this comment.
В List здесь нет смысла, т.к. ты не работаешь с этой структурой. Можно было бы даже не типизировать IEnumerable. get здесь тоже сам по себе не нужен.
Избавься от геттера, заюзай массив и не забудь для него использовать инициализатор, тогда будет по красоте. Типизацию IEnumerable оставь на свой вкус, я бы типизировал, но и без этого всё ок будет.
|
|
||
| [TestCaseSource(nameof(ConstructorArgumentExceptions))] | ||
| public void Constructor_ThrowsArgumentException(int precision, int scale, bool onlyPositive) => | ||
| FluentActions.Invoking(() => new NumberValidator(precision, scale, onlyPositive)) |
There was a problem hiding this comment.
Почти. Предлагаю вообще избавиться от FluentActions. С Assert.Throws<> выйдет лаконичнее.
| .SetName("True_WhenNegativeSymbolWithNumberLengthNotGreaterThanPrecision") | ||
| }; | ||
|
|
||
| private static IEnumerable<TestCaseData> IsValidNumberScaleTests = new TestCaseData[] |
There was a problem hiding this comment.
Раз IEnumerable<> типизирован, то после new нет нужды ещё раз писать тип - можно его убрать. Компилятор сам разберется.
| new NumberValidator(precision, scale, onlyPositive).IsValidNumber(number); | ||
|
|
||
|
|
||
| [TestCaseSource(nameof(ConstructorArgumentExceptions))] |
There was a problem hiding this comment.
Найс. После рефакторинга видно, что источник данных для теста с конструктором слишком далеко от самого теста.
Давай протащим ConstructorArgumentExceptions ближе к тесту, который использует этот источник данных.
Да и в целом история хорошая, когда данные лежат как можно ближе к месту использования.
| private static IEnumerable<TestCaseData> IsValidNumberSymbolsTests = new [] | ||
| { | ||
| new TestCaseData(3, 2, true, "a.sd").Returns(false).SetName("False_WhenGivenNotDigits"), | ||
| new TestCaseData(17, 2, true, "").Returns(false).SetName("False_WhenEmptyStringGiven") |
There was a problem hiding this comment.
Давай добавим чуть больше данных для проверок - null, спец.символы - /r , /n, просто символы %$# и т.д. Число с каким-то символом? Символ с каким-то числом? Строка из пробелов?
Что будет, если разделитель не точка, а запятая? А если разделителей несколько? А если в строке только разделитель?
Как валидатор ведет себя с очень большими числами? С очень большой точностью? А все вместе? Умеет ли он понимать числа в экспоненциальном формате?
А может, валидатор считает верным число в другой системе счисления? Иррациональные числа?
Возвращает ли валидатор на один и тот же входной параметр один и тот же результат? Для этого можно использовать атрибут [Repeat()].
А может, валидатор как-то меняет свой стейт? Что будет если подать сначала одно число, затем второе, а затем снова первое? Кстати, для того, чтоб обозначить, что метод ничего не изменяет (что-то внутри себя / входные параметры), то его можно пометить атрибутом [Pure] из неймспейса System.Diagnostics.Contracts или JetBrains.Annotations.
Тесты как документация кода должны отвечать на все эти вопросы и даже чуть больше)
| .SetName("True_WhenGivenEndlessRationalNumber"), | ||
| new TestCaseData(1001, 1000, true, $"{Math.PI}").Returns(true) | ||
| .SetName("True_WhenGivenEndlessIrrationalNumber"), | ||
| new TestCaseData(2, 1, true, "01").Returns(true) |
There was a problem hiding this comment.
Этот же тест в IsValidNumberDiffrentFormsOfNumbersTests
| new TestCaseData(17, 2, true, " ").Returns(false).SetName("False_WhenStringOfSpacesGiven") | ||
| }; | ||
|
|
||
| [Pure] |
There was a problem hiding this comment.
[Pure] стоило прицепить на метод IsValidNumber, с тестами их юзать не стоит.
Атрибуты обычно стараются выравнивать по длине
Чтоб
Было вот так
В случае, когда были только источники данных, я пропускал этот момент, но [Repeat(5)] прям глаз режет)
|
|
||
| [Pure] | ||
| [TestCaseSource(nameof(ConstructorArgumentExceptions))] | ||
| public void Constructor_ThrowsArgumentException(int precision, int scale, bool onlyPositive) => |
There was a problem hiding this comment.
Возможно, стоит добавить тест, когда конструктор не кидает исключений.
| [Test] | ||
| public void ValidatorState_ShouldStayUnchanged() | ||
| { | ||
| var validator = new NumberValidator(10, 9, true); |
There was a problem hiding this comment.
Нет структуры ААА - Arrange, Act, Assert. Если тест небольшой, то обычно просто пустыми строками разделяют. Если большой (либо есть желание), то дополнительно прям пишут, где какая секция, используя комментарии.
Тут писать комменты смысла нет, но логически всё равно стоит разделить.
| .SetName("True_WhenFracPartNotGreaterThanScale") | ||
| }; | ||
|
|
||
| private static IEnumerable<TestCaseData> IsValidNumberBigNumbersTests = new[] |
There was a problem hiding this comment.
Было бы правильнее назвать не BigNumbers, а LongNumbers, т.к. 1/3 в виде десятичной дроби - число длинное, но не большое). В то время, как Int64.MaxValue и большое, и длинное.
|
|
||
| private static IEnumerable<TestCaseData> IsValidNumberDiffrentFormsOfNumbersTests = new[] | ||
| { | ||
| new TestCaseData(2, 1, true, "01").Returns(true) |
There was a problem hiding this comment.
В программировании относительно часто используется так же и шестнадцатеричная система, её тоже стоит добавить).
Двоичная как-то не очень выглядит. Глядя на тест я вижу, что валидатор считает число 01 верным. А если увеличить разрядность и написать 0001? Должно быть это зависит от передаваемых параметров?
@pakapik