diff --git a/cs/HomeExercises/HomeExercises.csproj b/cs/HomeExercises/HomeExercises.csproj
deleted file mode 100644
index ede81aec..00000000
--- a/cs/HomeExercises/HomeExercises.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- netcoreapp3.1
- false
- HomeExercises
- ObjectComparison
- 8
- enable
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/cs/HomeExercises/HomeExercises.sln b/cs/HomeExercises/HomeExercises.sln
new file mode 100644
index 00000000..0795e45c
--- /dev/null
+++ b/cs/HomeExercises/HomeExercises.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HomeExercises", "HomeExercises\HomeExercises.csproj", "{217E7357-A6CA-4CBF-8C96-9F42B0EE4265}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Person.Tests", "Person.Tests\Person.Tests.csproj", "{CB57B3E5-40D3-46DC-8A72-4663207B0631}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NumberValidator.Tests", "NumberValidator.Tests\NumberValidator.Tests.csproj", "{28D5585A-51C7-4353-8F4F-F86887F0576E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {217E7357-A6CA-4CBF-8C96-9F42B0EE4265}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {217E7357-A6CA-4CBF-8C96-9F42B0EE4265}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {217E7357-A6CA-4CBF-8C96-9F42B0EE4265}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {217E7357-A6CA-4CBF-8C96-9F42B0EE4265}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CB57B3E5-40D3-46DC-8A72-4663207B0631}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CB57B3E5-40D3-46DC-8A72-4663207B0631}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CB57B3E5-40D3-46DC-8A72-4663207B0631}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CB57B3E5-40D3-46DC-8A72-4663207B0631}.Release|Any CPU.Build.0 = Release|Any CPU
+ {28D5585A-51C7-4353-8F4F-F86887F0576E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {28D5585A-51C7-4353-8F4F-F86887F0576E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {28D5585A-51C7-4353-8F4F-F86887F0576E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {28D5585A-51C7-4353-8F4F-F86887F0576E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5E766B1C-5638-4495-AC79-F7F4EC79629C}
+ EndGlobalSection
+EndGlobal
diff --git a/cs/HomeExercises/HomeExercises/HomeExercises.csproj b/cs/HomeExercises/HomeExercises/HomeExercises.csproj
new file mode 100644
index 00000000..dd1a785a
--- /dev/null
+++ b/cs/HomeExercises/HomeExercises/HomeExercises.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net6.0
+ false
+ HomeExercises
+ ObjectComparison
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cs/HomeExercises/HomeExercises/NumberValidator.cs b/cs/HomeExercises/HomeExercises/NumberValidator.cs
new file mode 100644
index 00000000..d2a43dc8
--- /dev/null
+++ b/cs/HomeExercises/HomeExercises/NumberValidator.cs
@@ -0,0 +1,48 @@
+using System.Text.RegularExpressions;
+
+namespace HomeExercises;
+
+public class NumberValidator
+{
+ private readonly Regex numberRegex;
+ private readonly bool onlyPositive;
+ private readonly int precision;
+ private readonly int scale;
+
+ public NumberValidator(int precision, int scale = 0, bool onlyPositive = false)
+ {
+ this.precision = precision;
+ this.scale = scale;
+ this.onlyPositive = onlyPositive;
+
+ if (precision <= 0)
+ throw new ArgumentException("precision must be a positive number");
+
+ if (scale < 0 || scale >= precision)
+ throw new ArgumentException("precision must be a non-negative number less or equal than precision");
+
+ numberRegex = new Regex(@"^([+-]?)(\d+)([.,](\d+))?$", RegexOptions.IgnoreCase);
+ }
+
+ public bool IsValidNumber(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ return false;
+
+ var match = numberRegex.Match(value);
+
+ if (!match.Success)
+ return false;
+
+ var intPart = match.Groups[1].Value.Length + match.Groups[2].Value.Length;
+ var fracPart = match.Groups[4].Value.Length;
+
+ if (intPart + fracPart > precision || fracPart > scale)
+ return false;
+
+ if (onlyPositive && match.Groups[1].Value == "-")
+ return false;
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/cs/HomeExercises/HomeExercises/Person.cs b/cs/HomeExercises/HomeExercises/Person.cs
new file mode 100644
index 00000000..dc320109
--- /dev/null
+++ b/cs/HomeExercises/HomeExercises/Person.cs
@@ -0,0 +1,20 @@
+namespace HomeExercises;
+
+public class Person
+{
+ public static int IdCounter;
+ public int Age, Height, Weight;
+ public int Id;
+ public string Name;
+ public Person? 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;
+ }
+}
\ No newline at end of file
diff --git a/cs/HomeExercises/README.md b/cs/HomeExercises/HomeExercises/README.md
similarity index 92%
rename from cs/HomeExercises/README.md
rename to cs/HomeExercises/HomeExercises/README.md
index 7c7a5851..74720085 100644
--- a/cs/HomeExercises/README.md
+++ b/cs/HomeExercises/HomeExercises/README.md
@@ -3,7 +3,7 @@
## Сравнение объектов
Изучите тест в классе ObjectComparison.
-Затем изучите [документацию FluentAssertions](http://fluentassertions.com/documentation.html).
+Затем изучите [документацию FluentAssertions](http://fluentassertions.com/documentation.html).
Перепишите тест с использованием наиболее подходящего метода FluentAssertions так чтобы:
@@ -17,7 +17,7 @@
Изучите код теста в классе NumberValidatorTests.
-Перепишите тест так, чтобы
+Перепишите тест так, чтобы
* найти и удалить повторяющиеся проверки,
* найти недостающие проверки,
diff --git a/cs/HomeExercises/HomeExercises/TsarRegistry.cs b/cs/HomeExercises/HomeExercises/TsarRegistry.cs
new file mode 100644
index 00000000..e335eaed
--- /dev/null
+++ b/cs/HomeExercises/HomeExercises/TsarRegistry.cs
@@ -0,0 +1,11 @@
+namespace HomeExercises;
+
+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));
+ }
+}
\ No newline at end of file
diff --git a/cs/HomeExercises/NumberValidator.Tests/NumberValidator.Tests.csproj b/cs/HomeExercises/NumberValidator.Tests/NumberValidator.Tests.csproj
new file mode 100644
index 00000000..1ac55590
--- /dev/null
+++ b/cs/HomeExercises/NumberValidator.Tests/NumberValidator.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cs/HomeExercises/NumberValidator.Tests/NumberValidatorTests.cs b/cs/HomeExercises/NumberValidator.Tests/NumberValidatorTests.cs
new file mode 100644
index 00000000..88b1ac44
--- /dev/null
+++ b/cs/HomeExercises/NumberValidator.Tests/NumberValidatorTests.cs
@@ -0,0 +1,73 @@
+using FluentAssertions;
+using NUnit.Framework;
+
+namespace NumberValidator.Tests;
+
+[TestFixture]
+public class NumberValidatorTests
+{
+ [TestCase(-5, 5, TestName = "Precision < 0")]
+ [TestCase(0, 5, TestName = "Precision = 0")]
+ [TestCase(5, -5, TestName = "Scale < 0")]
+ [TestCase(5, 5, TestName = "Precision = Scale")]
+ [TestCase(5, 10, TestName = "Precision < Scale")]
+ public void Constructor_Should_ThrowException_When_ArgumentsIncorrect(int precision, int scale)
+ {
+ var action = new Action(() => _ = new HomeExercises.NumberValidator(precision, scale));
+ action.Should().Throw();
+ }
+
+ [TestCase(10, 5, false, TestName = "Precision > 0, Scale >= 0, Precision > Scale")]
+ public void Constructor_ShouldNot_ThrowException_When_ArgumentsAreCorrect(int precision, int scale,
+ bool onlyPositive)
+ {
+ var action = new Action(() => _ = new HomeExercises.NumberValidator(precision, scale, onlyPositive));
+ action.Should().NotThrow();
+ }
+
+ [TestCase(15, TestName = "Only precision is given")]
+ public void Constructor_ShouldNot_ThrowException_When_OneArgumentGiven(int precision)
+ {
+ var action = new Action(() => _ = new HomeExercises.NumberValidator(precision));
+ action.Should().NotThrow();
+ }
+
+ [TestCase(1, 0, true, "5", TestName = "No fractional part")]
+ [TestCase(3, 0, true, "+99", TestName = "Sign and no fractional part")]
+ [TestCase(4, 2, false, "-1.25", TestName = "Negative number")]
+ public void IsValid_Should_ReturnTrue_When_CorrectValuesGiven(int precision, int scale,
+ bool onlyPositive, string value)
+ {
+ var validator = new HomeExercises.NumberValidator(precision, scale, onlyPositive);
+ validator.IsValidNumber(value).Should().BeTrue();
+ }
+
+ [TestCase(4, 2, true, "+5,33", TestName = "Works with dot as separator")]
+ [TestCase(4, 2, true, "+5.33", TestName = "Works with comma as separator")]
+ public void IsValid_Should_Work_When_DotOrCommaAreSeparators(int precision, int scale,
+ bool onlyPositive, string value)
+ {
+ var validator = new HomeExercises.NumberValidator(precision, scale, onlyPositive);
+ validator.IsValidNumber(value).Should().BeTrue();
+ }
+
+ [TestCase(3, 2, true, "00.00", TestName = "Actual precision < expected")]
+ [TestCase(3, 2, true, "+0.00",
+ TestName = "Sign was not taken into account when forming precision value")]
+ [TestCase(17, 2, true, "0.000", TestName = "Actual scale < expected")]
+ [TestCase(3, 2, true, "a.sd", TestName = "Letters instead of digits")]
+ [TestCase(3, 2, true, "-1.25", TestName = "Negative number when (onlyPositive = true)")]
+ [TestCase(10, 5, true, "+", TestName = "No number given")]
+ [TestCase(10, 5, true, "+ 5, 956", TestName = "Spaces are forbidden")]
+ [TestCase(10, 5, true, "45!34", TestName = "Incorrect separator")]
+ [TestCase(10, 5, true, "++3.45", TestName = "Two signs")]
+ [TestCase(10, 5, true, "2,,66", TestName = "Two separators")]
+ [TestCase(10, 5, true, "", TestName = "Empty string as number")]
+ [TestCase(10, 5, true, null, TestName = "Null instead of string")]
+ public void IsValid_Should_ReturnFalse_When_IncorrectValuesGiven(int precision, int scale,
+ bool onlyPositive, string value)
+ {
+ var validator = new HomeExercises.NumberValidator(precision, scale, onlyPositive);
+ validator.IsValidNumber(value).Should().BeFalse();
+ }
+}
\ No newline at end of file
diff --git a/cs/HomeExercises/NumberValidatorTests.cs b/cs/HomeExercises/NumberValidatorTests.cs
deleted file mode 100644
index a2878113..00000000
--- a/cs/HomeExercises/NumberValidatorTests.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System;
-using System.Text.RegularExpressions;
-using FluentAssertions;
-using NUnit.Framework;
-
-namespace HomeExercises
-{
- public class NumberValidatorTests
- {
- [Test]
- public void Test()
- {
- Assert.Throws(() => new NumberValidator(-1, 2, true));
- Assert.DoesNotThrow(() => new NumberValidator(1, 0, true));
- Assert.Throws(() => 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"));
- }
- }
-
- public class NumberValidator
- {
- private readonly Regex numberRegex;
- private readonly bool onlyPositive;
- private readonly int precision;
- private readonly int scale;
-
- public NumberValidator(int precision, int scale = 0, bool onlyPositive = false)
- {
- this.precision = precision;
- this.scale = scale;
- this.onlyPositive = onlyPositive;
- if (precision <= 0)
- throw new ArgumentException("precision must be a positive number");
- if (scale < 0 || scale >= precision)
- throw new ArgumentException("precision must be a non-negative number less or equal than precision");
- numberRegex = new Regex(@"^([+-]?)(\d+)([.,](\d+))?$", RegexOptions.IgnoreCase);
- }
-
- public bool IsValidNumber(string value)
- {
- // Проверяем соответствие входного значения формату N(m,k), в соответствии с правилом,
- // описанным в Формате описи документов, направляемых в налоговый орган в электронном виде по телекоммуникационным каналам связи:
- // Формат числового значения указывается в виде N(m.к), где m – максимальное количество знаков в числе, включая знак (для отрицательного числа),
- // целую и дробную часть числа без разделяющей десятичной точки, k – максимальное число знаков дробной части числа.
- // Если число знаков дробной части числа равно 0 (т.е. число целое), то формат числового значения имеет вид N(m).
-
- if (string.IsNullOrEmpty(value))
- return false;
-
- var match = numberRegex.Match(value);
- if (!match.Success)
- return false;
-
- // Знак и целая часть
- var intPart = match.Groups[1].Value.Length + match.Groups[2].Value.Length;
- // Дробная часть
- var fracPart = match.Groups[4].Value.Length;
-
- if (intPart + fracPart > precision || fracPart > scale)
- return false;
-
- if (onlyPositive && match.Groups[1].Value == "-")
- return false;
- return true;
- }
- }
-}
\ No newline at end of file
diff --git a/cs/HomeExercises/ObjectComparison.cs b/cs/HomeExercises/ObjectComparison.cs
deleted file mode 100644
index 44d9aed4..00000000
--- a/cs/HomeExercises/ObjectComparison.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using FluentAssertions;
-using NUnit.Framework;
-
-namespace HomeExercises
-{
- 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));
-
- // Перепишите код на использование 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);
-
- 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));
-
- // Какие недостатки у такого подхода?
- 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);
- }
- }
-
- 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 Person(string name, int age, int height, int weight, Person? parent)
- {
- Id = IdCounter++;
- Name = name;
- Age = age;
- Height = height;
- Weight = weight;
- Parent = parent;
- }
- }
-}
\ No newline at end of file
diff --git a/cs/HomeExercises/Person.Tests/Person.Tests.csproj b/cs/HomeExercises/Person.Tests/Person.Tests.csproj
new file mode 100644
index 00000000..7ebede4c
--- /dev/null
+++ b/cs/HomeExercises/Person.Tests/Person.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net6.0
+ enable
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cs/HomeExercises/Person.Tests/PersonTests.cs b/cs/HomeExercises/Person.Tests/PersonTests.cs
new file mode 100644
index 00000000..49c651f2
--- /dev/null
+++ b/cs/HomeExercises/Person.Tests/PersonTests.cs
@@ -0,0 +1,45 @@
+using FluentAssertions;
+using HomeExercises;
+using NUnit.Framework;
+
+namespace Person.Tests;
+
+[TestFixture]
+public class PersonTests
+{
+ [Test]
+ public void Persons_ShouldBeEqual_When_HaveSamePropertiesAndParents()
+ {
+ var actualTsar = TsarRegistry.GetCurrentTsar();
+ var expectedTsar = new HomeExercises.Person("Ivan IV The Terrible", 54, 170, 70,
+ new HomeExercises.Person("Vasili III of Russia", 28, 170, 60, null));
+
+ actualTsar.Should().BeEquivalentTo(expectedTsar,
+ config => config.Excluding(ctx => ctx.SelectedMemberInfo.Name == "Id"));
+ }
+
+ // Недостатки этого метода сравнения:
+ // 1. AreEqual(...) сильно связан со структурой класса Person:
+ // любая модификация этого класса может потребовать изменения проверяющего метода.
+ // 2. AreEqual(...) включает в себя слишком много проверок. Из-за этого мы получим
+ // неинформативное сообщение об ошибке в случае разности проверяемых объектов.
+ // 3. В методе типа AreEqual(...) легко забыть о каком-либо свойстве, т.е. допустить ошибку в сравнении.
+ // 4. Если у проверяемого объекта много свойств, то AreEqual(...) тяжело читать.
+ // 5. Возможно переполнение стека из-за рекурсивных вызовов AreEqual(...) внутри самого себя.
+ // 6. Для любого другого класса придётся писать новую версию метода AreEqual(...).
+ private bool AreEqual(HomeExercises.Person? actual, HomeExercises.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);
+ }
+}
\ No newline at end of file