Skip to content
55 changes: 55 additions & 0 deletions cs/HomeExercises/NumberValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Text.RegularExpressions;
using JetBrains.Annotations;

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);
}

[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;
}
}
}
80 changes: 0 additions & 80 deletions cs/HomeExercises/NumberValidatorTests.cs

This file was deleted.

83 changes: 0 additions & 83 deletions cs/HomeExercises/ObjectComparison.cs

This file was deleted.

23 changes: 23 additions & 0 deletions cs/HomeExercises/Person.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;

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;
}
}
}
72 changes: 72 additions & 0 deletions cs/HomeExercises/Tests/NumberValidatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using NUnit.Framework;
using System;

namespace HomeExercises.Tests
{
[TestFixture(TestOf = typeof(NumberValidator))]
public class NumberValidatorTests
{
[Test]
public void NumberValidatorCtor_WhenPassValidArguments_ShouldNotThrows() =>
Assert.DoesNotThrow(() => new NumberValidator(1, 0, true));

private static TestCaseData[] ArgumentExceptionTestCases =
{
new TestCaseData(-1, 2, true).SetName("NegativePrecision"),
new TestCaseData(0, 2, true).SetName("PrecisionEqualToZero"),
new TestCaseData(1, -2, true).SetName("NegativeScale"),
new TestCaseData(2, 2, true).SetName("PrecisionIsEqualToTheScale"),
new TestCaseData(2, 3, true).SetName("ScaleIsMorePrecision")
};

[TestCaseSource(nameof(ArgumentExceptionTestCases))]
public void NumberValidatorCtor_WhenPassInvalidArguments_ShouldThrowArgumentException(int precision, int scale, bool onlyPositive) =>
Assert.Throws<ArgumentException>(() => new NumberValidator(precision, scale, onlyPositive));

private static TestCaseData[] InvalidArgumentTestCases =
{
new TestCaseData(3,2,true,"a.sd").Returns(false).SetName("LettersInsteadOfNumber"),
new TestCaseData(3,2,true,"2.!").Returns(false).SetName("SymbolsInsteadOfNumber"),
new TestCaseData(3,2,true,null!).Returns(false).SetName("PassNumberIsNull"),
new TestCaseData(3,2,true,"").Returns(false).SetName("PassNumberIsEmpty"),
new TestCaseData(3,2,true," ").Returns(false).SetName("OnlySpaceInNumber"),
new TestCaseData(3,2,true," 2").Returns(false).SetName("MultipleSpacesAndOneDigitInNumber"),
new TestCaseData(3,2,true,"2,.3").Returns(false).SetName("TwoSeparatorsArePassed"),
new TestCaseData(3,2,true,".").Returns(false).SetName("OnlySeparatorArePassed"),
new TestCaseData(3,2,true,"2 3").Returns(false).SetName("SeparatedBySpace"),
new TestCaseData(3,2,true,"-0.00").Returns(false).SetName("IntPartWithNegativeSignMoreThanPrecision"),
new TestCaseData(3,2,true,"+1.23").Returns(false).SetName("IntPartWithPositiveSignMoreThanPrecision"),
new TestCaseData(3,2,true,"0.000").Returns(false).SetName("FractionalPartMoreThanScale"),
new TestCaseData(3,2,true,"2%3").Returns(false).SetName("PercentSignInTheFormOfSeparator"),
new TestCaseData(3,2,true,"2$").Returns(false).SetName("AmpersandInTheFormOfSeparator"),
new TestCaseData(3,2,true,"#").Returns(false).SetName("OctothorpeInTheFormOfNumber"),
new TestCaseData(3,2,true,"2@3").Returns(false).SetName("CommercialAtSymbolInTheFormOfSeparator"),
new TestCaseData(3,2,true,"(2.3)").Returns(false).SetName("NumberInParentheses"),
new TestCaseData(3,2,true,"2;3").Returns(false).SetName("SemicolonInTheFormOfSeparator"),
new TestCaseData(3,2,true,"2/r").Returns(false).SetName("CarriageReturnInNumber"),
new TestCaseData(3,2,true,"/n3").Returns(false).SetName("NewLineInNumber"),
new TestCaseData(3,2,true,"/t3.4").Returns(false).SetName("TabInNumber"),
new TestCaseData(3,2,true,"3.4/b").Returns(false).SetName("BackSpaceInNumber"),
new TestCaseData(3,2,true,"3.47e+10").Returns(false).SetName("NumberInExponentialForm"),
new TestCaseData(3,2,true,"10^3").Returns(false).SetName("NumberInAPower"),
new TestCaseData(3,2,true,"11101010").Returns(false).SetName("BinaryNumberSystem"),
new TestCaseData(3,2,true,"0xEA").Returns(false).SetName("HexadecimalNumberSystem"),
};

private static TestCaseData[] ValidArgumentTestCases =
{
new TestCaseData(3,2,true,"2,3").Returns(true).SetName("CharactersAreSeparatedByComma"),
new TestCaseData(3,2,true,"0").Returns(true).SetName("FractionalPartIsMissing"),
new TestCaseData(3,2,true,"0.0").Returns(true).SetName("NumberIsValid"),
new TestCaseData(19,2,true,"9223372036854775807").Returns(true).SetName("LargeIntPartInNumber"),
new TestCaseData(27,25,true,"3.1415926535897932384626433").Returns(true).SetName("LargeFracPartInNumber"),
new TestCaseData(45,25,true,"9223372036854775807.1415926535897932384626433").Returns(true).SetName("LargeNumber")
};

[TestOf(nameof(NumberValidator.IsValidNumber))]
[TestCaseSource(nameof(InvalidArgumentTestCases))]
[TestCaseSource(nameof(ValidArgumentTestCases)), Repeat(2)]
public bool NumberValidation_ShouldBeCorrect(int precision, int scale, bool onlyPositive, string number) =>
new NumberValidator(precision, scale, onlyPositive).IsValidNumber(number);
}
}
57 changes: 57 additions & 0 deletions cs/HomeExercises/Tests/ObjectComparisonTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using FluentAssertions;
using NUnit.Framework;
using System;

namespace HomeExercises.Tests
{
[TestFixture(TestOf = typeof(TsarRegistry))]
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));

actualTsar.Should()
.BeEquivalentTo(expectedTsar, x => x
.Excluding(x => x.Id)
.Excluding(x => x.Parent!.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));

// Какие недостатки у такого подхода?
// Данный подход делает класс труднорасширяемым, ведь при добавлении новых полей в класс Person
// нужно переписывать и метод сравнения для всех этих полей, так же новые поля cможет добавить
// другой разработчик, который не знает о таком методе сравнения, что приведет к новым, неотловоенным ошибкам -
// новые поля не будут сравниваться.
// В моем решении (CheckCurrentTsar), такой ошибки не возникнет и класс Person сможет без ошибок расширяться
// при условии, что сравниваться будут все поля, кроме Id (так же и их Parent), так как код написан не перебиранием
// всех полей для сравнения, а сравнением объекта в целом с исключением его Id.
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);
}
}
}
14 changes: 14 additions & 0 deletions cs/HomeExercises/TsarRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

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));
}
}
}