Skip to content

Павел Шипицын#37

Open
PavelMartinelli wants to merge 5 commits intokontur-courses:masterfrom
PavelMartinelli:Homework
Open

Павел Шипицын#37
PavelMartinelli wants to merge 5 commits intokontur-courses:masterfrom
PavelMartinelli:Homework

Conversation

@PavelMartinelli
Copy link

No description provided.

ClassicAssert.AreEqual(expectedTsar.Parent.Parent, actualTsar.Parent.Parent);
// Преимущества решение с FluentAssertions:
// - Легко расширяем при добавлении новых свойств в Person
// - Автоматически проверяем все свойства, включая вложенные объекты

Choose a reason for hiding this comment

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

А все ли вложенные объекты мы проверим? Советую почитать доку FluentAssertions https://fluentassertions.com/

Copy link
Author

@PavelMartinelli PavelMartinelli Oct 28, 2025

Choose a reason for hiding this comment

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

Да, я неправильно написал, что все вложенные объекты будут обязательно проверены. Вроде бы глубина вложенности будет по умолчанию только 10 уровней для избежания бесконечной рекурсии, но можно использовать .AllowingInfiniteRecursion() для снятия этого ограничения. Но наверное тут нам это не нужно.

// - Автоматически проверяем все свойства, включая вложенные объекты
// - При несовпадении конкретного свойства будет выдана информация какое именно свойство не совпало
actualTsar.Should().BeEquivalentTo(expectedTsar, options => options
.Excluding(p => p.Id)

Choose a reason for hiding this comment

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

Два раза вызываем Excluding для того чтобы исключить поле Id у объектов одного и того же класса Person. Можем ли мы как-то сделать общую настройку, которая будет затрагивать все экземпляры одного класса?

К примеру, если сейчас появится поле Sister того же класса, то лля него тоже нужно будет вызвать Excluding, что неудобно

// Какие недостатки у такого подхода?
// Недостатки подхода с CustomEquality:
// - Нет детальной информации о том, какое именно свойство не совпало
// - При добавлении новых свойств в Person нужно менять метод AreEqual

Choose a reason for hiding this comment

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

Помимо того что метод AreEqual неудобный, есть ли у него какие-то ещё проблемы, из-за которых что-то может пойти не так при работе с ним?

Copy link
Author

Choose a reason for hiding this comment

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

Наверное проблемой может быть переполнения стека при большом уровне вложенности. Метод уйдет в бесконечную рекурсию.

Choose a reason for hiding this comment

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

Стоит убрать после окончания рефакторинга

// - Легко расширяем при добавлении новых свойств в Person
// - Автоматически проверяем все свойства, включая вложенные объекты
// - При несовпадении конкретного свойства будет выдана информация какое именно свойство не совпало
actualTsar.Should().BeEquivalentTo(expectedTsar, options => options

Choose a reason for hiding this comment

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

Ещё хорошей практикой будет выделить этот способ проверки в отдельный метод, как снизу сделано с AreEqual. Это удобно, когда тебе нужно для разных тесткейсов вызывать сравнение объектов класса Person, а логика сравнения лежит в одном месте.

Comment on lines 60 to 63
[TestCase("1.23a4", 10, 7, true)]
[TestCase("t", 4, 3, true)]
[TestCase("b.0", 4, 3, true)]
[TestCase("1.2b34", 10, 7, true)]

Choose a reason for hiding this comment

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

Вот это всё по факту дубли. Для того чтобы составить тесткейсы, могу предложить дополнительно зайти на какой-либо сайт с онлайн Regex-ом. В него можно вставить паттерн из метода и посмотреть, что именно там происходит. Тогда получится избавиться от немалого количества дублей

[TestCase(2, 1, "123", false)]
[TestCase(3, 0, "-12", true)]
[TestCase(3, 1, "-123", false)]
public void IsValid_IntegerBounds_Expected(int precision, int scale, string value, bool expected)

Choose a reason for hiding this comment

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

Части с IntegerBound и Expected не понятны, стоит дополнить аналогично верхним тестам

[TestCase("11.234", 5, 4, true)]
[TestCase("11,234", 5, 4, true)]
[TestCase("1.234", 5, 3, true)]
public void IsValid_WhenValidNumbers_ReturnTrue(string value, int precision, int scale, bool onlyPositive)

Choose a reason for hiding this comment

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

Стоит полностью указывать название метода на который ориентирован тест. В данном случае корректнее будет назвать его IsValidNumber_WhenValidValue_ReturnTrue.

Comment on lines 92 to 103
[TestCase(8, 4, "12.34", true)]
[TestCase(5, 4, "11.234", true)]
[TestCase(7, 3, "1.234", true)]
[TestCase(8, 4, "+123,4567", true)]
[TestCase(4, 2, "+12.34", false)]
[TestCase(6, 2, "1.234", false)]
[TestCase(5, 4, "-12.3", true)]
[TestCase(8, 4, "-1234.567", true)]
[TestCase(12, 4, "-123.4567", true)]
[TestCase(6, 3, "-12,345", true)]
[TestCase(4, 1, "-123,4", false)]
[TestCase(10, 2, "-123.456", false)]

Choose a reason for hiding this comment

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

Тут тоже присутствуют дубли, а также постоянно меняются значения, из-за чего не понятно что именно проверяем

[TestCase(1, -1, false)]
[TestCase(1, 2, false)]
[TestCase(1, 1, false)]
public void Constructor_WhenInvalidArgs_Throws(int precision, int scale, bool onlyPositive)

Choose a reason for hiding this comment

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

А ещё касательно названий тестов. Есть разные способы составления названия, здесь используется сейчас <название метода>When<условие><Результат>. У нас в команде используется к примеру другая семантика - Should_<ожидаемый результат в snake case>_when<условие в snake case>, тестируемый метод мы обычно не указываем, т.к. тестовые файлы поделены по методам класса.

Так что можешь в целом почитать/посмотреть как в проектах шарповых идёт именование тестов и выбрать себе предпочтительный способ, если текущий кажется не сильно удобным, главное чтобы он был стандартизированным и удобным тебе.

ClassicAssert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("-1.23"));
ClassicAssert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("a.sd"));
[TestCase(-1, 2, true, TestName = "precision is negative")]
[TestCase(0, 0, false, TestName = "precision is zero")]

