Conversation
pakapik
left a comment
There was a problem hiding this comment.
Если покажется, что замечаний много, то не переживай - во-первых, нет, во-вторых, это нормально, т.к. ревью для того и делается 🙃
| } | ||
|
|
||
| [Test] | ||
| public void NumberValidator_WhenPassValidArguments_ShouldDoesNotThrows() |
There was a problem hiding this comment.
Хорошо. Ещё, как вариант в борьбе с дублированием кода, 2 предыдущих теста можно было бы объединить в один с именем NumberValidator_WhenPassInvalidArguments_ShouldThrowsArgumentException, подтягивая данные из TestCaseSource . Подобный подход хорош тем, что на один тест можно клепать много входных данных, проверяя n сценариев сразу. При этом можно манипулировать параметрами входных данных всякими SetXXX и Object Mother / Test Data Buider. Из минусов можно назвать, что тесту приходится давать чуть более общее имя (которое, впрочем, можно уточнить через SetName) и придумывать какое-то название для TestCaseSource, но это мелочи по сравнению с тем, какого объёма кода иногда можно избежать. Это просто к сведению, переделывать не нужно.
Буду оставлять ещё nit: замечания, которые цепляют глаз только у меня. Ты можешь принимать их к сведению, но не обязана что-то с этим делать - исправлять или даже отвечать.
| } | ||
|
|
||
| [Test] | ||
| public void NumberValidator_WhenPrecisionIsEqualToTheScale_ShouldReturnFalse() |
There was a problem hiding this comment.
Ожидаемый результат ShouldReturnFalse не соответствует секции Assert.
nit: к NumberValidator в названии теста я бы добавил ctor: NumberValidatorCtor, чтоб подчеркнуть, что тестируется именно конструктор, а не класс.
| [Test] | ||
| public void IsValidNumber_WhenPassOnlyPositiveIsFalseButNumbersDoesNotHaveNegativeSign_ShouldReturnTrue() | ||
| { | ||
| var validator = new NumberValidator(17, 2); |
There was a problem hiding this comment.
nit: в лекции видел, что у вас есть упоминание System Under Test. По некоторым гайдам/книгам/статьям, именно так и стоит называть тестируемый объект - sut. Если бы секция Arrange была больше, то из nit это превратилось в момент, который стоит поправить.
| } | ||
|
|
||
| [Test] | ||
| public void IsValidNumber_WhenPassOnlyPositiveIsFalseButNumbersDoesNotHaveNegativeSign_ShouldReturnTrue() |
There was a problem hiding this comment.
Откровенно говоря, я не понял условия 🙃
Почему PassOnlyPositive - IsFalse ?
| } | ||
|
|
||
| [Test] | ||
| public void IsValidNumber_WhenLettersInsteadOfNumber_ShouldReturnFalse() |
There was a problem hiding this comment.
nit: лично я не вижу смысла добавлять название тестируемого метода в название теста по той причине, что названия методов, бывает, меняются. А про тесты вспоминают, уже когда те начинают падать по иным причинам.
Обычно я делаю так: [Test][TestOf(nameof(NumberValidator.IsValidNumber))] public void ShouldBe[что-то]_When[что-то].
cs/HomeExercises/ObjectComparison.cs
Outdated
| Assert.AreEqual(actualTsar.Age, expectedTsar.Age); | ||
| Assert.AreEqual(actualTsar.Height, expectedTsar.Height); | ||
| Assert.AreEqual(actualTsar.Weight, expectedTsar.Weight); | ||
| // Перепишите код на использование Fluent Assertions. |
cs/HomeExercises/ObjectComparison.cs
Outdated
| var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70, | ||
| new Person("Vasili III of Russia", 28, 170, 60, null)); | ||
| // Какие недостатки у такого подхода? | ||
| //Данный подход делает класс труднорасширяемым, ведь при добавлении новых полей в класс Person |
There was a problem hiding this comment.
Казалось бы мелочь, но после // принято ставить пробел - так читается проще 🙃
cs/HomeExercises/ObjectComparison.cs
Outdated
| //новые поля не будут сравниваться. | ||
| //В моем решении (CheckCurrentTsar), такой ошибки не возникнет и класс Person сможет без ошибок расширяться | ||
| //при условии, что сравниваться будут все поля, кроме Id (так же и их Parent), так как код написан не перебиранием | ||
| //всех полей для сравнения, а сравнением объекта в целом с исключением его Id. |
| } | ||
|
|
||
| [Test] | ||
| public void IsValidNumber_WhenPassOnlyPositiveIsFalseButNumbersDoesNotHaveNegativeSign_ShouldReturnTrue() |
There was a problem hiding this comment.
nit: numbers множественное число, does следовало бы заменить на do - does используется только для he/she/it. Обращаю внимание только по той причине, что можно было сократить название длинного метода на пару буковок🙃
| { | ||
| [Test] | ||
| public void NumberValidator_WhenPassNegativePrecision_ShouldThrowsArgumentException() | ||
| public void NumberValidatorCtor_WhenPassNegativePrecision_ShouldThrowsArgumentException() |
There was a problem hiding this comment.
Оставлю ссылку на этот комментарий, и ещё раз скажу, что тесты, которые выкидывают исключение, можно объединить в один, указав для них TestCaseSource
| var numberIsValid = validator.IsValidNumber("a.sd"); | ||
|
|
||
| Assert.IsFalse(numberIsValid); | ||
| IsValidNumberTest(17,2,true,"0", true); |
There was a problem hiding this comment.
Тут тоже нужно подумать в сторону TestCaseSource. Вызывать внутри теста другой тест - плохая практика.
Под обобщением создания корректного валидатора я подразумевал немного иное. Подумай, как можно получать в методе / группе методов корректный валидатор, не вызывая непосредственно конструктор?
Но опустим пока этот момент. Сейчас тебе надо сделать так, чтоб все твои 8 тестов, о которых мы говорили, превратились в один, для которых бы использовался один или несколько TestCaseSource.
| public class NumberValidatorTests | ||
| { | ||
| private static IEnumerable<TestCaseData> ArgumentExceptionTestCases | ||
| { |
There was a problem hiding this comment.
Очевидно, что слетело форматирование. Если пользуешься rider, то в нем можно прожать ctrl + alt + L / ctrl + alt + shift + L, код отформатируется. Можно так же настроить автоформатирование (можешь погуглить) на каждое сохранение кода ctrl + s - очень удобно, кстати, рекомендую. Если в vs работаешь, то не подскажу, но всё легко гуглится.
| { | ||
| private static IEnumerable<TestCaseData> ArgumentExceptionTestCases | ||
| { | ||
| get |
| { | ||
| get | ||
| { | ||
| yield return new TestCaseData(-1, 2, true).SetName("WhenPassNegativePrecision"); |
| public void NumberValidatorCtor_WhenPassInvalidArguments_ShouldThrowArgumentException(int precision, | ||
| int scale, bool onlyPositive) | ||
| { | ||
| TestDelegate testDelegate = () => new NumberValidator(precision, scale, onlyPositive); |
There was a problem hiding this comment.
Всё тело метода можно упростить до одной строчки, не забудь про expression body
| yield return new TestCaseData("0.000", false).SetName("WhenFractionalPartMoreThanScale"); | ||
| } | ||
| } | ||
| private static IEnumerable<TestCaseData> ValidArgumentTestCases |
There was a problem hiding this comment.
Помимо уже озвученных замечаний InvalidArgumentTestCases и ValidArgumentTestCases стоит разделить пустой строкой друг от друга, но это тоже обычно автоформатируется.
| } | ||
| } | ||
|
|
||
| private NumberValidator numberValidator; |
There was a problem hiding this comment.
Тоже как вариант обобщения. Иногда SetUp очень удобен, но он запускается для каждого теста - в том числе для тестов, где ты проверяешь конструктор.
Помимо прочего, не беря во внимание SetUp (т.е. вообще не юзаем SetUp), хотел бы услышать ответ на вопрос - что может пойти не так, если использовать в тест-классе поле, тем более, если это поле-sut? Представь, что у тебя не несколько тестов, а 100-200 штук, и в каждом по своему разумению используется поле numberValidator?
По тому, как я бы хотел видеть создание numberValidator, дам подсказку - тебе нужно сделать обертку над созданием numberValidator, своего рода фабрику (не в ортодоксальном её понимании), какой-то мини-член класса, который отвечал бы только за что, что возвращал бы корректный валидатор)
К слову, поля обычно начинается с нижнего подчеркивания: _numberValidator. Это позволяет не использовать конструкцию this внутри класса, да и в целом сразу видно, что это поле, а не просто локальная переменная. Это тоже где-то настраивается в настройках среды. В Rider Settings (ctrl + alt + s) → Editor → Code Style → C#. И тут куча всяких настроек, можешь попробовать настроить под себя + выставить в подсказки мои замечания, чтоб среда сама тебе всё подсказывала в будущем. Но поле numberValidator тебе тут вообще не нужно)
| [TestCaseSource(nameof(InvalidArgumentTestCases))] | ||
| public void WhenPassInvalidArguments_ShouldReturnFalse(string number, bool expectedResult) | ||
| { | ||
| var actualResult = numberValidator.IsValidNumber(number); |
There was a problem hiding this comment.
Ещё один минус SetUp - это непонятно, в каком состоянии находится сейчас numberValidator. Очевидно, что он проинициализирован, но с какими параметрами? Сейчас название теста не соответствует проверяемым кейсам - ValidArgumentTestCases как источник данных, но при этом WhenPassInvalidArguments_ShouldReturnFalse.
cs/HomeExercises/ObjectComparison.cs
Outdated
| && actual.Age == expected.Age | ||
| && actual.Height == expected.Height | ||
| && actual.Weight == expected.Weight | ||
| && AreEqual(actual.Parent, expected.Parent); |
There was a problem hiding this comment.
Ранее не обратил внимание, но объясни ещё, пж, про 52 строку - в чем тут потенциальная опасность?
| Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("a.sd")); | ||
| TestDelegate testDelegate = () => new NumberValidator(1, 0, true); | ||
|
|
||
| Assert.DoesNotThrow(testDelegate); |
There was a problem hiding this comment.
Тут тоже можно до одной строки упростить.
|
|
||
| namespace HomeExercises | ||
| { | ||
| public class NumberValidatorTests |
There was a problem hiding this comment.
Ранее об этом не говорил, но класс тоже можно пометить атрибутом TestFixture, причем туда же можно по желанию так же впихнуть TestOf: [TestFixture(TestOf = typeof(NumberValidator))]
| Assert.Throws<ArgumentException>(() => new NumberValidator(-1, 2, false)); | ||
| Assert.DoesNotThrow(() => new NumberValidator(1, 0, true)); | ||
| [TestFixture] | ||
| [TestFixture(TestOf = typeof(NumberValidator))] |
There was a problem hiding this comment.
[TestFixture] дублировать не надо, хватит одной записи - первой или второй (на мой взгляд более предпочтительной)
| [TestOf(nameof(NumberValidator.IsValidNumber))] | ||
| [TestCaseSource(nameof(ArgumentTestCases))] | ||
| public void WhenPassInvalidArguments_ShouldReturnFalse(int precision, int scale, | ||
| bool onlyPositive, string number, bool expectedResult) |
There was a problem hiding this comment.
nit: очень странный перенос аргументов. Мб, у тебя по ширине строки это форматируется, не знаю. Обычно пишут либо в одну строку:
(int precision, int scale, bool onlyPositive, string number, bool expectedResult),
либо переносят вообще всё:
(
int precision,
int scale,
bool onlyPositive,
string number,
bool expectedResult)
| new TestCaseData(3,2,true,"+1.23", false).SetName("WhenIntPartWithPositiveSignMoreThanPrecision"), | ||
| new TestCaseData(3,2,true,"0.000", false).SetName("WhenFractionalPartMoreThanScale"), | ||
| new TestCaseData(3,2,true,"0", true).SetName("WhenFractionalPartIsMissing"), | ||
| new TestCaseData(3,2,true,"0.0", true).SetName("WhenNumberIsValid") |
There was a problem hiding this comment.
new TestCaseData(3,2,true,"0.0", true).Returns().SetName("WhenNumberIsValid")
А bool expectedResult можешь убрать. Соответственно, тебе придется возвращать результат вызова IsValidNumber.
| return false; | ||
| [TestOf(nameof(NumberValidator.IsValidNumber))] | ||
| [TestCaseSource(nameof(ArgumentTestCases))] | ||
| public void WhenPassInvalidArguments_ShouldReturnFalse(int precision, int scale, |
There was a problem hiding this comment.
Все ещё неверное название теста. У тебя в источнике есть кейсы, которые проверяют, что IsValidNumber вернет true + туда подаются валидные аргументы.
| var match = numberRegex.Match(value); | ||
| if (!match.Success) | ||
| return false; | ||
| Assert.AreEqual(expectedResult, correctValidator.IsValidNumber(number)); |
There was a problem hiding this comment.
Не надо смешивать секцию Act и Assert. Отдельно напиши в var actualResult = correctValidator.IsValidNumber(number);, затем отдельно Assert.AreEqual(expectedResult, actualResult). Впрочем, когда ты переделаешь с учетом замечаний сверху, тебе вообще не понадобится писать Assert.
Но совет про не смешивать всё ещё актуальный (во всех смыслах🙃🙃🙃)
| new TestCaseData(3,2,true,"+1.23", false).SetName("WhenIntPartWithPositiveSignMoreThanPrecision"), | ||
| new TestCaseData(3,2,true,"0.000", false).SetName("WhenFractionalPartMoreThanScale"), | ||
| new TestCaseData(3,2,true,"0", true).SetName("WhenFractionalPartIsMissing"), | ||
| new TestCaseData(3,2,true,"0.0", true).SetName("WhenNumberIsValid") |
There was a problem hiding this comment.
Давай добавим чуть больше данных для проверок - null, спец.символы - /r , /n, просто символы %$# и т.д. Число с каким-то символом? Символ с каким-то числом? Строка из пробелов?
Что будет, если разделитель не точка, а запятая? А если разделителей несколько? А если в строке только разделитель?
Как валидатор ведет себя с очень большими числами? С очень большой точностью? А все вместе? Умеет ли он понимать числа в экспоненциальном формате?
А может, валидатор считает верным число в другой системе счисления? Иррациональные числа?
Возвращает ли валидатор на один и тот же входной параметр один и тот же результат? Для этого можно использовать атрибут [Repeat()].
А может, валидатор как-то меняет свой стейт? Что будет если подать сначала одно число, затем второе, а затем снова первое? Кстати, для того, чтоб обозначить, что метод ничего не изменяет (что-то внутри себя / входные параметры), то его можно пометить атрибутом [Pure] из неймспейса System.Diagnostics.Contracts или JetBrains.Annotations. В данном случае [Pure] применяться должен к IsValidNumber, раз он внутри себя не делает ничего такого криминального.
Тесты как документация кода должны отвечать на все эти вопросы и даже больше) Так что сама тоже подумай над тем, каких бы ещё тестов добавить
@pakapik