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

Яценко Ирина #235

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

Conversation

lholypeachy
Copy link

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.

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

}

[Test]
public void NumberValidator_WhenPassValidArguments_ShouldDoesNotThrows()
Copy link

Choose a reason for hiding this comment

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

Хорошо. Ещё, как вариант в борьбе с дублированием кода, 2 предыдущих теста можно было бы объединить в один с именем NumberValidator_WhenPassInvalidArguments_ShouldThrowsArgumentException, подтягивая данные из TestCaseSource . Подобный подход хорош тем, что на один тест можно клепать много входных данных, проверяя n сценариев сразу. При этом можно манипулировать параметрами входных данных всякими SetXXX и Object Mother / Test Data Buider. Из минусов можно назвать, что тесту приходится давать чуть более общее имя (которое, впрочем, можно уточнить через SetName) и придумывать какое-то название для TestCaseSource, но это мелочи по сравнению с тем, какого объёма кода иногда можно избежать. Это просто к сведению, переделывать не нужно.

Буду оставлять ещё nit: замечания, которые цепляют глаз только у меня. Ты можешь принимать их к сведению, но не обязана что-то с этим делать - исправлять или даже отвечать.

}

[Test]
public void NumberValidator_WhenPrecisionIsEqualToTheScale_ShouldReturnFalse()
Copy link

Choose a reason for hiding this comment

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

Ожидаемый результат ShouldReturnFalse не соответствует секции Assert.

nit: к NumberValidator в названии теста я бы добавил ctor: NumberValidatorCtor, чтоб подчеркнуть, что тестируется именно конструктор, а не класс.

[Test]
public void IsValidNumber_WhenPassOnlyPositiveIsFalseButNumbersDoesNotHaveNegativeSign_ShouldReturnTrue()
{
var validator = new NumberValidator(17, 2);
Copy link

Choose a reason for hiding this comment

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

nit: в лекции видел, что у вас есть упоминание System Under Test. По некоторым гайдам/книгам/статьям, именно так и стоит называть тестируемый объект - sut. Если бы секция Arrange была больше, то из nit это превратилось в момент, который стоит поправить.

}

[Test]
public void IsValidNumber_WhenPassOnlyPositiveIsFalseButNumbersDoesNotHaveNegativeSign_ShouldReturnTrue()
Copy link

Choose a reason for hiding this comment

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

Откровенно говоря, я не понял условия 🙃
Почему PassOnlyPositive - IsFalse ?

}

[Test]
public void IsValidNumber_WhenLettersInsteadOfNumber_ShouldReturnFalse()
Copy link

Choose a reason for hiding this comment

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

nit: лично я не вижу смысла добавлять название тестируемого метода в название теста по той причине, что названия методов, бывает, меняются. А про тесты вспоминают, уже когда те начинают падать по иным причинам.

Обычно я делаю так: [Test][TestOf(nameof(NumberValidator.IsValidNumber))] public void ShouldBe[что-то]_When[что-то].

Assert.AreEqual(actualTsar.Age, expectedTsar.Age);
Assert.AreEqual(actualTsar.Height, expectedTsar.Height);
Assert.AreEqual(actualTsar.Weight, expectedTsar.Weight);
// Перепишите код на использование Fluent Assertions.
Copy link

Choose a reason for hiding this comment

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

В комменте уже нет смысла

var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70,
new Person("Vasili III of Russia", 28, 170, 60, null));
// Какие недостатки у такого подхода?
//Данный подход делает класс труднорасширяемым, ведь при добавлении новых полей в класс Person
Copy link

Choose a reason for hiding this comment

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

Казалось бы мелочь, но после // принято ставить пробел - так читается проще 🙃

//новые поля не будут сравниваться.
//В моем решении (CheckCurrentTsar), такой ошибки не возникнет и класс Person сможет без ошибок расширяться
//при условии, что сравниваться будут все поля, кроме Id (так же и их Parent), так как код написан не перебиранием
//всех полей для сравнения, а сравнением объекта в целом с исключением его Id.
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 IsValidNumber_WhenPassOnlyPositiveIsFalseButNumbersDoesNotHaveNegativeSign_ShouldReturnTrue()
Copy link

Choose a reason for hiding this comment

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

nit: numbers множественное число, does следовало бы заменить на do - does используется только для he/she/it. Обращаю внимание только по той причине, что можно было сократить название длинного метода на пару буковок🙃

Copy link

Choose a reason for hiding this comment

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

Круто, что коммиты логически разбиты

using NUnit.Framework;

