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
86 changes: 66 additions & 20 deletions cs/HomeExercises/NumberValidatorTests.cs
Copy link

Choose a reason for hiding this comment

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

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

Original file line number Diff line number Diff line change
@@ -1,34 +1,80 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
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.

using FluentAssertions; не используется. Обычно ide сама убирает неиспользуемые неймспейсы, но не всегда.

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))]

{
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 работаешь, то не подскажу, но всё легко гуглится.

get
Copy link

Choose a reason for hiding this comment

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

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

{
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?)

yield return new TestCaseData(1, -2, true).SetName("WhenPassNegativeScale");
yield return new TestCaseData(2, 2, true).SetName("WhenPrecisionIsEqualToTheScale");
}
}

[TestCaseSource(nameof(ArgumentExceptionTestCases))]
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


Assert.Throws<ArgumentException>(testDelegate);
}

[Test]
public void Test()
public void NumberValidatorCtor_WhenPassValidArguments_ShouldNotThrows()
{
Assert.Throws<ArgumentException>(() => new NumberValidator(-1, 2, true));
Assert.DoesNotThrow(() => new NumberValidator(1, 0, true));
Assert.Throws<ArgumentException>(() => new NumberValidator(-1, 2, false));
Assert.DoesNotThrow(() => new NumberValidator(1, 0, true));

Assert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0.0"));
Assert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0"));
Assert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0.0"));
Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("00.00"));
Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("-0.00"));
Assert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0.0"));
Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("+0.00"));
Assert.IsTrue(new NumberValidator(4, 2, true).IsValidNumber("+1.23"));
Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("+1.23"));
Assert.IsFalse(new NumberValidator(17, 2, true).IsValidNumber("0.000"));
Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("-1.23"));
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.

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

}
}

private static IEnumerable<TestCaseData> InvalidArgumentTestCases
{
get
{
yield return new TestCaseData("a.sd", false).SetName("WhenLettersInsteadOfNumber");
yield return new TestCaseData("2.!", false).SetName("WhenSymbolsInsteadOfNumber");
yield return new TestCaseData(null!, false).SetName("WhenPassNumberIsNull");
yield return new TestCaseData("", false).SetName("WhenPassNumberIsEmpty");
yield return new TestCaseData("-0.00", false).SetName("WhenIntPartWithNegativeSignMoreThanPrecision");
yield return new TestCaseData("+1.23", false).SetName("WhenIntPartWithPositiveSignMoreThanPrecision");
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 стоит разделить пустой строкой друг от друга, но это тоже обычно автоформатируется.

{
get
{
yield return new TestCaseData("0", true).SetName("WhenFractionalPartIsMissing");
yield return new TestCaseData("0.0", true).SetName("WhenNumberIsValid");
}
}

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 тебе тут вообще не нужно)


[SetUp]
public void SetUp()
{
numberValidator = new NumberValidator(3, 2, true);
}

[TestOf(nameof(NumberValidator.IsValidNumber))]
[TestCaseSource(nameof(ValidArgumentTestCases))]
[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.


Assert.AreEqual(expectedResult, actualResult);
}
}

