diff --git a/cs/HomeExercises/NumberValidator.cs b/cs/HomeExercises/NumberValidator.cs new file mode 100644 index 00000000..ba8f4771 --- /dev/null +++ b/cs/HomeExercises/NumberValidator.cs @@ -0,0 +1,53 @@ +using System; +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) + { + // Проверяем соответствие входного значения формату 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/NumberValidatorTests.cs b/cs/HomeExercises/NumberValidatorTests.cs index a2878113..668e5489 100644 --- a/cs/HomeExercises/NumberValidatorTests.cs +++ b/cs/HomeExercises/NumberValidatorTests.cs @@ -1,5 +1,4 @@ using System; -using System.Text.RegularExpressions; using FluentAssertions; using NUnit.Framework; @@ -7,74 +6,52 @@ namespace HomeExercises { public class NumberValidatorTests { - [Test] - public void Test() + [Test, Category("Constructor")] + public void ValidConstructorParameters_WithNoException() { - 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")); + Assert.DoesNotThrow(() => new NumberValidator(2,1, true)); } - } - - 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) + + [TestCase(-11, 2, false, TestName = "precision is negative", Category = "Constructor")] + [TestCase(8, -13, false, TestName = "scale is negative", Category = "Constructor")] + [TestCase(7, 8, false, TestName = "precision is less than scale", Category = "Constructor")] + [TestCase(0, 0, false, TestName = "precision is zero", Category = "Constructor")] + public void InvalidConstructorParameters_ThrowsArgumentException(int precision, int scale, bool onlyPositive) { - 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); + Assert.Throws(() => new NumberValidator(precision, scale, onlyPositive)); } - public bool IsValidNumber(string value) + [TestCase(null!, TestName = "Null input", Category = "IsValidNumber")] + [TestCase("", TestName = "Empty string", Category = "IsValidNumber")] + [TestCase(" ", TestName = "Whitespace string", Category = "IsValidNumber")] + [TestCase("%;?^", TestName = "Other symbols", Category = "IsValidNumber")] + [TestCase("ddd", TestName = "Non-numeric string", Category = "IsValidNumber")] + public void isValidNumber_ShouldBeFalse_WhenNumberIsNotCorrect(string number) { - // Проверяем соответствие входного значения формату 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; + var validator = new NumberValidator(8, 6, true); + validator.IsValidNumber(number).Should() + .BeFalse($"For {number}, IsValidNumber should return false as it's invalid data."); + } + + [TestCase("13.5", true, Category = "IsValidNumber")] + [TestCase("88", true, Category = "IsValidNumber")] + [TestCase("7.00", true, Category = "IsValidNumber")] + [TestCase("198.2", true, Category = "IsValidNumber")] + [TestCase("0", true, Category = "IsValidNumber")] + [TestCase("+2.0", true, Category = "IsValidNumber")] + public void isValidNumber_ShouldBeTrue_WhenNumberIsValid(string number, bool result) + { + var validator = new NumberValidator(5, 2, true); + validator.IsValidNumber(number).Should().Be(result); + } + [TestCase("-8", false)] + [TestCase("-0.8", false)] + [TestCase("+4.0", true)] + [TestCase("+0.00", true)] + public void isValidNumber_ShouldBeTrue_WhenNumberWithSigns(string number, bool result) + { + var validator = new NumberValidator(7, 3, true); + validator.IsValidNumber(number).Should().Be(result); } } } \ No newline at end of file diff --git a/cs/HomeExercises/ObjectComparison.cs b/cs/HomeExercises/ObjectComparison.cs index 44d9aed4..5decb0ff 100644 --- a/cs/HomeExercises/ObjectComparison.cs +++ b/cs/HomeExercises/ObjectComparison.cs @@ -14,17 +14,11 @@ public void CheckCurrentTsar() 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(x => x.Id) + .Excluding(y => y.Parent!.Id)); - // Перепишите код на использование 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] @@ -37,6 +31,15 @@ public void CheckCurrentTsar_WithCustomEquality() // Какие недостатки у такого подхода? Assert.True(AreEqual(actualTsar, expectedTsar)); + + + /*1. При любых изменениях в структуре класса Person, мне придется это менять в методе AreEqual + 2. Если тест не пройдет, он мне не скажет какие атрибуты или свойства не эквивалентны + 3. Код становится более сложный и менее читаемый. Возможно такой подход необходим если стандартных + возможностей сравнения недостаточно и необходимы более глубокие настройки. Но, не в этом случае. + 4. По названию теста не понять область моего тестирования */ + + } private bool AreEqual(Person? actual, Person? expected)