diff --git a/cs/HomeExercises/HomeExercises.csproj b/cs/HomeExercises/HomeExercises.csproj
index ede81aec..3d6fba00 100644
--- a/cs/HomeExercises/HomeExercises.csproj
+++ b/cs/HomeExercises/HomeExercises.csproj
@@ -14,7 +14,7 @@
-
+
\ No newline at end of file
diff --git a/cs/HomeExercises/NumberValidatorTests.cs b/cs/HomeExercises/NumberValidatorTests.cs
index a2878113..48cf1456 100644
--- a/cs/HomeExercises/NumberValidatorTests.cs
+++ b/cs/HomeExercises/NumberValidatorTests.cs
@@ -1,80 +1,183 @@
using System;
using System.Text.RegularExpressions;
using FluentAssertions;
+using FluentAssertions.Execution;
using NUnit.Framework;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
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;
- }
- }
+ [TestFixture]
+ public class NumberValidatorTests
+ {
+ private static IEnumerable IsValidNumberPrecisionTests = new []
+ {
+ new TestCaseData(3, 2, true, "+0.00").Returns(false)
+ .SetName("False_WhenSymbolWithNumberLengthGreaterThanPrecision"),
+ new TestCaseData(3, 2, true, "00.00").Returns(false)
+ .SetName("False_WhenIntPartWithFracPartGreaterThanPrecision"),
+
+ new TestCaseData(17, 2, true, "0").Returns(true)
+ .SetName("True_WhenNumberLengthNotGreaterThanPrecision"),
+ new TestCaseData(4, 2, true, "+1.23").Returns(true)
+ .SetName("True_WhenPositiveSymbolWithNumberLengthNotGreaterThanPrecision"),
+ new TestCaseData(4, 2, false, "-1.23").Returns(true)
+ .SetName("True_WhenNegativeSymbolWithNumberLengthNotGreaterThanPrecision")
+ };
+
+ private static IEnumerable IsValidNumberScaleTests = new []
+ {
+ new TestCaseData(17, 2, true, "0.000").Returns(false)
+ .SetName("False_WhenFracPartGreaterThanScale"),
+ new TestCaseData(17, 2, true, "0.0").Returns(true)
+ .SetName("True_WhenFracPartNotGreaterThanScale")
+ };
+
+ private static IEnumerable IsValidNumberLongNumbersTests = new[]
+ {
+ new TestCaseData(41, 2, true, $"{Int64.MaxValue}{Int64.MaxValue}").Returns(true)
+ .SetName("True_WhenGivenBigIntPart"),
+ new TestCaseData(41, 40, true, $"1,{Int64.MaxValue}{Int64.MaxValue}").Returns(true)
+ .SetName("True_WhenGivenBigFracPart"),
+ new TestCaseData(81, 80, true, $"{Int64.MaxValue}{Int64.MaxValue},{Int64.MaxValue}{Int64.MaxValue}")
+ .Returns(true).SetName("True_WhenGivenBigFractWithIntPart"),
+ new TestCaseData(1001, 1000, true, $"{1/3}").Returns(true)
+ .SetName("True_WhenGivenEndlessRationalNumber"),
+ new TestCaseData(1001, 1000, true, $"{Math.PI}").Returns(true)
+ .SetName("True_WhenGivenEndlessIrrationalNumber"),
+ };
+
+ private static IEnumerable IsValidNumberDiffrentFormsOfNumbersTests = new[]
+ {
+ new TestCaseData(2, 1, true, "01").Returns(true)
+ .SetName("True_WhenGivenNumberInBinaryForm"),
+ new TestCaseData(2, 1, true, "0001").Returns(false)
+ .SetName("False_WhenGivenNumberInBinaryFormWithLengthGreaterThanPrecision"),
+ new TestCaseData(5, 1, true, "8ABCD").Returns(false)
+ .SetName("False_WhenGivenNumberInHexadecimalFormWithLetters"),
+ new TestCaseData(8, 7, true, "1,23E+10").Returns(false)
+ .SetName("False_WhenGivenNumberInExponentialForm")
+ };
+
+ private static IEnumerable IsValidNumberPositivityTests = new []
+ {
+ new TestCaseData(3, 2, true, "-0.00").Returns(false)
+ .SetName("False_WhenAcceptsOnlyPositiveButGivenNegativeNumber"),
+ new TestCaseData(3, 2, false, "-0.0").Returns(true)
+ .SetName("True_WhenAcceptsAnyAndGivenNegativeNumber")
+ };
+
+ private static IEnumerable IsValidNumberSymbolsTests = new []
+ {
+ new TestCaseData(3, 2, true, "1,2").Returns(true).SetName("True_WhenSeparatorIsComma"),
+
+ new TestCaseData(3, 2, true, "1;2").Returns(false).SetName("False_WhenSeparatorIsNotCommaOrDot"),
+ new TestCaseData(3, 2, true, "1.,2").Returns(false).SetName("False_WhenStringContainsMoreThanOneSeparator"),
+ new TestCaseData(3, 2, true, "\n").Returns(false).SetName("False_WhenGivenSpecialCharacter"),
+ new TestCaseData(3, 2, true, ",").Returns(false).SetName("False_WhenOnlySeparatorGiven"),
+ new TestCaseData(3, 2, true, "1a").Returns(false).SetName("False_WhenGivenLetterAfterDigit"),
+ new TestCaseData(3, 2, true, "a1").Returns(false).SetName("False_WhenGivenDigitAfterLetter"),
+ new TestCaseData(3, 2, true, null).Returns(false).SetName("False_WhenGivenNull"),
+ new TestCaseData(3, 2, true, "a.sd").Returns(false).SetName("False_WhenGivenNotDigits"),
+ new TestCaseData(3, 2, true, "#").Returns(false).SetName("False_WhenGivenNotDigitSymbol"),
+ new TestCaseData(17, 2, true, "").Returns(false).SetName("False_WhenEmptyStringGiven"),
+ new TestCaseData(17, 2, true, " ").Returns(false).SetName("False_WhenStringOfSpacesGiven")
+ };
+
+ [Repeat(5)]
+ [TestCaseSource(nameof(IsValidNumberScaleTests))]
+ [TestCaseSource(nameof(IsValidNumberSymbolsTests))]
+ [TestCaseSource(nameof(IsValidNumberPrecisionTests))]
+ [TestCaseSource(nameof(IsValidNumberPositivityTests))]
+ [TestCaseSource(nameof(IsValidNumberLongNumbersTests))]
+ [TestCaseSource(nameof(IsValidNumberDiffrentFormsOfNumbersTests))]
+ public bool IsValidNumber_Returns(int precision, int scale, bool onlyPositive, string number) =>
+ new NumberValidator(precision, scale, onlyPositive).IsValidNumber(number);
+
+
+ private static IEnumerable ConstructorArgumentExceptions = new[]
+ {
+ new TestCaseData(-1, 2, true).SetName("WhenPercisionNotPositive"),
+ new TestCaseData(1, 2, true).SetName("WhenScaleGreaterThanPercision"),
+ new TestCaseData(1, -1, true).SetName("WhenScaleNotPositive"),
+ new TestCaseData(1, 1, true).SetName("WhenScaleEqualsPercision")
+ };
+
+ [TestCaseSource(nameof(ConstructorArgumentExceptions))]
+ public void Constructor_ThrowsArgumentException(int precision, int scale, bool onlyPositive) =>
+ Assert.Throws(() => new NumberValidator(precision, scale, onlyPositive));
+
+ private static IEnumerable ConstructorNoExceptions = new[]
+ {
+ new TestCaseData(2, 1, true).SetName("WhenPercisionPositiveAndScaleLessThanPrecision")
+ };
+
+ [TestCaseSource(nameof(ConstructorNoExceptions))]
+ public void Constructor_ShouldNotThrowException(int precision, int scale, bool onlyPositive) =>
+ Assert.DoesNotThrow(() => new NumberValidator(precision, scale, onlyPositive));
+
+ [Test]
+ public void ValidatorState_ShouldStayUnchanged()
+ {
+ var validator = new NumberValidator(10, 9, true);
+ var number = "1,2";
+
+ var resultBeforeChange = validator.IsValidNumber(number);
+ validator.IsValidNumber("00");
+ var resultAfterChange = validator.IsValidNumber(number);
+
+ resultAfterChange.Should().Be(resultBeforeChange);
+ }
+ }
+
+ 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);
+ }
+
+ [Pure]
+ 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 44d9aed4..2d022193 100644
--- a/cs/HomeExercises/ObjectComparison.cs
+++ b/cs/HomeExercises/ObjectComparison.cs
@@ -1,83 +1,101 @@
using FluentAssertions;
using NUnit.Framework;
+using System.Net;
namespace HomeExercises
{
- public class ObjectComparison
- {
- [Test]
- [Description("Проверка текущего царя")]
- [Category("ToRefactor")]
- public void CheckCurrentTsar()
- {
- var actualTsar = TsarRegistry.GetCurrentTsar();
+ 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));
+ 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);
+ // сравнение родителей такое же как и в коде, который нужно было отрефакторить - сравнивает по ссылке,
+ // а значит при создании экземпляра для сравнения, если родитель родителя будет не null,
+ // то будет ошибка, если это не ошибка в тесте, а так и должно быть,
+ // то реализация CheckCurrentTsar_WithCustomEquality работает неправильно,
+ // тк в таком случае она засчитает тест
+ // также данная реализация CheckCurrentTsar не сравнивает поле Weight для родителя,
+ // также как и оригинальный код, из-за чего данная реализация
+ // будет показывать результат отличный от CheckCurrentTsar_WithCustomEquality при
+ // небольших изменениях в TsarRegistry или в экземпляре expectedTsar
- 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);
- }
+ actualTsar.Should().BeEquivalentTo(expectedTsar, options => options
+ .Excluding(tsar => tsar.Id)
+ .Excluding(tsar => tsar.Parent));
+ expectedTsar.Parent.Should().BeEquivalentTo(actualTsar.Parent!, options => options
+ .Excluding(parent => parent.Id)
+ .Excluding(parent => parent.Weight));
- [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));
+ // данная реализация лучше CheckCurrentTsar_WithCustomEquality тем, что:
+ // 1) при ошибке в тесте, выводится объяснение того, что пошло не так
+ // 2) код теста легко читается - методы записаны последовательно, и что они делают понятно из названия
+ // 3) название теста на прямую отражает происходящее в коде и соответствует выводимому результату
+ // 4) более удобная в плане расширяемости
- // Какие недостатки у такого подхода?
- 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);
- }
- }
+ [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));
- 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));
- }
- }
+ // Какие недостатки у такого подхода?
+ // 1) функциональность теста разбита на 2 метода, усложняя его читаемость
+ // 2) тест уже посути не сравнивает царей, а проверяет работу метода AreEqual
+ // 3) отсутствие пояснения - только выкинет "ожидается true, а было false" при ошибке
+ // 4) такая реализация AreEqual может вызвать переполнение стэка, тк сравнение родителей рекурсивно
+ Assert.True(AreEqual(actualTsar, expectedTsar));
+ }
- public class Person
- {
- public static int IdCounter = 0;
- public int Age, Height, Weight;
- public string Name;
- public Person? Parent;
- public int Id;
+ 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 Person(string name, int age, int height, int weight, Person? parent)
- {
- Id = IdCounter++;
- Name = name;
- Age = age;
- Height = height;
- Weight = weight;
- Parent = 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