public class NumberValidator
{
Expand Down
139 changes: 70 additions & 69 deletions cs/HomeExercises/ObjectComparison.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,81 +3,82 @@

namespace HomeExercises
{
public class ObjectComparison
{
[Test]
[Description("Проверка текущего царя")]
[Category("ToRefactor")]
public void CheckCurrentTsar()
{
var actualTsar = TsarRegistry.GetCurrentTsar();
public class ObjectComparison
{
[Test]
[Description("Проверка текущего царя")]
[Category("ToRefactor")]
public void CheckCurrentTsar()
{
var actualTsar = TsarRegistry.GetCurrentTsar();

var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70,
new Person("Vasili III of Russia", 28, 170, 60, null));
var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70,
new Person("Vasili III of Russia", 28, 170, 60, null));

// Перепишите код на использование Fluent Assertions.
Assert.AreEqual(actualTsar.Name, expectedTsar.Name);
Assert.AreEqual(actualTsar.Age, expectedTsar.Age);
Assert.AreEqual(actualTsar.Height, expectedTsar.Height);
Assert.AreEqual(actualTsar.Weight, expectedTsar.Weight);
actualTsar.Should()
.BeEquivalentTo(expectedTsar, x => x
.Excluding(x => x.Id)
.Excluding(x => x.Parent!.Id));
}

Assert.AreEqual(expectedTsar.Parent!.Name, actualTsar.Parent!.Name);
Assert.AreEqual(expectedTsar.Parent.Age, actualTsar.Parent.Age);
Assert.AreEqual(expectedTsar.Parent.Height, actualTsar.Parent.Height);
Assert.AreEqual(expectedTsar.Parent.Parent, actualTsar.Parent.Parent);
}
[Test]
[Description("Альтернативное решение. Какие у него недостатки?")]
public void CheckCurrentTsar_WithCustomEquality()
{
var actualTsar = TsarRegistry.GetCurrentTsar();
var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70,
new Person("Vasili III of Russia", 28, 170, 60, null));

[Test]
[Description("Альтернативное решение. Какие у него недостатки?")]
public void CheckCurrentTsar_WithCustomEquality()
{
var actualTsar = TsarRegistry.GetCurrentTsar();
var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70,
new Person("Vasili III of Russia", 28, 170, 60, null));
// Какие недостатки у такого подхода?
// Данный подход делает класс труднорасширяемым, ведь при добавлении новых полей в класс Person
// нужно переписывать и метод сравнения для всех этих полей, так же новые поля cможет добавить
// другой разработчик, который не знает о таком методе сравнения, что приведет к новым, неотловоенным ошибкам -
// новые поля не будут сравниваться.
// В моем решении (CheckCurrentTsar), такой ошибки не возникнет и класс Person сможет без ошибок расширяться
// при условии, что сравниваться будут все поля, кроме Id (так же и их Parent), так как код написан не перебиранием
// всех полей для сравнения, а сравнением объекта в целом с исключением его Id.
Assert.True(AreEqual(actualTsar, expectedTsar));
}

// Какие недостатки у такого подхода?
Assert.True(AreEqual(actualTsar, expectedTsar));
}
private bool AreEqual(Person? actual, Person? expected)
{
if (actual == expected) return true;
if (actual == null || expected == null) return false;
return
actual.Name == expected.Name
&& 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 строку - в чем тут потенциальная опасность?

}
}

private bool AreEqual(Person? actual, Person? expected)
{
if (actual == expected) return true;
if (actual == null || expected == null) return false;
return
actual.Name == expected.Name
&& actual.Age == expected.Age
&& actual.Height == expected.Height
&& actual.Weight == expected.Weight
&& AreEqual(actual.Parent, expected.Parent);
}
}
public class TsarRegistry
{
public static Person GetCurrentTsar()
{
return new Person(
"Ivan IV The Terrible", 54, 170, 70,
new Person("Vasili III of Russia", 28, 170, 60, null));
}
}

public class TsarRegistry
{
public static Person GetCurrentTsar()
{
return new Person(
"Ivan IV The Terrible", 54, 170, 70,
new Person("Vasili III of Russia", 28, 170, 60, null));
}
}
public class Person
{
public static int IdCounter = 0;
public int Age, Height, Weight;
public string Name;
public Person? Parent;
public int Id;

public class Person
{
public static int IdCounter = 0;
public int Age, Height, Weight;
public string Name;
public Person? Parent;
public int Id;

public Person(string name, int age, int height, int weight, Person? parent)
{
Id = IdCounter++;
Name = name;
Age = age;
Height = height;
Weight = weight;
Parent = parent;
}
}
public Person(string name, int age, int height, int weight, Person? parent)
{
Id = IdCounter++;
Name = name;
Age = age;
Height = height;
Weight = weight;
Parent = parent;
}
}
}