From 6ee66abe62c458a4c9c2cd715c8bc3b492a27afa Mon Sep 17 00:00:00 2001 From: caiman Date: Mon, 27 Nov 2023 15:26:43 +0500 Subject: [PATCH 1/6] switched tests in ObjectComparsion to FluentAssertions and refactored tests --- cs/HomeExercises/ObjectComparison.cs | 56 +--------------- .../Tests/ObjectComparsion_Should.cs | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+), 53 deletions(-) create mode 100644 cs/HomeExercises/Tests/ObjectComparsion_Should.cs diff --git a/cs/HomeExercises/ObjectComparison.cs b/cs/HomeExercises/ObjectComparison.cs index 44d9aed4..e4657006 100644 --- a/cs/HomeExercises/ObjectComparison.cs +++ b/cs/HomeExercises/ObjectComparison.cs @@ -1,57 +1,7 @@ -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)); - - // Перепишите код на использование 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); - } - - [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.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); - } - } - + // Тесты в Tests/ObjectComparison_Should + public class TsarRegistry { public static Person GetCurrentTsar() diff --git a/cs/HomeExercises/Tests/ObjectComparsion_Should.cs b/cs/HomeExercises/Tests/ObjectComparsion_Should.cs new file mode 100644 index 00000000..e3f1609d --- /dev/null +++ b/cs/HomeExercises/Tests/ObjectComparsion_Should.cs @@ -0,0 +1,64 @@ +using FluentAssertions; +using NUnit.Framework; + +namespace HomeExercises.Tests +{ + public class ObjectComparison_Should + { + private Person actualTsar; + private Person expectedTsar; + + [SetUp] + public void SetUp() + { + actualTsar = TsarRegistry.GetCurrentTsar(); + expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70, + new Person("Vasili III of Russia", 28, 170, 60, null)); + } + + [Test] + [Description("Проверка текущего царя")] + [Category("ToRefactor")] + public void CheckCurrentTsar() + { + actualTsar.Should().BeEquivalentTo(expectedTsar, options => + options + .IncludingFields() + .Excluding(p => p.Id) + .Excluding(p => p.Parent!.Id) + .Excluding(p => p.Parent!.Weight) + .Excluding(p => p.Parent!.Parent) + ); + } + + [Test] + [Description("Альтернативное решение. Какие у него недостатки?")] + public void CheckCurrentTsar_WithCustomEquality() + { + Assert.True(AreEqual(actualTsar, expectedTsar)); + + // Какие недостатки у такого подхода? + // 1. При большой родословной будут проверяться все предки. И, при наличии расхождения у родителей, нам будет сложно понять в каком объекте произошло падение теста + // 2. Из названия теста непонятно, что именно значит CustomEquality и какое у него поведение + // 3. Не показывается информация, на каком именно объекте тест упал, что усложняет разработку + // 4. При расширении класса Person нужно будет дописывать AreEqual, а в FluentAssertions достаточно указать IncludingFields в конфигурации и все поля автоматически будут сравниваться + // 5. Повышается читабельность и тесты проще писать + + // Улучшайзинги + // 1. Также я вынес инициализацию объектов в метод SetUp, помеченный соответствующим атрибутом + // 2. Для структурированности проекта вынес тесты в папку Tests + } + + 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); + } + } +} \ No newline at end of file From 995aded4cba7a1d97debc2eca184207d70f85fa7 Mon Sep 17 00:00:00 2001 From: caiman Date: Tue, 28 Nov 2023 00:16:11 +0500 Subject: [PATCH 2/6] refactored tests for NumberValidator.cs --- ...erValidatorTests.cs => NumberValidator.cs} | 27 +--- .../Tests/NumberValidator_Should.cs | 130 ++++++++++++++++++ .../Tests/ObjectComparsion_Should.cs | 1 + 3 files changed, 132 insertions(+), 26 deletions(-) rename cs/HomeExercises/{NumberValidatorTests.cs => NumberValidator.cs} (64%) create mode 100644 cs/HomeExercises/Tests/NumberValidator_Should.cs diff --git a/cs/HomeExercises/NumberValidatorTests.cs b/cs/HomeExercises/NumberValidator.cs similarity index 64% rename from cs/HomeExercises/NumberValidatorTests.cs rename to cs/HomeExercises/NumberValidator.cs index a2878113..d0f5fb80 100644 --- a/cs/HomeExercises/NumberValidatorTests.cs +++ b/cs/HomeExercises/NumberValidator.cs @@ -1,34 +1,9 @@ using System; using System.Text.RegularExpressions; -using FluentAssertions; -using NUnit.Framework; 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")); - } - } + // тесты в Tests/NumberValues_Should public class NumberValidator { diff --git a/cs/HomeExercises/Tests/NumberValidator_Should.cs b/cs/HomeExercises/Tests/NumberValidator_Should.cs new file mode 100644 index 00000000..0e13b494 --- /dev/null +++ b/cs/HomeExercises/Tests/NumberValidator_Should.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections; +using FluentAssertions; +using NUnit.Framework; + +namespace HomeExercises.Tests +{ + public class NumberValidator_Should + { + #region ConstructorTestsSources + + private static IEnumerable IncorrectConstructorParamsTests() + { + yield return new TestCaseData(-1, 2, true) + .SetName("Constructor_ThrowsArgumentExceptionOnNegativePrecision"); + yield return new TestCaseData(1, 2, true) + .SetName("Constructor_ThrowsArgumentExceptionOnPrecisionLessThanScale"); + yield return new TestCaseData(1, 1, true) + .SetName("Constructor_ThrowsArgumentExceptionSamePrecisionAndScale"); + yield return new TestCaseData(1, -1, true) + .SetName("Constructor_ThrowsArgumentExceptionOnNegativeScale"); + } + + private static IEnumerable CorrectConstructorParamsTests() + { + yield return new TestCaseData(2, 1, true) + .SetName("Constructor_WorksWhenPrecisionIsNonNegativeAndGreaterThanScale"); + yield return new TestCaseData(2, 1, false) + .SetName("Constructor_WorksWhenPrecisionIsNonNegativeAndGreaterThanScale"); + } + + #endregion + + #region IsValidNumberTestsSources + + private static IEnumerable IsValidNumberPrecisionTests() + { + yield return new TestCaseData(3, 2, true, "00.00") + .SetName("IsValidNumber_ReturnsFalse_WhenSumOfIntPartAndFracPartIsGreaterThanPrecision") + .Returns(false); + yield return new TestCaseData(3, 2, true, "-0.00") + .SetName("IsValidNumber_ReturnsFalse_WhenSignWithFracAndIntPartsIsGreaterThanPrecision") + .Returns(false); + yield return new TestCaseData(3, 2, true, "+0.00") + .SetName("IsValidNumber_ReturnsFalse_WhenSignWithFracAndIntPartsIsGreaterThanPrecision") + .Returns(false); + + yield return new TestCaseData(17, 2, true, "0.0") + .SetName("IsValidNumber_ReturnsTrue_WhenSumOfIntPartAndFracPartIsNotGreaterThanPrecision") + .Returns(true); + yield return new TestCaseData(17, 2, true, "0") + .SetName("IsValidNumber_ReturnsTrue_WhenIntPartIsNotGreaterThanPrecision") + .Returns(true); + yield return new TestCaseData(17, 2, true, "+0.0") + .SetName("IsValidNumber_ReturnsTrue_WhenSumOfIntPartAndFracPartIsNotGreaterThanPrecision") + .Returns(true); + } + + private static IEnumerable IsValidNumberScaleTests() + { + yield return new TestCaseData(17, 2, true, "0.111") + .SetName("IsValidNumber_ReturnsFalse_WhenFracPartIsGreaterThanScale") + .Returns(false); + + yield return new TestCaseData(17, 2, true, "0.11") + .SetName("IsValidNumber_ReturnsTrue_WhenFracPartIsNotGreaterThanScale") + .Returns(true); + } + + private static IEnumerable IsValidNumberSignTests() + { + yield return new TestCaseData(17, 2, true, "-0.11") + .SetName("IsValidNumber_ReturnsFalse_WhenSignIsNegativeWithOnlyPositive") + .Returns(false); + + yield return new TestCaseData(17, 2, true, "+0.11") + .SetName("IsValidNumber_ReturnsTrue_WhenSignIsPositiveWithOnlyPositive") + .Returns(true); + yield return new TestCaseData(17, 2, false, "+0.11") + .SetName("IsValidNumber_ReturnsTrue_WhenSignIsPositiveWithoutOnlyPositive") + .Returns(true); + yield return new TestCaseData(17, 2, false, "-0.11") + .SetName("IsValidNumber_ReturnsTrue_WhenSignIsNegativeWithoutOnlyPositive") + .Returns(true); + } + + private static IEnumerable IsValidNumberValueTests() + { + yield return new TestCaseData(17, 2, true, null) + .SetName("IsValidNumber_ReturnsFalse_WhenNullIsGiven") + .Returns(false); + yield return new TestCaseData(17, 2, true, "") + .SetName("IsValidNumber_ReturnsFalse_WhenEmptyStringIsGiven") + .Returns(false); + yield return new TestCaseData(17, 2, true, "a.a") + .SetName("IsValidNumber_ReturnsFalse_WhenNonDigitStringIsGiven") + .Returns(false); + } + + #endregion + + [TestCaseSource(nameof(IncorrectConstructorParamsTests))] + public void FailsWithIncorrectConstructorArguments(int precision, int scale, bool onlyPositive) + { + new Func(() => new NumberValidator(precision, scale, onlyPositive)) + .Should() + .ThrowExactly(); + } + + [TestCaseSource(nameof(CorrectConstructorParamsTests))] + public void InitsWithCorrectConstructorArguments(int precision, int scale, bool onlyPositive) + { + new Func(() => new NumberValidator(precision, scale, onlyPositive)) + .Should() + .NotThrow(); + } + + [ + TestCaseSource(nameof(IsValidNumberPrecisionTests)), + TestCaseSource(nameof(IsValidNumberScaleTests)), + TestCaseSource(nameof(IsValidNumberSignTests)), + TestCaseSource(nameof(IsValidNumberValueTests)), + ] + public bool IsValidNumber(int precision, int scale, bool onlyPositive, string value) + { + return new Func(() => new NumberValidator(precision, scale, onlyPositive))() + .IsValidNumber(value); + } + } +} \ No newline at end of file diff --git a/cs/HomeExercises/Tests/ObjectComparsion_Should.cs b/cs/HomeExercises/Tests/ObjectComparsion_Should.cs index e3f1609d..cccb1c24 100644 --- a/cs/HomeExercises/Tests/ObjectComparsion_Should.cs +++ b/cs/HomeExercises/Tests/ObjectComparsion_Should.cs @@ -43,6 +43,7 @@ public void CheckCurrentTsar_WithCustomEquality() // 3. Не показывается информация, на каком именно объекте тест упал, что усложняет разработку // 4. При расширении класса Person нужно будет дописывать AreEqual, а в FluentAssertions достаточно указать IncludingFields в конфигурации и все поля автоматически будут сравниваться // 5. Повышается читабельность и тесты проще писать + // 7. Не инкапсулирована функциональность сравнения внутри класса // Улучшайзинги // 1. Также я вынес инициализацию объектов в метод SetUp, помеченный соответствующим атрибутом From 1cd370194a2c35829175d5e8bf11a3c1ec17177a Mon Sep 17 00:00:00 2001 From: caiman Date: Tue, 28 Nov 2023 18:56:53 +0500 Subject: [PATCH 3/6] specified error message for constructor fails --- cs/HomeExercises/Tests/NumberValidator_Should.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cs/HomeExercises/Tests/NumberValidator_Should.cs b/cs/HomeExercises/Tests/NumberValidator_Should.cs index 0e13b494..7b592b86 100644 --- a/cs/HomeExercises/Tests/NumberValidator_Should.cs +++ b/cs/HomeExercises/Tests/NumberValidator_Should.cs @@ -104,7 +104,8 @@ public void FailsWithIncorrectConstructorArguments(int precision, int scale, boo { new Func(() => new NumberValidator(precision, scale, onlyPositive)) .Should() - .ThrowExactly(); + .ThrowExactly() + .Where(e => e.Message.Contains("precision")); } [TestCaseSource(nameof(CorrectConstructorParamsTests))] From 3f84a0c1c444774d7e3c330e07955b242345d295 Mon Sep 17 00:00:00 2001 From: caiman Date: Tue, 28 Nov 2023 19:58:11 +0500 Subject: [PATCH 4/6] added testcase to failing constructor tests refactored message checking on constructor fails switched to another excluding overload to handle Id in Person --- cs/HomeExercises/Tests/NumberValidator_Should.cs | 14 ++++++++------ cs/HomeExercises/Tests/ObjectComparsion_Should.cs | 6 ++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cs/HomeExercises/Tests/NumberValidator_Should.cs b/cs/HomeExercises/Tests/NumberValidator_Should.cs index 7b592b86..5d252ba8 100644 --- a/cs/HomeExercises/Tests/NumberValidator_Should.cs +++ b/cs/HomeExercises/Tests/NumberValidator_Should.cs @@ -11,13 +11,15 @@ public class NumberValidator_Should private static IEnumerable IncorrectConstructorParamsTests() { - yield return new TestCaseData(-1, 2, true) + yield return new TestCaseData(-1, 2, true, "precision must be a positive number") .SetName("Constructor_ThrowsArgumentExceptionOnNegativePrecision"); - yield return new TestCaseData(1, 2, true) + yield return new TestCaseData(0, 2, true, "precision must be a positive number") + .SetName("Constructor_ThrowsArgumentExceptionOnZeroPrecision"); + yield return new TestCaseData(1, 2, true, "precision must be a non-negative number less or equal than precision") .SetName("Constructor_ThrowsArgumentExceptionOnPrecisionLessThanScale"); - yield return new TestCaseData(1, 1, true) + yield return new TestCaseData(1, 1, true, "precision must be a non-negative number less or equal than precision") .SetName("Constructor_ThrowsArgumentExceptionSamePrecisionAndScale"); - yield return new TestCaseData(1, -1, true) + yield return new TestCaseData(1, -1, true, "precision must be a non-negative number less or equal than precision") .SetName("Constructor_ThrowsArgumentExceptionOnNegativeScale"); } @@ -100,12 +102,12 @@ private static IEnumerable IsValidNumberValueTests() #endregion [TestCaseSource(nameof(IncorrectConstructorParamsTests))] - public void FailsWithIncorrectConstructorArguments(int precision, int scale, bool onlyPositive) + public void FailsWithIncorrectConstructorArguments(int precision, int scale, bool onlyPositive, string message) { new Func(() => new NumberValidator(precision, scale, onlyPositive)) .Should() .ThrowExactly() - .Where(e => e.Message.Contains("precision")); + .Where(e => e.Message.Equals(message, StringComparison.OrdinalIgnoreCase)); } [TestCaseSource(nameof(CorrectConstructorParamsTests))] diff --git a/cs/HomeExercises/Tests/ObjectComparsion_Should.cs b/cs/HomeExercises/Tests/ObjectComparsion_Should.cs index cccb1c24..e0264dd5 100644 --- a/cs/HomeExercises/Tests/ObjectComparsion_Should.cs +++ b/cs/HomeExercises/Tests/ObjectComparsion_Should.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using FluentAssertions.Equivalency; using NUnit.Framework; namespace HomeExercises.Tests @@ -24,10 +25,7 @@ public void CheckCurrentTsar() actualTsar.Should().BeEquivalentTo(expectedTsar, options => options .IncludingFields() - .Excluding(p => p.Id) - .Excluding(p => p.Parent!.Id) - .Excluding(p => p.Parent!.Weight) - .Excluding(p => p.Parent!.Parent) + .Excluding(memberInfo => memberInfo.SelectedMemberInfo.Name == "Id") ); } From 36fe5aa61e8cb401644ec5274551aa7a55ad2b6f Mon Sep 17 00:00:00 2001 From: caiman Date: Tue, 28 Nov 2023 20:05:32 +0500 Subject: [PATCH 5/6] refactored to nameof in case of renaming the field --- cs/HomeExercises/Tests/ObjectComparsion_Should.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cs/HomeExercises/Tests/ObjectComparsion_Should.cs b/cs/HomeExercises/Tests/ObjectComparsion_Should.cs index e0264dd5..9a99d857 100644 --- a/cs/HomeExercises/Tests/ObjectComparsion_Should.cs +++ b/cs/HomeExercises/Tests/ObjectComparsion_Should.cs @@ -25,7 +25,7 @@ public void CheckCurrentTsar() actualTsar.Should().BeEquivalentTo(expectedTsar, options => options .IncludingFields() - .Excluding(memberInfo => memberInfo.SelectedMemberInfo.Name == "Id") + .Excluding(memberInfo => memberInfo.SelectedMemberInfo.Name == nameof(Person.Id)) ); } From 75bbb8f5444a6fcf0d7ada2efb37b5467f90c97c Mon Sep 17 00:00:00 2001 From: caiman Date: Tue, 28 Nov 2023 20:11:56 +0500 Subject: [PATCH 6/6] added checking if the object is typeof Person --- cs/HomeExercises/Tests/ObjectComparsion_Should.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cs/HomeExercises/Tests/ObjectComparsion_Should.cs b/cs/HomeExercises/Tests/ObjectComparsion_Should.cs index 9a99d857..d5ef0e8f 100644 --- a/cs/HomeExercises/Tests/ObjectComparsion_Should.cs +++ b/cs/HomeExercises/Tests/ObjectComparsion_Should.cs @@ -25,8 +25,9 @@ public void CheckCurrentTsar() actualTsar.Should().BeEquivalentTo(expectedTsar, options => options .IncludingFields() - .Excluding(memberInfo => memberInfo.SelectedMemberInfo.Name == nameof(Person.Id)) - ); + .Excluding(memberInfo => + memberInfo.SelectedMemberInfo.Name == nameof(Person.Id) + && memberInfo.SelectedMemberInfo.DeclaringType == typeof(Person))); } [Test]