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