Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
aaab55a
refactor: rewrite ObjectComparison to use FluentAssertions
tazik23 Oct 28, 2025
6f21ba7
docs: add detailed comments comparing tests
tazik23 Oct 28, 2025
573cd11
Merge branch 'refactor/object-comparison-tests' into homework/tests-r…
tazik23 Oct 28, 2025
4d87d60
test: add NumberValidator ctor tests
tazik23 Oct 28, 2025
67c5bc2
test: add NumberValidator string validation tests
tazik23 Oct 28, 2025
9fc4895
test: add NumberValidator scale and precision tests
tazik23 Oct 28, 2025
e59c618
test: add NumberValidator OnlyPositive flag tests
tazik23 Oct 28, 2025
3ce8d12
test: add cases leading and trailing zeros
tazik23 Oct 28, 2025
e0624e7
test: add negative number case
tazik23 Oct 28, 2025
fa7a3d9
Merge branch 'refactor/number-validator-tests' into homework/tests-re…
tazik23 Oct 28, 2025
2f659b0
Merge remote-tracking branch 'origin/homework/tests-refactoring' into…
tazik23 Oct 30, 2025
d14872b
test: add digits and letters test case to NumberValidatorTests
tazik23 Oct 30, 2025
acb452c
refactor: correct the mistake in NumberValidator Exception message
tazik23 Oct 30, 2025
7740071
refactor: add message check in NumberValidatorTests
tazik23 Oct 30, 2025
f1a0ff0
test: add cyclic dependency and null parent test case in ObjectCompar…
tazik23 Oct 30, 2025
8ebde3b
test: add new test cases in ObjectComparison
tazik23 Oct 30, 2025
f826ad4
fix: extend FluentAssertions recursion depth limit
tazik23 Oct 30, 2025
294520a
refactor: correct exception message in NumberValidator
tazik23 Oct 30, 2025
56170df
refactor: rename test class
tazik23 Oct 30, 2025
ad3cca2
refactor: move GetTsarWithWrongAncestor to TsarRegistry
tazik23 Oct 30, 2025
81090c9
docs: add comments
tazik23 Oct 30, 2025
add76b2
refactor: make regex static and compiled
tazik23 Oct 30, 2025
73c2029
feat: add City record
tazik23 Oct 30, 2025
907d2d3
fix: checking that Id belongs to the Person
tazik23 Oct 30, 2025
53130ca
test: add test with different ids
tazik23 Oct 30, 2025
fce0da6
refactor: move TsarEqaulityComparer to separate class
tazik23 Oct 30, 2025
9d359fd
refactor: remove uneccessary IgnoreCase option
tazik23 Oct 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Testing/Basic/Basic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="FluentAssertions" Version="8.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
Expand Down
3 changes: 3 additions & 0 deletions Testing/Basic/Homework/1. ObjectComparison/City.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Basic.Homework._1._ObjectComparison;

public record City(int Id, string Name);
52 changes: 0 additions & 52 deletions Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs

This file was deleted.

8 changes: 6 additions & 2 deletions Testing/Basic/Homework/1. ObjectComparison/Person.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace HomeExercise.Tasks.ObjectComparison;
using Basic.Homework._1._ObjectComparison;

namespace HomeExercise.Tasks.ObjectComparison;


public class Person
Expand All @@ -8,14 +10,16 @@ public class Person
public string Name;
public Person Parent;
public int Id;
public City City;

public Person(string name, int age, int height, int weight, Person parent)
public Person(string name, int age, int height, int weight, Person parent, City city)
{
Id = IdCounter++;
Name = name;
Age = age;
Height = height;
Weight = weight;
Parent = parent;
City = city;
}
}
15 changes: 15 additions & 0 deletions Testing/Basic/Homework/1. ObjectComparison/TsarEqualityComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using FluentAssertions;
using HomeExercise.Tasks.ObjectComparison;

namespace Basic.Homework._1._ObjectComparison;

public static class TsarEqualityComparer
{
public static void CheckTsarEquality(Person actualTsar, Person expectedTsar)
{
actualTsar.Should().BeEquivalentTo(expectedTsar, o => o
.Excluding(m => m.Name.Equals(nameof(Person.Id)) && m.DeclaringType == typeof(Person))
.IgnoringCyclicReferences()
.AllowingInfiniteRecursion());
}
}
49 changes: 47 additions & 2 deletions Testing/Basic/Homework/1. ObjectComparison/TsarRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,56 @@
namespace HomeExercise.Tasks.ObjectComparison;
using Basic.Homework._1._ObjectComparison;

namespace HomeExercise.Tasks.ObjectComparison;

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));
new Person("Vasili III of Russia", 28, 170, 60, null, new City(1, "Peterburg")),
new City(1, "Peterburg"));
}

public static Person GetCurrentTsarWithCyclicDependency()
{
var tsar = new Person("Ivan IV The Terrible", 54, 170, 70, null, new City(1, "Peterburg"));
var parent = new Person("Vasili III of Russia", 28, 170, 60, tsar, new City(1, "Peterburg"));
tsar.Parent = parent;

return tsar;
}

public static Person GetCurrentTsarWithAncestryChain(int generations)
{
Person current = null!;

for (int i = generations; i > 0; i--)
{
current = new Person($"Ancestor {i}", 40 + i * 5, 170 + i, 65 + i, current, new City(1, "Peterburg"));
}

var tsar = new Person("Ivan IV The Terrible", 54, 170, 70, current, new City(1, "Peterburg"));

return tsar;
}