namespace HomeExercises
{
public class NumberValidatorTests
{
[Test]
public void NumberValidator_WhenPassNegativePrecision_ShouldThrowsArgumentException()
public void NumberValidatorCtor_WhenPassNegativePrecision_ShouldThrowsArgumentException()
Copy link

Choose a reason for hiding this comment

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

Оставлю ссылку на этот комментарий, и ещё раз скажу, что тесты, которые выкидывают исключение, можно объединить в один, указав для них TestCaseSource

var numberIsValid = validator.IsValidNumber("a.sd");

Assert.IsFalse(numberIsValid);
IsValidNumberTest(17,2,true,"0", true);
Copy link

Choose a reason for hiding this comment

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

Тут тоже нужно подумать в сторону TestCaseSource. Вызывать внутри теста другой тест - плохая практика.
Под обобщением создания корректного валидатора я подразумевал немного иное. Подумай, как можно получать в методе / группе методов корректный валидатор, не вызывая непосредственно конструктор?

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

using NUnit.Framework;

namespace HomeExercises
{
public class NumberValidatorTests
{
private static IEnumerable<TestCaseData> ArgumentExceptionTestCases
{
Copy link

Choose a reason for hiding this comment

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

Очевидно, что слетело форматирование. Если пользуешься rider, то в нем можно прожать ctrl + alt + L / ctrl + alt + shift + L, код отформатируется. Можно так же настроить автоформатирование (можешь погуглить) на каждое сохранение кода ctrl + s - очень удобно, кстати, рекомендую. Если в vs работаешь, то не подскажу, но всё легко гуглится.

using NUnit.Framework;

namespace HomeExercises
{
public class NumberValidatorTests
{
private static IEnumerable<TestCaseData> ArgumentExceptionTestCases
{
get
Copy link

Choose a reason for hiding this comment

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

Зачем тут геттер?)

{
get
{
yield return new TestCaseData(-1, 2, true).SetName("WhenPassNegativePrecision");
Copy link

Choose a reason for hiding this comment

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

Зачем тут yield return?)

public void NumberValidatorCtor_WhenPassInvalidArguments_ShouldThrowArgumentException(int precision,
int scale, bool onlyPositive)
{
TestDelegate testDelegate = () => 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.

Всё тело метода можно упростить до одной строчки, не забудь про expression body

yield return new TestCaseData("0.000", false).SetName("WhenFractionalPartMoreThanScale");
}
}
private static IEnumerable<TestCaseData> ValidArgumentTestCases
Copy link

Choose a reason for hiding this comment

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

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

}
}

private NumberValidator numberValidator;
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.

Тоже как вариант обобщения. Иногда SetUp очень удобен, но он запускается для каждого теста - в том числе для тестов, где ты проверяешь конструктор.

Помимо прочего, не беря во внимание SetUp (т.е. вообще не юзаем SetUp), хотел бы услышать ответ на вопрос - что может пойти не так, если использовать в тест-классе поле, тем более, если это поле-sut? Представь, что у тебя не несколько тестов, а 100-200 штук, и в каждом по своему разумению используется поле numberValidator?

По тому, как я бы хотел видеть создание numberValidator, дам подсказку - тебе нужно сделать обертку над созданием numberValidator, своего рода фабрику (не в ортодоксальном её понимании), какой-то мини-член класса, который отвечал бы только за что, что возвращал бы корректный валидатор)

К слову, поля обычно начинается с нижнего подчеркивания: _numberValidator. Это позволяет не использовать конструкцию this внутри класса, да и в целом сразу видно, что это поле, а не просто локальная переменная. Это тоже где-то настраивается в настройках среды. В Rider Settings (ctrl + alt + s)EditorCode StyleC#. И тут куча всяких настроек, можешь попробовать настроить под себя + выставить в подсказки мои замечания, чтоб среда сама тебе всё подсказывала в будущем. Но поле numberValidator тебе тут вообще не нужно)

[TestCaseSource(nameof(InvalidArgumentTestCases))]
public void WhenPassInvalidArguments_ShouldReturnFalse(string number, bool expectedResult)
{
var actualResult = numberValidator.IsValidNumber(number);
Copy link

Choose a reason for hiding this comment

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

Ещё один минус SetUp - это непонятно, в каком состоянии находится сейчас numberValidator. Очевидно, что он проинициализирован, но с какими параметрами? Сейчас название теста не соответствует проверяемым кейсам - ValidArgumentTestCases как источник данных, но при этом WhenPassInvalidArguments_ShouldReturnFalse.

&& actual.Age == expected.Age
&& actual.Height == expected.Height
&& actual.Weight == expected.Weight
&& AreEqual(actual.Parent, expected.Parent);
Copy link

Choose a reason for hiding this comment

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

Ранее не обратил внимание, но объясни ещё, пж, про 52 строку - в чем тут потенциальная опасность?

Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("a.sd"));
TestDelegate testDelegate = () => new NumberValidator(1, 0, true);

Assert.DoesNotThrow(testDelegate);
Copy link

Choose a reason for hiding this comment

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

Тут тоже можно до одной строки упростить.

using System.Text.RegularExpressions;
using FluentAssertions;
using NUnit.Framework;

namespace HomeExercises
{
public class NumberValidatorTests
Copy link

Choose a reason for hiding this comment

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

Ранее об этом не говорил, но класс тоже можно пометить атрибутом 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))]
Copy link

Choose a reason for hiding this comment

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

[TestFixture] дублировать не надо, хватит одной записи - первой или второй (на мой взгляд более предпочтительной)

[TestOf(nameof(NumberValidator.IsValidNumber))]
[TestCaseSource(nameof(ArgumentTestCases))]
public void WhenPassInvalidArguments_ShouldReturnFalse(int precision, int scale,
bool onlyPositive, string number, bool expectedResult)
Copy link

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

Все ещё неверное название теста. У тебя в источнике есть кейсы, которые проверяют, что IsValidNumber вернет true + туда подаются валидные аргументы.

var match = numberRegex.Match(value);
if (!match.Success)
return false;
Assert.AreEqual(expectedResult, correctValidator.IsValidNumber(number));
Copy link

Choose a reason for hiding this comment

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

Не надо смешивать секцию 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")
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. В данном случае [Pure] применяться должен к IsValidNumber, раз он внутри себя не делает ничего такого криминального.

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

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