From 7a57337a6e1daa87c996ba4b133326b693295d3c Mon Sep 17 00:00:00 2001 From: Matvey Date: Tue, 28 Oct 2025 17:55:14 +0500 Subject: [PATCH 1/4] testing homework completed with ObjectComparison.cs and NumberValidatorTests.cs --- .../1. ObjectComparison/ObjectComparison.cs | 74 ++++++++++-- .../NumberValidatorTests.cs | 106 ++++++++++++++---- 2 files changed, 147 insertions(+), 33 deletions(-) diff --git a/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs b/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs index d544c47..8886f66 100644 --- a/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs +++ b/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs @@ -1,7 +1,9 @@ -using NUnit.Framework; +using FluentAssertions; +using NUnit.Framework; using NUnit.Framework.Legacy; namespace HomeExercise.Tasks.ObjectComparison; + public class ObjectComparison { [Test] @@ -14,16 +16,37 @@ 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); + actualTsar.Should().BeEquivalentTo(expectedTsar, config => config + .Excluding(x => x.Id) + .Excluding(x => x.Parent.Id)); + + /* + Заменяем на Should().BeEquivalentTo() из Fluent Assertions. + BeEquivalentTo проверяет два объекта необязательно одного класса на совпадение одноименных полей, + на совпадение полей объектов, которые могут находиться в качестве ссылок в полях сравниваемых объектов + (как в случае с Parent внутри класса Person) + Здесь не хватит цепочки Should().BeEquivalentTo(), ведь метод сравнивает все поля + (в том числе поля объектов, которые лежат в полях в виде ссылки сравниваемого объекта), + и здесь нам мешают Id, ведь в тесте мы создаем новые объекты класса, Id у объектов разные + Мы работаем с настройками данного assertion-метода, исключая из проверки поля с айдишниками + Считаю, что лучше исключать конкретные поля, даже если их несколько, не используя EndsWith, чтобы + не возникало проблем с тем, что чудом может появится поле, которое оканчивается на Id и его необходимо + включать в проверку, а с EndsWith мы его пропустим. + Если actualTsar == null || expectedTsar == null, то выдает false, как и предполагается. - 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); + Уточнения: + 1. Если мы захотим создать класс, похожий на Person и сравнить, то проверка пройдет, но необходимо + осознавать, что если в actual будет какое-то поле, которого нет в expected, то по проверке объекты не + будут "эквивалентны", а если наоборот, в expected найдутся все поля actual, но expected будет содержать + лишние поля, то тест будет пройден успешно, поэтому разные классы лучше не использовать, такое "расширение" + может ломать логику теста и выдавать некорректные результаты без внесения изменений + 2. Такое построение теста дает полную возможность добавлять практически любые поля без внесения + изменений в тест, если мы остаемся в рамках одно и того же класса, ведь метод сам пробегается по всем полям + без уточнений в его параметрах или в тесте в целом. Исключение и проблема - поля, которые схожи с Id, + то есть являются какой-то "внутрянкой" и используются исключительно при работе с объектами класса в коде, + при их появлении тест начнет падать, но хотя бы этим падением даст о себе знать и разработчик сможет + быстро добавить лишний .Excluding() в тест + */ } [Test] @@ -49,4 +72,33 @@ private bool AreEqual(Person? actual, Person? expected) && actual.Weight == expected.Weight && AreEqual(actual.Parent, expected.Parent); } -} + + /* + Недостатки альтернативного решения: + + 1. Альтернативное решение не позволяет спокойно расширять функциональность класса, а точнее добавлять новые поля + в класс, объекты которого мы сравниваем. + Мы в любом случае должны вносить изменения в AreEqual, который при всем при этом лежит внутри файла с тестами, + то есть он не является методом класса, объекты которого мы сравниваем. Разработчик мог бы сразу изменить + подобный метод сравнения при появлении новых полей, если бы он использовался в рамках работы с классом вне тестов + В данном случае тест упадет при появлении новых полей, если разработчик не знает о внутрянках тестов или не лезет + туда вообще, хотя появление новых полей (исключая что-то по типу Id) никак не должно влиять на исход работы теста + + 2. Альтернативное решение при запуске теста и появлении какого-то несовпадения не дает никаких подробностей + по тому, что вообще произошло. При использовании булевого утверждения увидим, что Expected: True, But was: False + Данная информация не помогает проблему решить, а использование Fluent Assertions дает подробную информацию + + 3. Читаемость: в целом очень легко понять, что делает данный тест, не заглядывая в AreEqual. + Однако, если мы хотим вникнуть в подробности, что конкретно происходит в тесте, то мы опускаем глаза и видим + не очень большой но неприятный отрывок кода, в котором необходимо разобраться. Тут даже используется рекурсия, + чтобы проверять объект по ссылке на родителя (рекурсия имеет свойство вызывать переполнение стека, чисто + теоретически это может произойти и здесь, если человек чудом сам себе родитель, нет проверки на + циклические ссылки). Относительно "моего" решения разница безусловно есть, но назвать читаемость явной + проблемой этого решения нельзя, слегка приятнуто за уши + + Небольшой плюс альтернативного решения: + Оно будет быстрее использования BeEquivalentTo(), по информации из интернета метод из Fluent Assertions + использует рефлексию и может быть в 10 раз медленее альтернативного решения, что в целом не так критично + исходя из сложности обоих методов, отсюда вытекает и небольшой минус выбранного мной решения. + */ +} \ No newline at end of file diff --git a/Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs b/Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs index 950c9bc..4664f90 100644 --- a/Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs +++ b/Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs @@ -1,31 +1,93 @@ - -using NUnit.Framework; +using NUnit.Framework; using NUnit.Framework.Legacy; +using FluentAssertions; namespace HomeExercise.Tasks.NumberValidator; [TestFixture] 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)); - - 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")); + [TestCase(0, 2, TestName = "Precision is zero")] + [TestCase(1, -1, TestName = "Scale is negative")] + [TestCase(1, 2, TestName = "Precision is less than scale")] + [TestCase(1, 1, TestName = "Precision equals scale")] + public void Constructor_ThrowsException_OnInvalidArguments(int precision, int scale) + { + Action constructor = () => new NumberValidator(precision, scale); + constructor.Should().Throw(); + } + + [TestCase(1, 0, TestName = "Precision and scale are minimum possible")] + [TestCase(1000, 500, TestName = "Precision and scale are big enough")] + public void Constructor_DoesNotThrowException_OnValidArguments(int precision, int scale) + { + Action constructor = () => new NumberValidator(precision, scale, true); + constructor.Should().NotThrow(); + } + + [TestCase("a.sd", TestName = "Input contains letters")] + [TestCase("", TestName = "Input is empty string")] + [TestCase(null, TestName = "Input is null")] + [TestCase("12+3", TestName = "Input contains invalid characters")] + public void Validator_ReturnsFalse_OnInvalidInputFormats(string input) + { + var validator = new NumberValidator(5); + validator.IsValidNumber(input).Should().BeFalse(); + } + + [TestCase(1, 0, "0", TestName = "One zero")] + [TestCase(2, 1, "0.0", TestName = "One zero with fraction part")] + [TestCase(3, 1, "00.0", TestName = "Two leading zeros with fraction part")] + public void Validator_ReturnsTrue_WithProperZeros(int precision, int scale, string input) + { + var validator = new NumberValidator(precision, scale); + validator.IsValidNumber(input).Should().BeTrue(); + } + + [TestCase(6, 0, "1234", TestName = "Simple integer")] + [TestCase(20, 10, "123.54", TestName = "Number with fraction part")] + public void Validator_ReturnsTrue_WithProperNumbers(int precision, int scale, string input) + { + var validator = new NumberValidator(precision, scale); + validator.IsValidNumber(input).Should().BeTrue(); + } + + [TestCase("1234567", TestName = "Precision overflow with numerals")] + [TestCase("0000000", TestName = "Precision overflow with zeros")] + public void Validator_ReturnsFalse_WithPrecisionOverflow(string input) + { + var validator = new NumberValidator(6); + validator.IsValidNumber(input).Should().BeFalse(); + } + + [TestCase("1.23456", TestName = "Scale overflow with numerals")] + [TestCase("0.00000", TestName = "Scale overflow with zeros")] + public void Validator_ReturnsFalse_WithScaleOverflow(string input) + { + var validator = new NumberValidator(10, 4); + validator.IsValidNumber(input).Should().BeFalse(); + } + + [TestCase("-123", TestName = "Negative integer")] + [TestCase("-0", TestName = "Negative zero")] + public void Validator_ReturnsFalse_WithNegativeNumberWhenNotAllowed(string input) + { + var validator = new NumberValidator(10, 0, true); + validator.IsValidNumber(input).Should().BeFalse(); + } + + [TestCase("-1.23", TestName = "Negative float")] + [TestCase("+1.23", TestName = "Positive float")] + public void Validator_ReturnsFalse_WithSignPrecisionOverflow(string input) + { + var validator = new NumberValidator(3, 2); + validator.IsValidNumber(input).Should().BeFalse(); + } + + [TestCase] + public void Validator_ReturnsTrue_WithCommaSeparator() + { + var validator = new NumberValidator(5, 2); + validator.IsValidNumber("12,34").Should().BeTrue(); } } \ No newline at end of file From f4580522e94a362d51e78566555c8cdd954b2488 Mon Sep 17 00:00:00 2001 From: Matvey Date: Wed, 29 Oct 2025 21:17:58 +0500 Subject: [PATCH 2/4] ObjectComparison.cs tests refactor based on review feedback --- .../1. ObjectComparison/ObjectComparison.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs b/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs index 8886f66..51dfa0d 100644 --- a/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs +++ b/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs @@ -8,17 +8,14 @@ public class ObjectComparison { [Test] [Description("Проверка текущего царя")] - [Category("ToRefactor")] - public void CheckCurrentTsar() + public void TsarRegistry_ShouldReturnExpectedCurrentTsar_Test() { 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 = GetExpectedCurrentTsar(); actualTsar.Should().BeEquivalentTo(expectedTsar, config => config - .Excluding(x => x.Id) - .Excluding(x => x.Parent.Id)); + .Excluding(info => info.Name == "Id") + .IgnoringCyclicReferences()); /* Заменяем на Should().BeEquivalentTo() из Fluent Assertions. @@ -51,11 +48,10 @@ быстро добавить лишний .Excluding() в тест [Test] [Description("Альтернативное решение. Какие у него недостатки?")] - public void CheckCurrentTsar_WithCustomEquality() + public void TsarRegistry_ShouldReturnExpectedCurrentTsar_WithCustomEquality_Test() { 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 = GetExpectedCurrentTsar(); // Какие недостатки у такого подхода? ClassicAssert.True(AreEqual(actualTsar, expectedTsar)); @@ -73,6 +69,12 @@ private bool AreEqual(Person? actual, Person? expected) && AreEqual(actual.Parent, expected.Parent); } + private static Person GetExpectedCurrentTsar() + { + var expectedTsarParent = new Person("Vasili III of Russia", 28, 170, 60, null); + return new Person("Ivan IV The Terrible", 54, 170, 70, expectedTsarParent); + } + /* Недостатки альтернативного решения: @@ -92,8 +94,8 @@ 2. Альтернативное решение при запуске теста Однако, если мы хотим вникнуть в подробности, что конкретно происходит в тесте, то мы опускаем глаза и видим не очень большой но неприятный отрывок кода, в котором необходимо разобраться. Тут даже используется рекурсия, чтобы проверять объект по ссылке на родителя (рекурсия имеет свойство вызывать переполнение стека, чисто - теоретически это может произойти и здесь, если человек чудом сам себе родитель, нет проверки на - циклические ссылки). Относительно "моего" решения разница безусловно есть, но назвать читаемость явной + теоретически это может произойти и здесь, если человек чудом сам себе родитель, нет проверки на + циклические ссылки). Относительно "моего" решения разница безусловно есть, но назвать читаемость явной проблемой этого решения нельзя, слегка приятнуто за уши Небольшой плюс альтернативного решения: From 8df83ef1812856032add1e2c62d9ec2412f34348 Mon Sep 17 00:00:00 2001 From: Matvey Date: Wed, 29 Oct 2025 21:32:04 +0500 Subject: [PATCH 3/4] fixed error with incorrect .Excluding expression in ObjectComparison.cs test --- Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs b/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs index 51dfa0d..f8ae354 100644 --- a/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs +++ b/Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using FluentAssertions.Equivalency; using NUnit.Framework; using NUnit.Framework.Legacy; @@ -14,7 +15,7 @@ public void TsarRegistry_ShouldReturnExpectedCurrentTsar_Test() var expectedTsar = GetExpectedCurrentTsar(); actualTsar.Should().BeEquivalentTo(expectedTsar, config => config - .Excluding(info => info.Name == "Id") + .Excluding((IMemberInfo info) => info.Name == "Id") .IgnoringCyclicReferences()); /* From 766f520ffa8bda16a8c9de84bad90f145e3358f6 Mon Sep 17 00:00:00 2001 From: Matvey Date: Wed, 29 Oct 2025 22:04:44 +0500 Subject: [PATCH 4/4] NumberValidator refactor based on review feedback --- .../2. NumberValidator/NumberValidator.cs | 2 +- .../NumberValidatorTests.cs | 52 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Testing/Basic/Homework/2. NumberValidator/NumberValidator.cs b/Testing/Basic/Homework/2. NumberValidator/NumberValidator.cs index 327ce9c..55c3c61 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 or equal 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 4664f90..959ebe5 100644 --- a/Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs +++ b/Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs @@ -1,5 +1,4 @@ using NUnit.Framework; -using NUnit.Framework.Legacy; using FluentAssertions; namespace HomeExercise.Tasks.NumberValidator; @@ -7,21 +6,21 @@ namespace HomeExercise.Tasks.NumberValidator; [TestFixture] public class NumberValidatorTests { - [TestCase(0, 2, TestName = "Precision is zero")] - [TestCase(1, -1, TestName = "Scale is negative")] - [TestCase(1, 2, TestName = "Precision is less than scale")] - [TestCase(1, 1, TestName = "Precision equals scale")] - public void Constructor_ThrowsException_OnInvalidArguments(int precision, int scale) + [TestCase(0, 2, "positive", TestName = "Precision is zero")] + [TestCase(1, -1, "non-negative", TestName = "Scale is negative")] + [TestCase(1, 2, "less or equal", TestName = "Precision is less than scale")] + [TestCase(1, 1, "less or equal", TestName = "Precision equals scale")] + public void Constructor_ThrowsException_OnInvalidArguments_Test(int precision, int scale, string expectedMessage) { - Action constructor = () => new NumberValidator(precision, scale); - constructor.Should().Throw(); + var constructor = () => new NumberValidator(precision, scale); + constructor.Should().Throw().WithMessage($"*{expectedMessage}*"); } [TestCase(1, 0, TestName = "Precision and scale are minimum possible")] - [TestCase(1000, 500, TestName = "Precision and scale are big enough")] - public void Constructor_DoesNotThrowException_OnValidArguments(int precision, int scale) + [TestCase(2, 1, TestName = "Highest valid scale below precision")] + public void Constructor_DoesNotThrowException_OnBoundaryValidArguments_Test(int precision, int scale) { - Action constructor = () => new NumberValidator(precision, scale, true); + var constructor = () => new NumberValidator(precision, scale, true); constructor.Should().NotThrow(); } @@ -29,7 +28,7 @@ public void Constructor_DoesNotThrowException_OnValidArguments(int precision, in [TestCase("", TestName = "Input is empty string")] [TestCase(null, TestName = "Input is null")] [TestCase("12+3", TestName = "Input contains invalid characters")] - public void Validator_ReturnsFalse_OnInvalidInputFormats(string input) + public void Validator_ReturnsFalse_OnInvalidInputFormats_Test(string input) { var validator = new NumberValidator(5); validator.IsValidNumber(input).Should().BeFalse(); @@ -38,31 +37,31 @@ public void Validator_ReturnsFalse_OnInvalidInputFormats(string input) [TestCase(1, 0, "0", TestName = "One zero")] [TestCase(2, 1, "0.0", TestName = "One zero with fraction part")] [TestCase(3, 1, "00.0", TestName = "Two leading zeros with fraction part")] - public void Validator_ReturnsTrue_WithProperZeros(int precision, int scale, string input) + public void Validator_ReturnsTrue_WithProperZeros_Test(int precision, int scale, string input) { var validator = new NumberValidator(precision, scale); validator.IsValidNumber(input).Should().BeTrue(); } - [TestCase(6, 0, "1234", TestName = "Simple integer")] - [TestCase(20, 10, "123.54", TestName = "Number with fraction part")] - public void Validator_ReturnsTrue_WithProperNumbers(int precision, int scale, string input) + [TestCase(4, 0, "1234", TestName = "Simple integer")] + [TestCase(5, 2, "123.54", TestName = "Number with fraction part")] + public void Validator_ReturnsTrue_WithProperNumbers_Test(int precision, int scale, string input) { var validator = new NumberValidator(precision, scale); validator.IsValidNumber(input).Should().BeTrue(); } - [TestCase("1234567", TestName = "Precision overflow with numerals")] - [TestCase("0000000", TestName = "Precision overflow with zeros")] - public void Validator_ReturnsFalse_WithPrecisionOverflow(string input) + [TestCase("123", TestName = "Precision overflow with numerals")] + [TestCase("000", TestName = "Precision overflow with zeros")] + public void Validator_ReturnsFalse_WithPrecisionOverflow_Test(string input) { - var validator = new NumberValidator(6); + var validator = new NumberValidator(2); validator.IsValidNumber(input).Should().BeFalse(); } [TestCase("1.23456", TestName = "Scale overflow with numerals")] [TestCase("0.00000", TestName = "Scale overflow with zeros")] - public void Validator_ReturnsFalse_WithScaleOverflow(string input) + public void Validator_ReturnsFalse_WithScaleOverflow_Test(string input) { var validator = new NumberValidator(10, 4); validator.IsValidNumber(input).Should().BeFalse(); @@ -70,7 +69,7 @@ public void Validator_ReturnsFalse_WithScaleOverflow(string input) [TestCase("-123", TestName = "Negative integer")] [TestCase("-0", TestName = "Negative zero")] - public void Validator_ReturnsFalse_WithNegativeNumberWhenNotAllowed(string input) + public void Validator_ReturnsFalse_WithNegativeNumberWhenNotAllowed_Test(string input) { var validator = new NumberValidator(10, 0, true); validator.IsValidNumber(input).Should().BeFalse(); @@ -78,16 +77,17 @@ public void Validator_ReturnsFalse_WithNegativeNumberWhenNotAllowed(string input [TestCase("-1.23", TestName = "Negative float")] [TestCase("+1.23", TestName = "Positive float")] - public void Validator_ReturnsFalse_WithSignPrecisionOverflow(string input) + public void Validator_ReturnsFalse_WithSignPrecisionOverflow_Test(string input) { var validator = new NumberValidator(3, 2); validator.IsValidNumber(input).Should().BeFalse(); } - [TestCase] - public void Validator_ReturnsTrue_WithCommaSeparator() + [TestCase("12,34", TestName = "Comma separator")] + [TestCase("12.34", TestName = "Dot separator")] + public void Validator_ReturnsTrue_WithCommaSeparator_Test(string input) { var validator = new NumberValidator(5, 2); - validator.IsValidNumber("12,34").Should().BeTrue(); + validator.IsValidNumber(input).Should().BeTrue(); } } \ No newline at end of file