Skip to content
115 changes: 95 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,109 @@
using System;
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))]

{
[Test]
public void Test()
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

{
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, 2, true);

Assert.Throws<ArgumentException>(testDelegate);
}
}

[Test]
public void NumberValidatorCtor_WhenPassNegativeScale_ShouldThrowsArgumentException()
{
TestDelegate testDelegate = () => new NumberValidator(1, -2);

Assert.Throws<ArgumentException>(testDelegate);
}

[Test]
public void NumberValidatorCtor_WhenPassValidArguments_ShouldDoesNotThrows()
{
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.

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

}

[Test]
public void NumberValidatorCtor_WhenPrecisionIsEqualToTheScale_ShouldThrowsArgumentException()
{
TestDelegate testDelegate = () => new NumberValidator(2, 2, true);

Assert.Throws<ArgumentException>(testDelegate);
}

public void IsValidNumberTest(int precision, int scale, bool onlyPositive,
string number, bool expectedResult)
{
var validator = new NumberValidator(precision, scale, onlyPositive);

var actualResult = validator.IsValidNumber(number);

Assert.AreEqual(expectedResult, actualResult);
}

[Test]
[TestOf(nameof(NumberValidator.IsValidNumber))]
public void WhenFractionalPartIsMissing_ShouldReturnTrue()
{
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.

}

[Test]
[TestOf(nameof(NumberValidator.IsValidNumber))]
public void WhenLettersInsteadOfNumber_ShouldReturnFalse()
{
IsValidNumberTest(3, 2, true, "a.sd", false);
}

[Test]
[TestOf(nameof(NumberValidator.IsValidNumber))]
public void WhenSymbolsInsteadOfNumber_ShouldReturnFalse()
{
IsValidNumberTest(3, 2, true, "2.!", false);
}

[Test]
[TestOf(nameof(NumberValidator.IsValidNumber))]
public void WhenNumberIsNull_ShouldReturnFalse()
{
IsValidNumberTest(17,2,true, null!, false);
}

[Test]
[TestOf(nameof(NumberValidator.IsValidNumber))]
public void WhenPassNumberIsEmpty_ShouldReturnFalse()
{
IsValidNumberTest(3,2,true,"", false);
}

[Test]
[TestOf(nameof(NumberValidator.IsValidNumber))]
public void WhenIntPartWithNegativeSignMoreThanPrecision_ShouldReturnFalse()
{
IsValidNumberTest(3,2,true,"-0.00", false);
}

[Test]
[TestOf(nameof(NumberValidator.IsValidNumber))]
public void WhenIntPartWithPositiveSignMoreThanPrecision_ShouldReturnFalse()
{
IsValidNumberTest(3,2,true,"+1.23", false);
}

[Test]
[TestOf(nameof(NumberValidator.IsValidNumber))]
public void WhenFractionalPartMoreThanScale_ShouldReturnFalse()
{
IsValidNumberTest(17,2,true, "0.000", false);
}
}

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;
}
}
}