From a6ccffc82e6e1a2fbbea57d4281408582b002a47 Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Sun, 26 Nov 2023 14:44:45 +0500 Subject: [PATCH 1/5] ObjectComparison.CheckCurrentTsar test rewritten to use FluentAssertions --- cs/HomeExercises/ObjectComparison.cs | 46 +++++++++++++++++----------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/cs/HomeExercises/ObjectComparison.cs b/cs/HomeExercises/ObjectComparison.cs index 44d9aed4..244283d9 100644 --- a/cs/HomeExercises/ObjectComparison.cs +++ b/cs/HomeExercises/ObjectComparison.cs @@ -14,17 +14,17 @@ public void CheckCurrentTsar() 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); + + /* + * Чем это решение лучше решения в CheckCurrentTsar_WithCustomEquality: + * 1. Это решение не нужно будет переписывать при добавлении/удалении в класс/из класса Person полей. + * 2. В этом решении наглядно видно, какое именно поле не учитывается при сравнении объектов. + * 3. При падении теста мы увидим, какие именно поля объектов не прошли проверку на равенство. + */ + actualTsar + .Should() + .BeEquivalentTo(expectedTsar, options => options + .Excluding(info => info.SelectedMemberInfo.Name.Equals(nameof(Person.Id)))); } [Test] @@ -36,10 +36,18 @@ public void CheckCurrentTsar_WithCustomEquality() new Person("Vasili III of Russia", 28, 170, 60, null)); // Какие недостатки у такого подхода? + + /* + * 1. Если мы решим изменить класс Person (например, добавить или удалить какие-то поля), + * то тогда придется переписывать метод AreEqual + * 2. Если тест не пройдет, то мы не увидим, какие именно поля у двух объектов не совпали. + * Нам просто выдаст сообщение 'Expected: True, But was: False' + */ + Assert.True(AreEqual(actualTsar, expectedTsar)); } - private bool AreEqual(Person? actual, Person? expected) + private static bool AreEqual(Person? actual, Person? expected) { if (actual == expected) return true; if (actual == null || expected == null) return false; @@ -52,7 +60,7 @@ private bool AreEqual(Person? actual, Person? expected) } } - public class TsarRegistry + public abstract class TsarRegistry { public static Person GetCurrentTsar() { @@ -64,15 +72,17 @@ public static Person GetCurrentTsar() public class Person { - public static int IdCounter = 0; - public int Age, Height, Weight; - public string Name; - public Person? Parent; + private static int _idCounter; + public readonly int Age; + public readonly int Height; + public readonly int Weight; + public readonly string Name; + public readonly Person? Parent; public int Id; public Person(string name, int age, int height, int weight, Person? parent) { - Id = IdCounter++; + Id = _idCounter++; Name = name; Age = age; Height = height; From e521a3c4891afacbc1119f9f7483c9b057a4f190 Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Sun, 26 Nov 2023 16:34:30 +0500 Subject: [PATCH 2/5] NumberValidatorTests refactored --- cs/HomeExercises/NumberValidatorTests.cs | 123 ++++++++++++++++++----- 1 file changed, 96 insertions(+), 27 deletions(-) diff --git a/cs/HomeExercises/NumberValidatorTests.cs b/cs/HomeExercises/NumberValidatorTests.cs index a2878113..db041be3 100644 --- a/cs/HomeExercises/NumberValidatorTests.cs +++ b/cs/HomeExercises/NumberValidatorTests.cs @@ -8,45 +8,114 @@ namespace HomeExercises public class NumberValidatorTests { [Test] - public void Test() + public void Constructor_ThrowsArgumentException_WhenPrecisionIsNegative() + { + Assert.Throws(() => new NumberValidator(-1, 0, true)); + } + + [Test] + public void Constructor_ThrowsArgumentException_WhenScaleIsNegative() + { + Assert.Throws(() => new NumberValidator(1, -1, true)); + } + + [Test] + public void Constructor_ThrowsArgumentException_WhenScaleIsGreaterThanOrEqualToPrecision() + { + Assert.Throws(() => new NumberValidator(1, 2, true)); + } + + [Test] + public void Constructor_SuccessfullyCreatesObject_OnCorrectInput() { - 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)); + } + + [TestCase(null)] + [TestCase(" ")] + [TestCase(" ")] + [TestCase("")] + [TestCase("\n")] + public void IsValidNumber_ReturnsFalse_WhenValueIsNullOrEmpty(string value) + { + var numberValidator = new NumberValidator(1, 0, true); + + numberValidator.IsValidNumber(value).Should().BeFalse(); + } + + [TestCase(".")] + [TestCase("a.sd")] + [TestCase("+-5")] + [TestCase("1..3")] + [TestCase("1.")] + [TestCase(".3")] + public void IsValidNumber_ReturnsFalse_WhenValueIsNaN(string value) + { + var numberValidator = new NumberValidator(2, 1, true); + + numberValidator.IsValidNumber(value).Should().BeFalse(); + } + + [TestCase("00.00", 3, 2)] + [TestCase("+1.00", 3, 2)] + [TestCase("-1.00", 3, 2)] + [TestCase("-1", 1, 0)] + public void IsValidNumber_ReturnsFalse_WhenIntAndFracPartsAreGreaterThanPrecision(string value, int precision, int scale) + { + var numberValidator = new NumberValidator(precision, scale); + + numberValidator.IsValidNumber(value).Should().BeFalse(); + } + + [TestCase("0.00", 3, 1)] + [TestCase("-123.12", 6, 0)] + public void IsValidNumber_ReturnsFalse_WhenFracPartIsGreaterThanScale(string value, int precision, int scale) + { + var numberValidator = new NumberValidator(precision, scale); + + numberValidator.IsValidNumber(value).Should().BeFalse(); + } + + [TestCase("-1.2", 3, 1)] + [TestCase("-0.00", 4, 2)] + public void IsValidNumber_ReturnsFalse_WhenValueIsNegativeButOnlyPositiveIsTrue(string value, int precision, int scale) + { + var numberValidator = new NumberValidator(precision, scale, true); + + numberValidator.IsValidNumber(value).Should().BeFalse(); + } + + [TestCase("0.0", 17, 2, true)] + [TestCase("0.0", 2, 1, false)] + [TestCase("+1.23", 4, 2, true)] + [TestCase("-1.23", 4, 2, false)] + [TestCase("0,1", 2, 1, true)] + [TestCase("0", 1, 0, true)] + public void IsValidNumber_ReturnsTrue_OnCorrectInputData(string value, int precision, int scale, bool onlyPositive) + { + var numberValidator = new NumberValidator(precision, scale, onlyPositive); - 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")); + numberValidator.IsValidNumber(value).Should().BeTrue(); } } public class NumberValidator { - private readonly Regex numberRegex; - private readonly bool onlyPositive; - private readonly int precision; - private readonly int scale; + 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; + _precision = precision; + _scale = scale; + _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); + _numberRegex = new Regex(@"^([+-]?)(\d+)([.,](\d+))?$", RegexOptions.IgnoreCase); } public bool IsValidNumber(string value) @@ -60,7 +129,7 @@ public bool IsValidNumber(string value) if (string.IsNullOrEmpty(value)) return false; - var match = numberRegex.Match(value); + var match = _numberRegex.Match(value); if (!match.Success) return false; @@ -69,10 +138,10 @@ public bool IsValidNumber(string value) // Дробная часть var fracPart = match.Groups[4].Value.Length; - if (intPart + fracPart > precision || fracPart > scale) + if (intPart + fracPart > _precision || fracPart > _scale) return false; - if (onlyPositive && match.Groups[1].Value == "-") + if (_onlyPositive && match.Groups[1].Value == "-") return false; return true; } From f9d2b34726e4c16c05a624197d5569a30e8b737d Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Tue, 28 Nov 2023 22:41:48 +0500 Subject: [PATCH 3/5] refactor --- cs/HomeExercises/HomeExercises.csproj | 2 +- cs/HomeExercises/NumberValidator.cs | 52 ++++++ cs/HomeExercises/NumberValidatorTests.cs | 149 ------------------ cs/HomeExercises/ObjectComparison.cs | 67 +------- cs/HomeExercisesTests/GlobalUsings.cs | 1 + .../HomeExercisesTests.csproj | 24 +++ cs/HomeExercisesTests/NumberValidatorTests.cs | 92 +++++++++++ .../ObjectComparisonTests.cs | 66 ++++++++ cs/testing.sln | 6 + 9 files changed, 246 insertions(+), 213 deletions(-) create mode 100644 cs/HomeExercises/NumberValidator.cs delete mode 100644 cs/HomeExercises/NumberValidatorTests.cs create mode 100644 cs/HomeExercisesTests/GlobalUsings.cs create mode 100644 cs/HomeExercisesTests/HomeExercisesTests.csproj create mode 100644 cs/HomeExercisesTests/NumberValidatorTests.cs create mode 100644 cs/HomeExercisesTests/ObjectComparisonTests.cs diff --git a/cs/HomeExercises/HomeExercises.csproj b/cs/HomeExercises/HomeExercises.csproj index ede81aec..9d989cbc 100644 --- a/cs/HomeExercises/HomeExercises.csproj +++ b/cs/HomeExercises/HomeExercises.csproj @@ -1,12 +1,12 @@  - netcoreapp3.1 false HomeExercises ObjectComparison 8 enable + net7.0 diff --git a/cs/HomeExercises/NumberValidator.cs b/cs/HomeExercises/NumberValidator.cs new file mode 100644 index 00000000..cf3c6484 --- /dev/null +++ b/cs/HomeExercises/NumberValidator.cs @@ -0,0 +1,52 @@ +using System; +using System.Text.RegularExpressions; + +namespace HomeExercises +{ + public class NumberValidator + { + private static readonly Regex NumberRegex = new Regex(@"^([+-]?)(\d+)([.,](\d+))?$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + 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("scale must be a non-negative number less than precision"); + } + + 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 deleted file mode 100644 index db041be3..00000000 --- a/cs/HomeExercises/NumberValidatorTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using FluentAssertions; -using NUnit.Framework; - -namespace HomeExercises -{ - public class NumberValidatorTests - { - [Test] - public void Constructor_ThrowsArgumentException_WhenPrecisionIsNegative() - { - Assert.Throws(() => new NumberValidator(-1, 0, true)); - } - - [Test] - public void Constructor_ThrowsArgumentException_WhenScaleIsNegative() - { - Assert.Throws(() => new NumberValidator(1, -1, true)); - } - - [Test] - public void Constructor_ThrowsArgumentException_WhenScaleIsGreaterThanOrEqualToPrecision() - { - Assert.Throws(() => new NumberValidator(1, 2, true)); - } - - [Test] - public void Constructor_SuccessfullyCreatesObject_OnCorrectInput() - { - Assert.DoesNotThrow(() => new NumberValidator(1, 0, true)); - } - - [TestCase(null)] - [TestCase(" ")] - [TestCase(" ")] - [TestCase("")] - [TestCase("\n")] - public void IsValidNumber_ReturnsFalse_WhenValueIsNullOrEmpty(string value) - { - var numberValidator = new NumberValidator(1, 0, true); - - numberValidator.IsValidNumber(value).Should().BeFalse(); - } - - [TestCase(".")] - [TestCase("a.sd")] - [TestCase("+-5")] - [TestCase("1..3")] - [TestCase("1.")] - [TestCase(".3")] - public void IsValidNumber_ReturnsFalse_WhenValueIsNaN(string value) - { - var numberValidator = new NumberValidator(2, 1, true); - - numberValidator.IsValidNumber(value).Should().BeFalse(); - } - - [TestCase("00.00", 3, 2)] - [TestCase("+1.00", 3, 2)] - [TestCase("-1.00", 3, 2)] - [TestCase("-1", 1, 0)] - public void IsValidNumber_ReturnsFalse_WhenIntAndFracPartsAreGreaterThanPrecision(string value, int precision, int scale) - { - var numberValidator = new NumberValidator(precision, scale); - - numberValidator.IsValidNumber(value).Should().BeFalse(); - } - - [TestCase("0.00", 3, 1)] - [TestCase("-123.12", 6, 0)] - public void IsValidNumber_ReturnsFalse_WhenFracPartIsGreaterThanScale(string value, int precision, int scale) - { - var numberValidator = new NumberValidator(precision, scale); - - numberValidator.IsValidNumber(value).Should().BeFalse(); - } - - [TestCase("-1.2", 3, 1)] - [TestCase("-0.00", 4, 2)] - public void IsValidNumber_ReturnsFalse_WhenValueIsNegativeButOnlyPositiveIsTrue(string value, int precision, int scale) - { - var numberValidator = new NumberValidator(precision, scale, true); - - numberValidator.IsValidNumber(value).Should().BeFalse(); - } - - [TestCase("0.0", 17, 2, true)] - [TestCase("0.0", 2, 1, false)] - [TestCase("+1.23", 4, 2, true)] - [TestCase("-1.23", 4, 2, false)] - [TestCase("0,1", 2, 1, true)] - [TestCase("0", 1, 0, true)] - public void IsValidNumber_ReturnsTrue_OnCorrectInputData(string value, int precision, int scale, bool onlyPositive) - { - var numberValidator = new NumberValidator(precision, scale, onlyPositive); - - numberValidator.IsValidNumber(value).Should().BeTrue(); - } - } - - 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) - { - _precision = precision; - _scale = scale; - _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 index 244283d9..15c45c41 100644 --- a/cs/HomeExercises/ObjectComparison.cs +++ b/cs/HomeExercises/ObjectComparison.cs @@ -1,66 +1,6 @@ -using FluentAssertions; -using NUnit.Framework; - -namespace HomeExercises +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)); - - /* - * Чем это решение лучше решения в CheckCurrentTsar_WithCustomEquality: - * 1. Это решение не нужно будет переписывать при добавлении/удалении в класс/из класса Person полей. - * 2. В этом решении наглядно видно, какое именно поле не учитывается при сравнении объектов. - * 3. При падении теста мы увидим, какие именно поля объектов не прошли проверку на равенство. - */ - actualTsar - .Should() - .BeEquivalentTo(expectedTsar, options => options - .Excluding(info => info.SelectedMemberInfo.Name.Equals(nameof(Person.Id)))); - } - - [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)); - - // Какие недостатки у такого подхода? - - /* - * 1. Если мы решим изменить класс Person (например, добавить или удалить какие-то поля), - * то тогда придется переписывать метод AreEqual - * 2. Если тест не пройдет, то мы не увидим, какие именно поля у двух объектов не совпали. - * Нам просто выдаст сообщение 'Expected: True, But was: False' - */ - - Assert.True(AreEqual(actualTsar, expectedTsar)); - } - - private static 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 abstract class TsarRegistry + public static class TsarRegistry { public static Person GetCurrentTsar() { @@ -77,9 +17,10 @@ public class Person public readonly int Height; public readonly int Weight; public readonly string Name; - public readonly Person? Parent; + public Person? Parent; public int Id; + public Person(string name, int age, int height, int weight, Person? parent) { Id = _idCounter++; diff --git a/cs/HomeExercisesTests/GlobalUsings.cs b/cs/HomeExercisesTests/GlobalUsings.cs new file mode 100644 index 00000000..cefced49 --- /dev/null +++ b/cs/HomeExercisesTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/cs/HomeExercisesTests/HomeExercisesTests.csproj b/cs/HomeExercisesTests/HomeExercisesTests.csproj new file mode 100644 index 00000000..a24acaec --- /dev/null +++ b/cs/HomeExercisesTests/HomeExercisesTests.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/cs/HomeExercisesTests/NumberValidatorTests.cs b/cs/HomeExercisesTests/NumberValidatorTests.cs new file mode 100644 index 00000000..4d61b00c --- /dev/null +++ b/cs/HomeExercisesTests/NumberValidatorTests.cs @@ -0,0 +1,92 @@ +using FluentAssertions; +using HomeExercises; + +namespace HomeExercisesTests +{ + public class NumberValidatorTests + { + [TestCase(-1, 0, Description = "Precision should be a positive number")] + [TestCase(1, -1, Description = "Scale must be a non-negative number")] + [TestCase(1, 2, Description = "Scale must be less than precision")] + public void Constructor_ThrowsArgumentException_OnIncorrectInput(int precision, int scale) + { + Assert.Throws(() => new NumberValidator(precision, scale, true)); + } + + [Test] + public void Constructor_SuccessfullyCreatesObject_OnCorrectInput() + { + Assert.DoesNotThrow(() => new NumberValidator(1, 0, true)); + } + + [TestCase(null)] + [TestCase(" ")] + [TestCase(" ")] + [TestCase("")] + [TestCase("\n")] + public void IsValidNumber_ReturnsFalse_WhenValueIsNullOrEmpty(string value) + { + var numberValidator = new NumberValidator(1, 0, true); + + numberValidator.IsValidNumber(value).Should().BeFalse(); + } + + [TestCase(".")] + [TestCase("a.sd")] + [TestCase("+-5")] + [TestCase("1..3")] + [TestCase("1.")] + [TestCase(".3")] + public void IsValidNumber_ReturnsFalse_WhenValueIsNaN(string value) + { + var numberValidator = new NumberValidator(2, 1, true); + + numberValidator.IsValidNumber(value).Should().BeFalse(); + } + + [TestCase("00.00", 3, 2)] + [TestCase("+1.00", 3, 2)] + [TestCase("-1.00", 3, 2)] + [TestCase("-1", 1, 0)] + public void IsValidNumber_ReturnsFalse_WhenIntAndFracPartsAreGreaterThanPrecision(string value, int precision, + int scale) + { + var numberValidator = new NumberValidator(precision, scale); + + numberValidator.IsValidNumber(value).Should().BeFalse(); + } + + [TestCase("0.00", 3, 1)] + [TestCase("-123.12", 6, 0)] + public void IsValidNumber_ReturnsFalse_WhenFracPartIsGreaterThanScale(string value, int precision, int scale) + { + var numberValidator = new NumberValidator(precision, scale); + + numberValidator.IsValidNumber(value).Should().BeFalse(); + } + + [TestCase("-1.2", 3, 1)] + [TestCase("-0.00", 4, 2)] + public void IsValidNumber_ReturnsFalse_WhenValueIsNegativeButOnlyPositiveIsTrue(string value, int precision, + int scale) + { + var numberValidator = new NumberValidator(precision, scale, true); + + numberValidator.IsValidNumber(value).Should().BeFalse(); + } + + [TestCase("0.0", 17, 2, true)] + [TestCase("0.0", 2, 1, false)] + [TestCase("+1.23", 4, 2, true)] + [TestCase("-1.23", 4, 2, false)] + [TestCase("0,1", 2, 1, true)] + [TestCase("0", 1, 0, true)] + public void IsValidNumber_ReturnsTrue_OnCorrectInputData(string value, int precision, int scale, + bool onlyPositive) + { + var numberValidator = new NumberValidator(precision, scale, onlyPositive); + + numberValidator.IsValidNumber(value).Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/cs/HomeExercisesTests/ObjectComparisonTests.cs b/cs/HomeExercisesTests/ObjectComparisonTests.cs new file mode 100644 index 00000000..19fff78e --- /dev/null +++ b/cs/HomeExercisesTests/ObjectComparisonTests.cs @@ -0,0 +1,66 @@ +using FluentAssertions; +using HomeExercises; + +namespace HomeExercisesTests +{ + public class ObjectComparisonTests + { + [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)); + + /* + * Чем это решение лучше решения в CheckCurrentTsar_WithCustomEquality: + * 1. Это решение не нужно будет переписывать при добавлении/удалении в класс/из класса Person полей. + * 2. В этом решении наглядно видно, какое именно поле не учитывается при сравнении объектов. + * 3. При падении теста мы увидим, какие именно поля объектов не прошли проверку на равенство. + */ + actualTsar + .Should() + .BeEquivalentTo(expectedTsar, options => options + .Excluding(info => + info.SelectedMemberPath.Equals(nameof(Person.Id)) || + info.SelectedMemberPath.EndsWith($"{nameof(Person.Parent)}.{nameof(Person.Id)}")) + .IgnoringCyclicReferences() + .AllowingInfiniteRecursion()); + } + + [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)); + + // Какие недостатки у такого подхода? + + /* + * 1. Если мы решим изменить класс Person (например, добавить или удалить какие-то поля), + * то тогда придется переписывать метод AreEqual + * 2. Если тест не пройдет, то мы не увидим, какие именно поля у двух объектов не совпали. + * Нам просто выдаст сообщение 'Expected: True, But was: False' + */ + + Assert.True(AreEqual(actualTsar, expectedTsar)); + } + + private static 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 diff --git a/cs/testing.sln b/cs/testing.sln index 4642966f..61cff887 100644 --- a/cs/testing.sln +++ b/cs/testing.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HomeExercises", "HomeExerci EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Challenge", "Challenge\Challenge.csproj", "{BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HomeExercisesTests", "HomeExercisesTests\HomeExercisesTests.csproj", "{224B11F4-2395-4280-8F7A-6E193AD3504E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}.Release|Any CPU.Build.0 = Release|Any CPU + {224B11F4-2395-4280-8F7A-6E193AD3504E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {224B11F4-2395-4280-8F7A-6E193AD3504E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {224B11F4-2395-4280-8F7A-6E193AD3504E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {224B11F4-2395-4280-8F7A-6E193AD3504E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 29addbf2e18012741d5781bd17c9fb2bb7c41eee Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Wed, 29 Nov 2023 13:01:31 +0500 Subject: [PATCH 4/5] ObjectComparisonTests.CheckCurrentTsar method refactored --- cs/HomeExercisesTests/ObjectComparisonTests.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cs/HomeExercisesTests/ObjectComparisonTests.cs b/cs/HomeExercisesTests/ObjectComparisonTests.cs index 19fff78e..d05d97f3 100644 --- a/cs/HomeExercisesTests/ObjectComparisonTests.cs +++ b/cs/HomeExercisesTests/ObjectComparisonTests.cs @@ -20,13 +20,15 @@ public void CheckCurrentTsar() * 1. Это решение не нужно будет переписывать при добавлении/удалении в класс/из класса Person полей. * 2. В этом решении наглядно видно, какое именно поле не учитывается при сравнении объектов. * 3. При падении теста мы увидим, какие именно поля объектов не прошли проверку на равенство. + * 4. Это решение игнорирует циклические ссылки, поэтому этот тест не выбросит StackOverflowException + * при циклических зависимостях. */ actualTsar .Should() .BeEquivalentTo(expectedTsar, options => options .Excluding(info => - info.SelectedMemberPath.Equals(nameof(Person.Id)) || - info.SelectedMemberPath.EndsWith($"{nameof(Person.Parent)}.{nameof(Person.Id)}")) + info.SelectedMemberInfo.Name.Equals(nameof(Person.Id)) + && info.SelectedMemberInfo.DeclaringType.Name.Equals(nameof(Person))) .IgnoringCyclicReferences() .AllowingInfiniteRecursion()); } @@ -38,7 +40,7 @@ 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)); - + // Какие недостатки у такого подхода? /* @@ -46,6 +48,7 @@ public void CheckCurrentTsar_WithCustomEquality() * то тогда придется переписывать метод AreEqual * 2. Если тест не пройдет, то мы не увидим, какие именно поля у двух объектов не совпали. * Нам просто выдаст сообщение 'Expected: True, But was: False' + * 3. Если передать зацикленного царя, тест выбросит Stack Overflow Exception. */ Assert.True(AreEqual(actualTsar, expectedTsar)); From 8a56f809a785016206503ab7da6b3556198703bd Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Wed, 29 Nov 2023 22:03:39 +0500 Subject: [PATCH 5/5] ObjectComparisonTests.CheckCurrentTsar refactored --- cs/HomeExercisesTests/ObjectComparisonTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cs/HomeExercisesTests/ObjectComparisonTests.cs b/cs/HomeExercisesTests/ObjectComparisonTests.cs index d05d97f3..c615c8c1 100644 --- a/cs/HomeExercisesTests/ObjectComparisonTests.cs +++ b/cs/HomeExercisesTests/ObjectComparisonTests.cs @@ -28,7 +28,7 @@ public void CheckCurrentTsar() .BeEquivalentTo(expectedTsar, options => options .Excluding(info => info.SelectedMemberInfo.Name.Equals(nameof(Person.Id)) - && info.SelectedMemberInfo.DeclaringType.Name.Equals(nameof(Person))) + && info.SelectedMemberInfo.DeclaringType == typeof(Person)) .IgnoringCyclicReferences() .AllowingInfiniteRecursion()); }