Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Козлов Данил #228

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

SenyaPevko
Copy link

@SenyaPevko SenyaPevko commented Nov 26, 2023

Copy link

@pakapik pakapik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В целом всё круто)

.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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Было бы неплохо разделить пустой строкой кейсы с false и true

yield return new TestCaseData(4, 2, false, "-1.23").Returns(true)
.SetName("IsValidNumber_ReturnsTrue_WhenNegativeSymbolWithNumberLengthNotGreaterThanPrecision");
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Было бы неплохо разделить пустой строкой источники данных

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)),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Атрибут 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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно заменить на expression body

[Test, TestCaseSource(nameof(ConstructorArgumentExceptions))]
public void Constructor_ThrowsArgumentException(int precision, int scale, bool onlyPositive)
{
Invoking(() => new NumberValidator(precision, scale, onlyPositive)).Should().Throw<ArgumentException>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Применение using static только ради вызова Invoking необоснованно.

[TestFixture]
public class NumberValidatorTests
{
public static IEnumerable IsValidNumberPrecisionTests
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему public?

{
get
{
yield return new TestCaseData(3, 2, true, "+0.00").Returns(false)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем здесь yield return? Можно обойтись без него, выйдет лаконичнее.

get
{
yield return new TestCaseData(3, 2, true, "+0.00").Returns(false)
.SetName("IsValidNumber_ReturnsFalse_WhenSymbolWithNumberLengthGreaterThanPrecision");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет нужды и в названии теста, и в названии сценарии теста дублировать информацию.
Тест у тебя называется 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);
// сравнение родителей такое же как и в коде, который нужно было отрефакторить - сравнивает по ссылке,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Весьма любопытно🧐

{
private static IEnumerable<TestCaseData> IsValidNumberPrecisionTests
{
get
Copy link

@pakapik pakapik Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не совсем то, что я имел в виду.

Про разделение 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>()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В List здесь нет смысла, т.к. ты не работаешь с этой структурой. Можно было бы даже не типизировать IEnumerable. get здесь тоже сам по себе не нужен.

Избавься от геттера, заюзай массив и не забудь для него использовать инициализатор, тогда будет по красоте. Типизацию IEnumerable оставь на свой вкус, я бы типизировал, но и без этого всё ок будет.


[TestCaseSource(nameof(ConstructorArgumentExceptions))]
public void Constructor_ThrowsArgumentException(int precision, int scale, bool onlyPositive) =>
FluentActions.Invoking(() => new NumberValidator(precision, scale, onlyPositive))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почти. Предлагаю вообще избавиться от FluentActions. С Assert.Throws<> выйдет лаконичнее.

.SetName("True_WhenNegativeSymbolWithNumberLengthNotGreaterThanPrecision")
};

private static IEnumerable<TestCaseData> IsValidNumberScaleTests = new TestCaseData[]
Copy link

@pakapik pakapik Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Раз IEnumerable<> типизирован, то после new нет нужды ещё раз писать тип - можно его убрать. Компилятор сам разберется.

new NumberValidator(precision, scale, onlyPositive).IsValidNumber(number);


[TestCaseSource(nameof(ConstructorArgumentExceptions))]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Найс. После рефакторинга видно, что источник данных для теста с конструктором слишком далеко от самого теста.

Давай протащим 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")
Copy link

@pakapik pakapik Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай добавим чуть больше данных для проверок - 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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Этот же тест в IsValidNumberDiffrentFormsOfNumbersTests

new TestCaseData(17, 2, true, " ").Returns(false).SetName("False_WhenStringOfSpacesGiven")
};

[Pure]
Copy link

@pakapik pakapik Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Pure] стоило прицепить на метод IsValidNumber, с тестами их юзать не стоит.

Атрибуты обычно стараются выравнивать по длине

Чтоб
Было вот так

В случае, когда были только источники данных, я пропускал этот момент, но [Repeat(5)] прям глаз режет)


[Pure]
[TestCaseSource(nameof(ConstructorArgumentExceptions))]
public void Constructor_ThrowsArgumentException(int precision, int scale, bool onlyPositive) =>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Возможно, стоит добавить тест, когда конструктор не кидает исключений.

[Test]
public void ValidatorState_ShouldStayUnchanged()
{
var validator = new NumberValidator(10, 9, true);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет структуры ААА - Arrange, Act, Assert. Если тест небольшой, то обычно просто пустыми строками разделяют. Если большой (либо есть желание), то дополнительно прям пишут, где какая секция, используя комментарии.

Тут писать комменты смысла нет, но логически всё равно стоит разделить.

.SetName("True_WhenFracPartNotGreaterThanScale")
};

private static IEnumerable<TestCaseData> IsValidNumberBigNumbersTests = new[]
Copy link

@pakapik pakapik Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Было бы правильнее назвать не BigNumbers, а LongNumbers, т.к. 1/3 в виде десятичной дроби - число длинное, но не большое). В то время, как Int64.MaxValue и большое, и длинное.


private static IEnumerable<TestCaseData> IsValidNumberDiffrentFormsOfNumbersTests = new[]
{
new TestCaseData(2, 1, true, "01").Returns(true)
Copy link

@pakapik pakapik Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В программировании относительно часто используется так же и шестнадцатеричная система, её тоже стоит добавить).

Двоичная как-то не очень выглядит. Глядя на тест я вижу, что валидатор считает число 01 верным. А если увеличить разрядность и написать 0001? Должно быть это зависит от передаваемых параметров?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants