Skip to content

Русинов Матвей#48

Open
nbdevncrs wants to merge 4 commits intokontur-courses:masterfrom
nbdevncrs:master
Open

Русинов Матвей#48
nbdevncrs wants to merge 4 commits intokontur-courses:masterfrom
nbdevncrs:master

Conversation

@nbdevncrs
Copy link

No description provided.

@nbdevncrs
Copy link
Author

@SquirrelLeonid

Copy link

@SquirrelLeonid SquirrelLeonid left a comment

Choose a reason for hiding this comment

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

Здесь оставлю общий комментарий

  1. Правок в задаче немного, но тем не менее было бы хорошо оформить в виде двух отдельных коммитов. Коммит стоит рассматривать как законченную мысль. Это как выполнить маленький шаг из большой задачи. В данном случае точно можно было разделить домашку на две задачи: ObjectComparison и NumberValidator. Дробить можно и дальше, но слишком мелкие шаги тоже не стоит делать, чтобы не засорять историю в гите (или другой системе контроля версий)

  2. Сообщения в коммитах стоит делать более информативными (но не длинными).
    К примеру, в первой строке можно коротко описать основной шаг, а в последующих строках по пунктам тезисно указать подробности. При внесени правок как раз будет возможность потренироваться :)

  3. Проверь, что код отформатирован с помощью Resharper


public class ObjectComparison
{
[Test]

Choose a reason for hiding this comment

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

Оставлю комментарий здесь, т.к. github не дает мне выбрать неизмененные строки кода
Обычно имена тестов следуют некоторым правилам (соглашениям). В разных компаниях (да и командах внутри компании) они могут различаться. Например, один из способов именования такой: <ClassName>_<ShouldBe...>_[When...]_Test.
В данном случае можно ограничиться добавлением суффикса _Test в конец имени тестов. Так мы явно выделим эти методы от "обычных" методов.

К слову, почти такой же способ ты использовал в NumberValidatorTests

Copy link
Author

Choose a reason for hiding this comment

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

поменял названия, которые совсем не подходили по структуре
обновил названия, которые не полностью совпадали со структурой

@@ -14,16 +16,37 @@ public void CheckCurrentTsar()
var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70,

Choose a reason for hiding this comment

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

В способе получения expextedTsar есть две проблемы:

  1. Код в тестах CheckCurrentTsar_WithCustomEquality и CheckCurrentTsar дублируется. Если мы захотим изменить ожидаемого царя, то изменения придется внести в двух местах. Это повышает риск ошибки.
  2. При создании expectedTsar (да и в методе TsarRegistry.GetCurrentTsar) читаемость кода снижается при указании родителя в параметрах. Легко может возникнуть вопрос: внутренний объект Person - это родитель или потомок? Для ответа надо смотреть в конструктор класса. В данном случае для повышения читаемости стоит вынести создание родителя в отдельную строку. Тогда о назначении объекта будет говорить имя переменной, в которую он записан.

Copy link
Author

Choose a reason for hiding this comment

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

  1. вынес в метод-хелпер в файле с тестами получение текущего предполагаемого царя
  2. объявляю переменную с родителем в отдельной строке

Choose a reason for hiding this comment

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

Ага, именно то, что хотелось увидеть здесь

Comment on lines 19 to 21
actualTsar.Should().BeEquivalentTo(expectedTsar, config => config
.Excluding(x => x.Id)
.Excluding(x => x.Parent.Id));

Choose a reason for hiding this comment

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

Решение рабочее и, формально, удовлетворяет требованиям в задаче. Нам действительно нужно писать меньше кода при расширении Person. Но давай подумаем о двух моментах:

  1. Сейчас тест упадет, если мы начнем указывать внуков (или дедов). Подумай, как этого можно избежать.
  2. Сейчас мы никак не обрабатываем циклические ссылки.
    Подумай, как это можно сделать.

Подсказка: решение есть в рамках fluentAssertions

Copy link
Author

Choose a reason for hiding this comment

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

  1. добавил решение через перебор Id, а не конкретных переменных
  2. добавил .IgnoringCyclicReferences() из FluentAssertions

public void Constructor_ThrowsException_OnInvalidArguments(int precision, int scale)
{
Action constructor = () => new NumberValidator(precision, scale);
constructor.Should().Throw<ArgumentException>();

Choose a reason for hiding this comment

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

В целом нам может быть достаточно, что конструктор кидает ArgumentException. Однако часто в тестах важно отследить, что проверяемый код кидает не только исключение определенного типа, но и с определенным сообщением. Это будет являться дополнительной гарантией, что код делает то, что от него ожидается.

Здесь стоит сделать тест более информативным в этом плане.

Copy link
Author

Choose a reason for hiding this comment

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

добавил expectedMessage в Testcase по тем, что есть в конструкторе, с помощью WithMessage() и маски, чтобы не хранить полные сообщения

Choose a reason for hiding this comment

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

Маска действительно позволяет хранить меньше, но в тестах все таки важна конкретика.
К примеру, если конструктор вместо "precision must be a positive number" начнет выдавать "precision must NOT BE a positive number", то тест все равно пройдет. Хотя по хорошему он должен упасть.

Т.е. в данном случае нам наоборот стоит хранить полный текст сообщения - при любом его изменении тест поможет нам отловить это.

На зачет задачи это не повлияет, но можешь залить соответствующую правку

Comment on lines 20 to 26
[TestCase(1, 0, TestName = "Precision and scale are minimum possible")]
[TestCase(1000, 500, TestName = "Precision and scale are big enough")]
public void Constructor_DoesNotThrowException_OnValidArguments(int precision, int scale)
{
Action constructor = () => new NumberValidator(precision, scale, true);
constructor.Should().NotThrow();
}

Choose a reason for hiding this comment

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

С одной стороны, под рефакторингом понимается изменение кода без изменения его поведения. С этой точки зрения тесты действительно должны быть - ведь они были в исходной версии.
Но с другой стороны - какую пользу дает этот набор тестов сравнительно с тестами Constructor_ThrowsException_OnInvalidArguments?
Т.е. мы очевидно ожидаем, что конструктор не будет кидать исключение при корректных параметрах. Вместе с этим мы описали некорректные параметры в пессимистичном тесте Constructor_ThrowsException_OnInvalidArguments. Я не предлагаю снести эти тесты - этот комментарий тебе просто на обдумывание

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.

Это хороший подход
Граничные случаи - достаточно частое место для ошибок

Comment on lines 47 to 49
[TestCase(6, 0, "1234", TestName = "Simple integer")]
[TestCase(20, 10, "123.54", TestName = "Number with fraction part")]
public void Validator_ReturnsTrue_WithProperNumbers(int precision, int scale, string input)

Choose a reason for hiding this comment

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

Здесь проверяем оптимистичный сценарий, когда число указано корректно. Но у читателя может возникнуть вполне закономерный вопрос - почему выбраны именно такие значения для precision и scale в тестах? В данном случае стоит указать минимально допустимые значения для precision и scale в тест-кейсах.

Copy link
Author

Choose a reason for hiding this comment

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

изначально думал сделать такие "рандомные" числа для того, чтобы не вызывать падение теста на каких-то недочетах (не серьезных проблем) при работе со scale и precision, поскольку для этого были предназначены другие тесты, но понял, что тогда и смысл этого теста в целом теряется, поправил

Choose a reason for hiding this comment

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

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

Comment on lines 55 to 57
[TestCase("1234567", TestName = "Precision overflow with numerals")]
[TestCase("0000000", TestName = "Precision overflow with zeros")]
public void Validator_ReturnsFalse_WithPrecisionOverflow(string input)

Choose a reason for hiding this comment

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

В чем то похоже на предыдущий комментарий.
Можно обойтись более короткими числами и меньшим значением для precision. В этом тесте ключевым фактом для нас должно быть то, что количество знаков в числе больше значения precision

Copy link
Author

Choose a reason for hiding this comment

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

согласен, поправил

}

[TestCase]
public void Validator_ReturnsTrue_WithCommaSeparator()

Choose a reason for hiding this comment

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

А только ли запятая у нас бывает разделителем?

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.

Ага
Тесты часто пересекаются в чем то. Примеры выше уже захватывают точку и, как бы, неявно проверяют корректность ее обработки.
Здесь важно различать что каждый конкретный тест проверяет. Этот вот прям направлен на разделитель дробной части. По факту он же может служить документацией по допустимым разделителям

@SquirrelLeonid
Copy link

Задачу засчитываю на полный балл.

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