diff --git a/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs b/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs index d544c47..0fbdfe1 100644 --- a/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs +++ b/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using FluentAssertions; +using NUnit.Framework; using NUnit.Framework.Legacy; namespace HomeExercise.Tasks.ObjectComparison; @@ -6,7 +7,6 @@ public class ObjectComparison { [Test] [Description("Проверка текущего царя")] - [Category("ToRefactor")] public void CheckCurrentTsar() { var actualTsar = TsarRegistry.GetCurrentTsar(); @@ -14,16 +14,11 @@ public void CheckCurrentTsar() var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70, new Person("Vasili III of Russia", 28, 170, 60, null)); - // Перепишите код на использование Fluent Assertions. - ClassicAssert.AreEqual(actualTsar.Name, expectedTsar.Name); - ClassicAssert.AreEqual(actualTsar.Age, expectedTsar.Age); - ClassicAssert.AreEqual(actualTsar.Height, expectedTsar.Height); - ClassicAssert.AreEqual(actualTsar.Weight, expectedTsar.Weight); - - ClassicAssert.AreEqual(expectedTsar.Parent!.Name, actualTsar.Parent!.Name); - ClassicAssert.AreEqual(expectedTsar.Parent.Age, actualTsar.Parent.Age); - ClassicAssert.AreEqual(expectedTsar.Parent.Height, actualTsar.Parent.Height); - ClassicAssert.AreEqual(expectedTsar.Parent.Parent, actualTsar.Parent.Parent); + actualTsar.Should().BeEquivalentTo( + expectedTsar, + options => options + .Excluding(member => member.DeclaringType == typeof(Person) + && member.Name == nameof(Person.Id))); } [Test] @@ -34,7 +29,18 @@ public void CheckCurrentTsar_WithCustomEquality() var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70, new Person("Vasili III of Russia", 28, 170, 60, null)); - // Какие недостатки у такого подхода? + // Какие недостатки у такого подхода? + // + // Решение с FluentAssertions лучше тем, что оно: + // 1. позволяет не изменять проверяющую на равенство часть метода при добавлении новых полей и свойств классу Person. + // Они автоматически будут проверяться. + // Если проверять их не нужно, их можно исключить из проверки, добавив Excluding. + // 2. лучше читается. + // Видно, что объекты сравниваются по всем полям, кроме Id. + // В альтернативном решении нужно искать, какие поля присутствуют в сравнении, а какие - нет. + // 3. работает для каждого типа. + // Альтернативное решение предполагает, что мы должны каждый раз писать новый метод AreEqual. + ClassicAssert.True(AreEqual(actualTsar, expectedTsar)); } diff --git a/Testing/Basic/Homework/2. NumberValidator/NumberValidator.cs b/Testing/Basic/Homework/2. NumberValidator/NumberValidator.cs index 327ce9c..9ec611c 100644 --- a/Testing/Basic/Homework/2. NumberValidator/NumberValidator.cs +++ b/Testing/Basic/Homework/2. NumberValidator/NumberValidator.cs @@ -17,7 +17,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"); + throw new ArgumentException("scale must be a non-negative number less than precision"); numberRegex = new Regex(@"^([+-]?)(\d+)([.,](\d+))?$", RegexOptions.IgnoreCase); } diff --git a/Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs b/Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs index 950c9bc..de2b915 100644 --- a/Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs +++ b/Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs @@ -1,31 +1,242 @@ - +using FluentAssertions; using NUnit.Framework; -using NUnit.Framework.Legacy; namespace HomeExercise.Tasks.NumberValidator; [TestFixture] public class NumberValidatorTests { + private const string PrecisionOutOfRangeMessage = "precision must be a positive number"; + private const string ScaleOutOfRangeMessage = "scale must be a non-negative number less than precision"; + + [TestCase(0)] + [TestCase(-1)] + [TestCase(int.MinValue)] + [Description("Конструктор бросает исключение, если precision меньше или равен 0")] + public void Constructor_ThrowsException_WhenPrecisionIsNotPositive( + int precision) + { + var constructor = () => new NumberValidator(precision); + constructor.Should().Throw().WithMessage(PrecisionOutOfRangeMessage); + } + + [TestCase(-1)] + [TestCase(int.MinValue)] + [Description("Конструктор бросает исключение, если scale отрицателен")] + public void Constructor_ThrowsException_WhenScaleIsNegative( + int scale) + { + var constructor = () => new NumberValidator(1, scale); + constructor.Should().Throw().WithMessage(ScaleOutOfRangeMessage); + } + + [TestCase(1, 2)] + [TestCase(2, 2)] + [Description("Конструктор бросает исключение, если scale больше или равен precision")] + public void Constructor_ThrowsException_WhenScaleIsGreaterThanOrEqualToPrecision( + int precision, + int scale) + { + var constructor = () => new NumberValidator(precision, scale); + constructor.Should().Throw().WithMessage(ScaleOutOfRangeMessage); + } + + [TestCase(10, 5)] + [TestCase(10, 0)] + [TestCase(int.MaxValue, int.MaxValue - 1)] + [Description("Нет исключений в конструкторе, если параметры правильные")] + public void Constructor_DoesNotThrowException_WithCorrectParams( + int precision, + int scale) + { + var constructor = () => new NumberValidator(precision, scale); + constructor.Should().NotThrow(); + } + [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)); - - ClassicAssert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0.0")); - ClassicAssert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0")); - ClassicAssert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0.0")); - ClassicAssert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("00.00")); - ClassicAssert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("-0.00")); - ClassicAssert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0.0")); - ClassicAssert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("+0.00")); - ClassicAssert.IsTrue(new NumberValidator(4, 2, true).IsValidNumber("+1.23")); - ClassicAssert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("+1.23")); - ClassicAssert.IsFalse(new NumberValidator(17, 2, true).IsValidNumber("0.000")); - ClassicAssert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("-1.23")); - ClassicAssert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("a.sd")); - } -} \ No newline at end of file + [Description("Scale равен 0, если не указан явно")] + public void Constructor_SetsFractionalPartLengthToZero_ByDefault() + { + var numberValidator = new NumberValidator(10); + + numberValidator.IsValidNumber("1").Should().BeTrue(); + numberValidator.IsValidNumber("1.1").Should().BeFalse(); + } + + [Test] + [Description("Работает с отрицательными числами, если в конструкторе не указано обратное")] + public void IsValidNumber_ValidatesNegativeNumbers_IfNotDisabledInConstructor() + { + var numberValidator = new NumberValidator(20, 10); + + numberValidator.IsValidNumber("-1").Should().BeTrue(); + numberValidator.IsValidNumber("-1.1").Should().BeTrue(); + } + + [Test, Combinatorial] + [Description("Возвращает true, если строка - удовлетворяющее ограничениям целое число")] + public void IsValidNumber_ReturnsTrue_IfIntegerNumberIsValid( + [Values("", "-", "+")] string sign, + [Values("0", "1", "1234567890", "00", "01")] string number) + { + number = sign + number; + var numberValidator = new NumberValidator(number.Length, 0, false); + + numberValidator.IsValidNumber(number).Should().BeTrue(); + } + + [Test, Combinatorial] + [Description("Возвращает true, если строка - удовлетворяющее ограничениям число с дробной частью")] + public void IsValidNumber_ReturnsTrue_IfNumberWithFractionalPartIsValid( + [Values("", "-", "+")] string sign, + [Values("0", "1", "1234567890", "00", "01")] string intPart, + [Values(".", ",")] string decimalPoint, + [Values("0", "1", "1234567890", "00", "01")] string fracPart) + { + var number = sign + intPart + decimalPoint + fracPart; + var precision = sign.Length + intPart.Length + fracPart.Length; + var numberValidator = new NumberValidator(precision, fracPart.Length, false); + + numberValidator.IsValidNumber(number).Should().BeTrue(); + } + + [TestCase(1, 0, "00")] + [TestCase(3, 2, "00.00")] + [TestCase(1, 0, "11")] + [TestCase(2, 0, "-11")] + [TestCase(2, 1, "-1.1")] + [TestCase(2, 1, "11.1")] + [TestCase(3, 1, "-11.1")] + [Description("Возвращает false, если у числа много знаков в целой части")] + public void IsValidNumber_ReturnsFalse_IfTooManyDigits( + int precision, + int scale, + string number) + { + var numberValidator = new NumberValidator(precision, scale, false); + + numberValidator.IsValidNumber(number).Should().BeFalse(); + } + + [TestCase(2, 0, "0.0")] + [TestCase(3, 1, "0.00")] + [TestCase(2, 0, "1.1")] + [TestCase(3, 1, "1.11")] + [Description("Возвращает false, если у числа много знаков в дробной части")] + public void IsValidNumber_ReturnsFalse_IfTooManyDigitsInFractionalPart( + int precision, + int scale, + string number) + { + var numberValidator = new NumberValidator(precision, scale, false); + + numberValidator.IsValidNumber(number).Should().BeFalse(); + } + + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + [TestCase(" ")] + [Description("Возвращает false, если строка null или состоит из пробелов")] + public void IsValidNumber_ReturnsFalse_IfStringIsNullOrWhitespace( + string number) + { + var numberValidator = new NumberValidator(10, 9, false); + + numberValidator.IsValidNumber(number).Should().BeFalse(); + } + + [TestCaseSource(nameof(GetNotNumbers))] + [Description("Возвращает false, если строка не является числом")] + public void IsValidNumber_ReturnsFalse_IfNotNumber( + string number) + { + var numberValidator = new NumberValidator(number.Length, number.Length - 1, false); + + numberValidator.IsValidNumber(number).Should().BeFalse(); + } + + [TestCase("-0")] + [TestCase("-1")] + [TestCase("-1.1")] + [Description("Не работает с отрицательными числами, если они отключены")] + public void IsValidNumber_ReturnsFalse_IfNumberIsNegativeAndNegativeNumbersDisabled( + string number) + { + var numberValidator = new NumberValidator(number.Length, number.Length - 1, true); + + numberValidator.IsValidNumber(number).Should().BeFalse(); + } + + [TestCase("0")] + [TestCase("+0")] + [TestCase("1")] + [TestCase("+1")] + [TestCase("1.1")] + [TestCase("+1.1")] + [Description("Работает с положительными числами, если отрицательные отключены")] + public void IsValidNumber_ReturnsTrue_IfNumberIsPositiveAndNegativeNumbersAreDisabled( + string number) + { + var numberValidator = new NumberValidator(number.Length, number.Length - 1, true); + + numberValidator.IsValidNumber(number).Should().BeTrue(); + } + + [Test] + [Description("Работает с большими числами")] + public void IsValidNumber_ReturnsTrue_IfBigIntegerNumberIsValid() + { + var length = 10_000; + var number = new string('1', length); + var numberValidator = new NumberValidator(length, 0, false); + + numberValidator.IsValidNumber(number).Should().BeTrue(); + } + + [Test] + [Description("Работает с числами с большой дробной частью")] + public void IsValidNumber_ReturnsTrue_IfBigNumberWithFractionalPartIsValid() + { + var length = 10_000; + var number = $"0.{new string('1', length - 1)}"; + + var numberValidator = new NumberValidator(length, length - 1, false); + + numberValidator.IsValidNumber(number).Should().BeTrue(); + } + + [Test] + [Description("Проверка на нескольких валидаторах")] + public void IsValidNumber_Works_WithMultipleNumberValidatorInstances() + { + var number = "111.11"; + + var numberValidator1 = new NumberValidator(5, 2, false); + var numberValidator2 = new NumberValidator(3, 1, false); + + numberValidator1.IsValidNumber(number).Should().BeTrue(); + numberValidator2.IsValidNumber(number).Should().BeFalse(); + } + + private static IEnumerable GetNotNumbers() + { + yield return new TestCaseData("abc"); + yield return new TestCaseData("123abc"); + yield return new TestCaseData("123.abc"); + yield return new TestCaseData("abc.123"); + yield return new TestCaseData("."); + yield return new TestCaseData(","); + yield return new TestCaseData("1."); + yield return new TestCaseData(".1"); + yield return new TestCaseData("--1"); + yield return new TestCaseData("++1"); + yield return new TestCaseData("1.-1"); + yield return new TestCaseData("1.+1"); + yield return new TestCaseData("-.1"); + yield return new TestCaseData("+.1"); + yield return new TestCaseData(" 1"); + yield return new TestCaseData("1 "); + yield return new TestCaseData("1. "); + } +}