public static Person GetTsarWithWrongAncestor(int generations, int wrongAncestorLevel)
{
Person current = null!;

for (int i = generations; i > 0; i--)
{
current = new Person($"Ancestor {i}", 40 + i * 5, 170 + i, 65 + i, current, new City(1, "Peterburg"));

if (i == wrongAncestorLevel)
{
current.Name = "Imposter";
}
}

var tsar = new Person("Ivan IV The Terrible", 54, 170, 70, current, new City(1, "Peterburg"));

return tsar;
}
}
115 changes: 115 additions & 0 deletions Testing/Basic/Homework/1. ObjectComparison/TsarTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using Basic.Homework._1._ObjectComparison;
using FluentAssertions;
using HomeExercise.Tasks.ObjectComparison;
using NUnit.Framework;

namespace HomeExercise.Tasks.TsarTests;

public class TsarTests
{
[Test]
public void CheckTsarEquality_WithValidTsar_ShouldNotThrow()
{
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, new City(1, "Peterburg")),
new City(1, "Peterburg"));

var act = () => TsarEqualityComparer.CheckTsarEquality(actualTsar, expectedTsar);

act.Should().NotThrow();
}

[Test]
public void CheckTsarEquality_WithCyclicDependency_ShouldNotThrow()
{
var actualTsar = TsarRegistry.GetCurrentTsarWithCyclicDependency();

var expectedTsar =
new Person("Ivan IV The Terrible", 54, 170, 70, null, new City(1, "Peterburg"));
var expectedParent =
new Person("Vasili III of Russia", 28, 170, 60, expectedTsar, new City(1, "Peterburg"));
expectedTsar.Parent = expectedParent;

var act = () => TsarEqualityComparer.CheckTsarEquality(actualTsar, expectedTsar);

act.Should().NotThrow();
}

[Test]
public void CheckTsarEquality_WithParentNullInOneObject_ShouldThrow()
{
var actualTsar = TsarRegistry.GetCurrentTsar();
var expectedTsar =
new Person("Ivan IV The Terrible", 54, 170, 70, null, new City(1, "Peterburg"));

var act = () => TsarEqualityComparer.CheckTsarEquality(actualTsar, expectedTsar);

act.Should().Throw<AssertionException>();
}

[Test]
public void CheckTsarEquality_WithDifferentChainLength_ShouldThrow()
{
var actualTsar = TsarRegistry.GetCurrentTsarWithAncestryChain(5);
var expectedTsar = TsarRegistry.GetCurrentTsarWithAncestryChain(3);

var act = () => TsarEqualityComparer.CheckTsarEquality(actualTsar, expectedTsar);

act.Should().Throw<AssertionException>();
}


[TestCase(5, 3, TestName = "WrongAncestorInMiddleOfChain")]
[TestCase(5, 1, TestName = "WrongAncestorAtStartOfChain")]
[TestCase(5, 5, TestName = "WrongAncestorAtEndOfChain")]
[TestCase(100, 50, TestName = "WrongAncestorLongChain")]
public void CheckTsarEquality_WithWrongAncestorInChain_ShouldThrow(int generations, int wrongAncestorLevel)
{
var actualTsar = TsarRegistry.GetCurrentTsarWithAncestryChain(generations);
var expectedTsar = TsarRegistry.GetTsarWithWrongAncestor(generations, wrongAncestorLevel);

var act = () => TsarEqualityComparer.CheckTsarEquality(actualTsar, expectedTsar);

act.Should().Throw<AssertionException>();
}

[Test]
public void CheckTsarEquality_WithDifferentCityIds_ShouldThrow()
{
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, new City(2, "Peterburg")),
new City(1, "Peterburg"));

var act = () => TsarEqualityComparer.CheckTsarEquality(actualTsar, expectedTsar);

act.Should().Throw<AssertionException>();
}

[Test]
public void CheckTsarEquality_WithDifferentIds_ShouldThrow()
{
var actualTsar = TsarRegistry.GetCurrentTsar();

var expectedTsar = TsarRegistry.GetCurrentTsar();
expectedTsar.Id = 2;
expectedTsar.City = new City(2, "Peterburg");

var act = () => TsarEqualityComparer.CheckTsarEquality(actualTsar, expectedTsar);

act.Should().Throw<AssertionException>();
}

}

/*
* Что хорошо в решении:
* 1.) Тест легче читать: 3 строчки vs найти метод, залезть в него и посмотреть: а что он там делает
* 2.) Явно указываем, какие поля мы исключаем из проверки
* 3.) При добавлении новых полей в Person нужно провести минимальный рефакторинг(исключить из проверки
* или вообще ничего не трогать)
* 4.) Падаем с адекватной информацией об ошибке, в отличие от просто непонятного False
* 5.) Проверяем всю династию(не только родителя)
* 6.) Прошлое решение падало при циклической зависимости(StackOverflow), мое решение корректно обрабатывает этот случай
*/
9 changes: 5 additions & 4 deletions Testing/Basic/Homework/2. NumberValidator/NumberValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ namespace HomeExercise.Tasks.NumberValidator;

public class NumberValidator
{
private readonly Regex numberRegex;
private static readonly Regex NumberRegex =
new Regex(@"^([+-]?)(\d+)([.,](\d+))?$", RegexOptions.Compiled);

private readonly bool onlyPositive;
private readonly int precision;
private readonly int scale;
Expand All @@ -17,8 +19,7 @@ public NumberValidator(int precision, int scale = 0, bool onlyPositive = false)
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);
throw new ArgumentException("scale must be a non-negative number less than precision");
}

public bool IsValidNumber(string value)
Expand All @@ -32,7 +33,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;

Expand Down
Loading