Choose a reason for hiding this comment

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

У тебя ниже есть тест "scale equals precision", однако если взглянуть на этот тест, может появится впечатление что они проверяют одно и то же. Когда мы просматриваем тесты, мы не знаем в каком порядке чередуются проверки в коде, поэтому следует писать тест так, чтобы он падал только на необходимой для нас проверке, то есть здесь желательно передавать scale, который больше нуля.

[TestCase("-1.23", 3, 2, true, TestName = "negative when positive only")]
[TestCase("1.2a", 3, 2, true, TestName = "invalid character in fraction")]
[TestCase("-123", 4, 0, true, TestName = "negative integer when positive only")]
[TestCase("-1.23", 4, 2, true, TestName = "negative fractional when positive only")]

Choose a reason for hiding this comment

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

Дубль для теста "negative when positive only"?

[TestCase("", 1, 0, false, TestName = "empty string")]
[TestCase(null, 1, 0, false, TestName = "null value")]
[TestCase("01.23", 3, 2, true, TestName = "leading zeros")]
[TestCase("+0.00", 3, 2, true, TestName = "sign with zero")]

Choose a reason for hiding this comment

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

Непонятное название тесткейса. Фактически падаем не из-за того что знак указан вместе с нулём, а потому что precision == 3, а целая и дробная часть в сумме равны 4, потому как туда входит знак


[TestCase("", 1, 0, false, TestName = "empty string")]
[TestCase(null, 1, 0, false, TestName = "null value")]
[TestCase("01.23", 3, 2, true, TestName = "leading zeros")]

Choose a reason for hiding this comment

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

Не из-за этого падаем. Если установить precision = 5, то тест упадёт. Стоит добавить его в валидные случаи, такие числа могут быть валидными

validator.IsValidNumber(value).Should().BeTrue();
}

[TestCase("", 1, 0, false, TestName = "empty string")]

Choose a reason for hiding this comment

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

Можно к этим тесткейсам добавить случаи, когда перед числовым значением есть пробелы, после значения пробелы и случай когда до и после они есть

[TestCase(null, 1, 0, false, TestName = "null value")]
[TestCase("01.23", 3, 2, true, TestName = "leading zeros")]
[TestCase("+0.00", 3, 2, true, TestName = "sign with zero")]
[TestCase("-1.23", 3, 2, true, TestName = "negative when positive only")]

Choose a reason for hiding this comment

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

Если посмотреть в дебаге, то до желаемой проверки мы не добираемся, тест проходит не из-за причин указанных в названии

[TestCase("01.23", 3, 2, true, TestName = "leading zeros")]
[TestCase("+0.00", 3, 2, true, TestName = "sign with zero")]
[TestCase("-1.23", 3, 2, true, TestName = "negative when positive only")]
[TestCase("1.2a", 3, 2, true, TestName = "invalid character in fraction")]

Choose a reason for hiding this comment

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

стоит добавить случаи когда они в целой части, а также когда просто внутрь строка без чисел попадает. Можно закинуть также случаи со строкой со специальными символами

}

[TestCase("123", 2, 1, false, TestName = "integer exceeds precision")]
[TestCase("-12", 2, 1, false, TestName = "negative integer exceeds precision")]

Choose a reason for hiding this comment

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

А зачем здесь scale отличный от нуля, если проверка целой части?

[TestCase(1, -1, false, TestName = "scale is negative")]
[TestCase(1, 2, false, TestName = "scale greater than precision")]
[TestCase(1, 1, false, TestName = "scale equals precision")]
public void Constructor_WhenInvalidArgs_ThrowsArgumentException(int precision, int scale, bool onlyPositive)

Choose a reason for hiding this comment

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

Вообще здесь было бы идеально перебирать все варианты значения onlyPositive, т.к. никакого влияния на конструктор он не имеет, но внутрь передаётся. Можно реализовать это через TestCaseSource https://docs.nunit.org/articles/nunit/writing-tests/attributes/testcasesource.html

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