Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Чечулин Антон #224

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
6 changes: 3 additions & 3 deletions cs/HomeExercises/HomeExercises.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace>HomeExercises</RootNamespace>
<AssemblyName>ObjectComparison</AssemblyName>
Expand All @@ -13,8 +13,8 @@
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>

</Project>
138 changes: 75 additions & 63 deletions cs/HomeExercises/NumberValidatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,76 +5,88 @@

namespace HomeExercises
{
public class NumberValidatorTests
{
[Test]
public void Test()
{
Assert.Throws<ArgumentException>(() => new NumberValidator(-1, 2, true));
Assert.DoesNotThrow(() => new NumberValidator(1, 0, true));
Assert.Throws<ArgumentException>(() => new NumberValidator(-1, 2, false));
Assert.DoesNotThrow(() => new NumberValidator(1, 0, true));
public class NumberValidatorTests
{
[TestCase(-1, 2, true, TestName = "CreateNumberValidator_PrecisionNotPositive_ThrowsArgumentException")]
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
[TestCase(1, -1, true, TestName = "CreateNumberValidator_ScaleNegative_ThrowsArgumentException")]
[TestCase(1, 2, true, TestName = "CreateNumberValidator_SacleBiggerPrecision_ThrowsArgumentException")]
public void Throws(int precision, int scale, bool onlyPostive)
{
Action action = () => new NumberValidator(precision, scale, onlyPostive);
action.Should().Throw<ArgumentException>();
}

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"));
}
}
[TestCase(17, 2, true, " ", TestName = "IsValidNumber_NumberEmpty_Fails")]
[TestCase(8, 2, true, "1/43", TestName = "IsValidNumber_ValueNotMatch_Fails")]
[TestCase(3, 2, true, "00.00", TestName = "IsValidNumber_PrecisionLessCountNumbers_Fails")]
[TestCase(3, 2, false, "-0.00", TestName = "IsValidNumber_PrecisionLessCountNumbersAndSign_Fails")]
[TestCase(17, 2, true, "0.000", TestName = "IsValidNumber_ScaleLessFractPartLength_Fails")]
[TestCase(3, 2, true, "a.sd", TestName = "IsValidNumber_LettersInValue_Fails")]
[TestCase(3, 2, true, "-1.2", TestName = "IsValidNumber_OnlyPositiveTrueAndNegativeNumber_Fails")]
public void Fails(int precision, int scale, bool onlyPostive, string value)
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
{
var validator = new NumberValidator(precision, scale, onlyPostive);
var result = validator.IsValidNumber(value);
result.Should().BeFalse();
}

public class NumberValidator
{
private readonly Regex numberRegex;
private readonly bool onlyPositive;
private readonly int precision;
private readonly int scale;
[TestCase(17, 2, true, "0", TestName = "IsValidNumber_IntAndFractPartLessWhenPrecision_Success")]
[TestCase(4, 2, true, "+1.23", TestName = "IsValidNumber_PrecisionEqualsCountNumbersAndSign_Success")]
[TestCase(3, 2, true, "5,13", TestName = "IsValidNumber_PrecisionEqualsCountNumbers_Success")]
[TestCase(4, 2, true, "2.25", TestName = "IsValidNumber_PrecisionBiggerCountNumbers_Success")]

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 void Success(int precision, int scale, bool onlyPostive, string value)
{
var validator = new NumberValidator(precision, scale, onlyPostive);
var result = validator.IsValidNumber(value);
result.Should().BeTrue();
}
}

public bool IsValidNumber(string value)
{
// Проверяем соответствие входного значения формату N(m,k), в соответствии с правилом,
// описанным в Формате описи документов, направляемых в налоговый орган в электронном виде по телекоммуникационным каналам связи:
// Формат числового значения указывается в виде N(m.к), где m – максимальное количество знаков в числе, включая знак (для отрицательного числа),
// целую и дробную часть числа без разделяющей десятичной точки, k – максимальное число знаков дробной части числа.
// Если число знаков дробной части числа равно 0 (т.е. число целое), то формат числового значения имеет вид N(m).
public class NumberValidator
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly static Regex numberRegex = new Regex(@"^([+-]?)(\d+)([.,](\d+))?$", RegexOptions.Compiled);
private readonly bool onlyPositive;
private readonly int precision;
private readonly int scale;

if (string.IsNullOrEmpty(value))
return false;
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, scale must be less or equal than precision");
}

var match = numberRegex.Match(value);
if (!match.Success)
return false;
public bool IsValidNumber(string value)
{
// Проверяем соответствие входного значения формату N(m,k), в соответствии с правилом,
// описанным в Формате описи документов, направляемых в налоговый орган в электронном виде по телекоммуникационным каналам связи:
// Формат числового значения указывается в виде N(m.к), где m – максимальное количество знаков в числе, включая знак (для отрицательного числа),
// целую и дробную часть числа без разделяющей десятичной точки, k – максимальное число знаков дробной части числа.
// Если число знаков дробной части числа равно 0 (т.е. число целое), то формат числового значения имеет вид N(m).

// Знак и целая часть
var intPart = match.Groups[1].Value.Length + match.Groups[2].Value.Length;
// Дробная часть
var fracPart = match.Groups[4].Value.Length;
if (string.IsNullOrEmpty(value))
return false;

if (intPart + fracPart > precision || fracPart > scale)
return false;
var match = numberRegex.Match(value);
if (!match.Success)
return false;

if (onlyPositive && match.Groups[1].Value == "-")
return false;
return true;
}
}
// Знак и целая часть
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;
}
}
}
145 changes: 75 additions & 70 deletions cs/HomeExercises/ObjectComparison.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,81 +3,86 @@

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()
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
{
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));
actualTsar.Should().BeEquivalentTo(expectedTsar, options =>
options
.Excluding(tsar => tsar.SelectedMemberInfo.Name == nameof(Person.Id))
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
.AllowingInfiniteRecursion());
}

