diff --git a/cs/HomeExercises/HomeExercises.csproj b/cs/HomeExercises/HomeExercises.csproj index ede81aec..14244f22 100644 --- a/cs/HomeExercises/HomeExercises.csproj +++ b/cs/HomeExercises/HomeExercises.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0-windows false HomeExercises ObjectComparison diff --git a/cs/HomeExercises/NumberValidator/NumberValidator.cs b/cs/HomeExercises/NumberValidator/NumberValidator.cs new file mode 100644 index 00000000..f6904677 --- /dev/null +++ b/cs/HomeExercises/NumberValidator/NumberValidator.cs @@ -0,0 +1,53 @@ +using System; +using System.Text.RegularExpressions; + +namespace HomeExercises.NumberValidator +{ + 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/NumberValidator/NumberValidatorTests.cs b/cs/HomeExercises/NumberValidator/NumberValidatorTests.cs new file mode 100644 index 00000000..7e745a4c --- /dev/null +++ b/cs/HomeExercises/NumberValidator/NumberValidatorTests.cs @@ -0,0 +1,65 @@ +using System; +using FluentAssertions; +using NUnit.Framework; + +namespace HomeExercises.NumberValidator +{ + public class NumberValidatorTests + { + [TestCase(0, 2, TestName = "precision is zero")] + [TestCase(-1, 2, TestName = "precision is negative")] + [TestCase(1, -1, TestName = "scale is negative")] + [TestCase(1, 2, TestName = "scale is greater than precision")] + [TestCase(1, 1, TestName = "scale is equals precision")] + public void Constructor_Fails_OnIncorrectArguments(int precision, int scale) + { + Action a = () => { new NumberValidator(precision, scale); }; + a.Should().Throw(); + } + + [Test] + public void Constructor_Success_WithOneArgument() + { + Action a = () => { new NumberValidator(1); }; + a.Should().NotThrow(); + } + + [TestCase(3, 2, false, null, TestName = "value is null")] + [TestCase(3, 2, false, "", TestName = "value is empty")] + [TestCase(3, 2, false, " ", TestName = "value is space")] + [TestCase(3, 2, false, "+1..23", TestName = "value contains two separators")] + [TestCase(3, 2, false, "++0", TestName = "value contains two signs")] + [TestCase(3, 2, false, "1.2a", TestName = "value contains letters")] + [TestCase(3, 2, false, "+", TestName = "value only contains sign")] + [TestCase(3, 2, false, "0?0", TestName = "value separated by other symbol than dot or comma")] + [TestCase(3, 2, false, " 0", TestName = "value contains spaces before number")] + [TestCase(3, 2, false, "0 ", TestName = "value contains spaces after number")] + [TestCase(3, 2, false, "0.", TestName = "value hasn't contains numbers after separator")] + [TestCase(3, 2, false, ".0", TestName = "value hasn't contains numbers before separator")] + [TestCase(17, 2, false, "0.000", TestName = "value's fraction part length is greater than scale")] + [TestCase(5, 2, true, "-0.00", TestName = "negative sign when onlyPositive is true")] + [TestCase(3, 2, false, "+0.00", TestName = "intPart and fractPart together is greater than precision")] + public void IsValidNumber_ReturnsFalse_OnIncorrectArguments(int precision, int scale, bool onlyPositive, string value) + { + new NumberValidator(precision, scale, onlyPositive) + .IsValidNumber(value) + .Should() + .BeFalse(); + } + + [TestCase(17, 2, true, "0", TestName = "value without sign")] + [TestCase(17, 2, true, "+0", TestName = "value with positive sign")] + [TestCase(17, 2, false, "-0", TestName = "value with negative sign")] + [TestCase(17, 2, true, "0.0", TestName = "value with period as delimiter")] + [TestCase(17, 2, true, "0,0", TestName = "value with comma as delimiter")] + [TestCase(17, 2, true, "+0,0", TestName = "value with sign and delimiter")] + [TestCase(40, 20, true, "1234567890", TestName = "value with different numbers")] + public void IsValidNumber_ReturnsTrue_OnCorrectArguments(int precision, int scale, bool onlyPositive, string value) + { + new NumberValidator(precision, scale, onlyPositive) + .IsValidNumber(value) + .Should() + .BeTrue(); + } + } +} \ 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.cs b/cs/HomeExercises/Person.cs new file mode 100644 index 00000000..d4922662 --- /dev/null +++ b/cs/HomeExercises/Person.cs @@ -0,0 +1,21 @@ +namespace HomeExercises +{ + 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/TsarRegistry/TsarRegistry.cs b/cs/HomeExercises/TsarRegistry/TsarRegistry.cs new file mode 100644 index 00000000..68e62c3a --- /dev/null +++ b/cs/HomeExercises/TsarRegistry/TsarRegistry.cs @@ -0,0 +1,11 @@ +namespace HomeExercises.TsarRegistry +{ + 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/TsarRegistry/TsarRegistryTests.cs b/cs/HomeExercises/TsarRegistry/TsarRegistryTests.cs new file mode 100644 index 00000000..6214e12e --- /dev/null +++ b/cs/HomeExercises/TsarRegistry/TsarRegistryTests.cs @@ -0,0 +1,59 @@ +using FluentAssertions; +using NUnit.Framework; + +namespace HomeExercises.TsarRegistry +{ + public class TsarRegistryTests + { + [Test] + [Description("Проверка текущего царя")] + public void GetCurrentTsar_ReturnsCorrectTsar() + { + 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)); + + actualTsar + .Should() + .BeEquivalentTo(expectedTsar, options => options + .Excluding(memberInfo => memberInfo.SelectedMemberInfo.DeclaringType == typeof(Person) + && memberInfo.SelectedMemberInfo.Name == nameof(Person.Id))); + } + + [Test] + [Description("Альтернативное решение. Какие у него недостатки?")] + /* 1. Непонятно из названия, что тест проверяет и какого результата ожидает. + * 2. При использовании такого Assert, при падении теста мы узнаем лишь, что мы получили False, + * без дополнительной информации об отличающихся полях и т.д. + * 3. При изменении класса Person нам всегда придётся соответственно менять и локальный метод + * AreEqual для класса Person. + * 4. Если каким-то образом произойдёт бесконечная рекурсия внутри класса Person, то данный способ привдёт к + * бесконечному проходу по нему. Проверка через FluentAssertions ограничена глубиной + * в 10 вхождений и это значение кастомизируемо. + * 5. При добавлении новых полей в класс Person, которые мы хотим включать в этот Equals - метод может + * сильно разрастись и его читаемость сильно снизится. + */ + 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); + } + } +} \ No newline at end of file