// Перепишите код на использование 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);
// Недостаток в том, что нужно будет переписывать тест при любом исправлении в классе Person.
// Возможно нужно добавить какое-то поле, или изменить название уже существующего,
// придется изменять каждый раз метод AreEqual, так же снижается читаемость теста,
// так как приходится разбираться еще в методе AreEqual
// Мое решение же лучше, потому что при добавлении новых полей нужно добавлять только те,
// которые необходимо игнорировать при сравнении классов, плюс гораздо локаничнее и читаемее
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
// Если передать в тест царя, который ссылается сам на себя, то его выполнение будет прервано из-за
// Stack overflow, то есть переполнения стека рекурсии.
// FluentAssertions видит возможную циклическую ссылку объекта на самого себя и выдает ошибку
// Cyclic reference to type HomeExercises.Person detected
// При разных царях тест не показывает, в чем отличие и поэтому тяжело отловить ошибку, если же упадет тест
// с FluentAssertions, он показывает, где именно была ошибка и в чем она заключается
// например Expected member Age to be 53, but found 54.
[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.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);
}
// Какие недостатки у такого подхода?
Assert.True(AreEqual(actualTsar, expectedTsar));
}

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

// Какие недостатки у такого подхода?
Assert.True(AreEqual(actualTsar, expectedTsar));
}
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));
}
}

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 Person
{
public static int IdCounter = 0;
public int Age, Height, Weight;
public string Name;
public Person? Parent;
public int Id;

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;
}
}
public Person(string name, int age, int height, int weight, Person? parent)
{
Id = IdCounter++;
Name = name;
Age = age;
Height = height;
Weight = weight;
Parent = parent;
}
}
}
13 changes: 8 additions & 5 deletions cs/testing.sln
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.13
# Visual Studio Version 17
VisualStudioVersion = 17.7.34024.191
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{E7A56C48-8E36-465B-9F8E-67BC8525CFE5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "Samples\Samples.csproj", "{E7A56C48-8E36-465B-9F8E-67BC8525CFE5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HomeExercises", "HomeExercises\HomeExercises.csproj", "{4F9FBCCA-43E0-431B-944D-834D16AD18F9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HomeExercises", "HomeExercises\HomeExercises.csproj", "{4F9FBCCA-43E0-431B-944D-834D16AD18F9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Challenge", "Challenge\Challenge.csproj", "{BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Challenge", "Challenge\Challenge.csproj", "{BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -31,4 +31,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E4A3E5B6-B75B-4770-81B7-1EA62A1DF3F0}
EndGlobalSection
EndGlobal