-
Notifications
You must be signed in to change notification settings - Fork 274
Савельев Григорий #234
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
base: master
Are you sure you want to change the base?
Савельев Григорий #234
Changes from 3 commits
8afbb05
61147d0
962b91c
ef911a9
6e8a83b
2eb7645
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| | ||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||
| # Visual Studio Version 17 | ||
| VisualStudioVersion = 17.5.002.0 | ||
| MinimumVisualStudioVersion = 10.0.40219.1 | ||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HomeExercises", "HomeExercises.csproj", "{B4229E79-0D13-4186-BD8D-89CAC9F1EA8A}" | ||
| EndProject | ||
| Global | ||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
| Debug|Any CPU = Debug|Any CPU | ||
| Release|Any CPU = Release|Any CPU | ||
| EndGlobalSection | ||
| GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
| {B4229E79-0D13-4186-BD8D-89CAC9F1EA8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| {B4229E79-0D13-4186-BD8D-89CAC9F1EA8A}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| {B4229E79-0D13-4186-BD8D-89CAC9F1EA8A}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| {B4229E79-0D13-4186-BD8D-89CAC9F1EA8A}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| EndGlobalSection | ||
| GlobalSection(SolutionProperties) = preSolution | ||
| HideSolutionNode = FALSE | ||
| EndGlobalSection | ||
| GlobalSection(ExtensibilityGlobals) = postSolution | ||
| SolutionGuid = {5E766B1C-5638-4495-AC79-F7F4EC79629C} | ||
| EndGlobalSection | ||
| EndGlobal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,28 +5,77 @@ | |
|
|
||
| namespace HomeExercises | ||
| { | ||
| [TestFixture] | ||
| public class NumberValidatorTests | ||
|
||
| { | ||
| [Test] | ||
| public void Test() | ||
| [Category("NumberValidator.Constructor; Exception")] | ||
|
||
| [TestCase(-5, 5, TestName = "Precision < 0")] | ||
| [TestCase(0, 5, TestName = "Precision = 0")] | ||
| [TestCase(5, -5, TestName = "Scale < 0")] | ||
| [TestCase(5, 5, TestName = "Precision = Scale")] | ||
| [TestCase(5, 10, TestName = "Precision < Scale")] | ||
| public void Constructor_With_IncorrectArguments_Should_ThrowException(int precision, int scale) | ||
|
||
| { | ||
| 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)); | ||
|
|
||
| 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")); | ||
| var action = new Action(() => new NumberValidator(precision, scale)); | ||
|
||
| action.Should().Throw<ArgumentException>(); | ||
| } | ||
|
|
||
| [Category("NumberValidator.Constructor; No exception")] | ||
| [TestCase(10, 5, false, TestName = "Correct parameters")] | ||
|
||
| public void Constructor_With_CorrectArguments_ShouldNot_ThrowException(int precision, | ||
| int scale, bool onlyPositive) | ||
| { | ||
| var action = new Action(() => new NumberValidator(precision, scale, onlyPositive)); | ||
| action.Should().NotThrow(); | ||
| } | ||
|
|
||
| [Category("NumberValidator.Constructor; No exception")] | ||
| [TestCase(15, TestName = "Only precision is given")] | ||
| public void Constructor_With_OneArgument_ShouldNot_ThrowException(int precision) | ||
| { | ||
| var action = new Action(() => new NumberValidator(precision)); | ||
| action.Should().NotThrow(); | ||
| } | ||
|
|
||
| [Category("NumberValidator.IsValid(...); Correct values")] | ||
| [TestCase(1, 0, true, "5", TestName = "No fraction part")] | ||
| [TestCase(3, 0, true, "+99", TestName = "Sign and no fraction part")] | ||
| [TestCase(4,2, false,"-1.25", TestName = "Negative number")] | ||
| public void IsValid_ShouldReturn_True_When_CorrectValue(int precision, int scale, | ||
| bool onlyPositive, string value) | ||
| { | ||
| var validator = new NumberValidator(precision, scale, onlyPositive); | ||
| validator.IsValidNumber(value).Should().BeTrue(); | ||
| } | ||
|
|
||
| [Category("NumberValidator.IsValid(...); Separators")] | ||
| [TestCase(4, 2, true, "+5,33", TestName = "Works with dot as separator")] | ||
| [TestCase(4, 2, true, "+5.33", TestName = "Works with comma as separator")] | ||
| public void IsValid_ShouldWork_WithDotAndComma_Separators(int precision, int scale, | ||
| bool onlyPositive, string value) | ||
| { | ||
| var validator = new NumberValidator(precision, scale, onlyPositive); | ||
| validator.IsValidNumber(value).Should().BeTrue(); | ||
| } | ||
|
|
||
| [Category("NumberValidator.IsValid(...); Incorrect values")] | ||
| [TestCase(3, 2, true, "00.00", TestName = "Actual precision < expected")] | ||
| [TestCase(3, 2, true, "+0.00", | ||
| TestName = "Sign was not taken into account when forming precision value")] | ||
| [TestCase(17, 2, true, "0.000", TestName = "Actual scale < expected")] | ||
| [TestCase(3, 2, true, "a.sd", TestName = "Letters instead of digits")] | ||
| [TestCase(3, 2, true, "-1.25", TestName = "Negative number when (onlyPositive = true)")] | ||
| [TestCase(10, 5, true, "+", TestName = "No number given")] | ||
| [TestCase(10, 5, true, "+ 5, 956", TestName = "Spaces are forbidden")] | ||
| [TestCase(10, 5, true, "45!34", TestName = "Incorrect separator")] | ||
| [TestCase(10, 5, true, "++3.45", TestName = "Two signs")] | ||
| [TestCase(10, 5, true, "2,,66", TestName = "Two separators")] | ||
| [TestCase(10, 5, true, "", TestName = "Empty string as number")] | ||
| public void IsValid_ShouldReturn_False_When_IncorrectValue(int precision, int scale, | ||
|
||
| bool onlyPositive, string value) | ||
| { | ||
| var validator = new NumberValidator(precision, scale, onlyPositive); | ||
| validator.IsValidNumber(value).Should().BeFalse(); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -42,10 +91,13 @@ 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); | ||
| } | ||
|
|
||
|
|
@@ -61,11 +113,13 @@ public bool IsValidNumber(string value) | |
| return false; | ||
|
|
||
| var match = numberRegex.Match(value); | ||
|
|
||
| if (!match.Success) | ||
| return false; | ||
|
|
||
| // Знак и целая часть | ||
| var intPart = match.Groups[1].Value.Length + match.Groups[2].Value.Length; | ||
|
|
||
| // Дробная часть | ||
| var fracPart = match.Groups[4].Value.Length; | ||
|
|
||
|
|
@@ -74,6 +128,7 @@ public bool IsValidNumber(string value) | |
|
|
||
| if (onlyPositive && match.Groups[1].Value == "-") | ||
| return false; | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,55 @@ | ||
| using FluentAssertions; | ||
| using System.Text.RegularExpressions; | ||
| using FluentAssertions; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace HomeExercises | ||
| { | ||
| public class ObjectComparison | ||
|
||
| { | ||
| [Test] | ||
| [Description("Проверка текущего царя")] | ||
| [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); | ||
| // В начальном наборе тестов был сделан акцент на то, что у объекта класса Person | ||
| // должнен быть родитель первого поколения. | ||
| actualTsar.Parent.Should().NotBe(null, "Должен быть родитель 1-го поколения"); | ||
|
|
||
| // Полностью повторяем логику проверки до рефакторинга. | ||
| // Из родителей проверяем только первое поколение, причём не сравниваем у них вес. | ||
| actualTsar.Should().BeEquivalentTo(expectedTsar, config => | ||
|
||
| { | ||
| return config.Excluding(person => person.Id) | ||
| .Excluding(person => person.Parent!.Parent) | ||
| .Excluding(person => person.Parent!.Weight) | ||
| .Excluding(person => person.Parent!.Id); | ||
| }); | ||
| } | ||
|
|
||
| [Test] | ||
| [Description("Проверка текущего царя и всех его родителей")] | ||
|
||
| [Category("AlternativeTest")] | ||
|
||
| public void CheckCurrentTsar_WithCustomEquality_Informative() | ||
|
||
| { | ||
| // Этим шаблоном мы исключаем все идентификаторы из проверки. | ||
| // То есть исключаем Id у 1-го, 2-го, ..., n - 1, n родителя. | ||
| const string excludingPattern = @"^(Parent\.)*Id$"; | ||
|
||
|
|
||
| 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); | ||
| // Эта версия теста является информативным аналогом метода CheckCurrentTsar_WithCustomEquality(...) | ||
| // До сих пор возможна глубокая рекурсия! | ||
| actualTsar.Should().BeEquivalentTo(expectedTsar, config => | ||
|
||
| { | ||
| return config.Excluding(ctx => Regex.IsMatch(ctx.SelectedMemberPath, excludingPattern)); | ||
| }); | ||
| } | ||
|
|
||
| [Test] | ||
|
|
@@ -35,7 +60,16 @@ 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)); | ||
|
|
||
| // Какие недостатки у такого подхода? | ||
| /* Недостатки этого подхода: | ||
| * 1. AreEqual(...) сильно связан со структурой класса Person: | ||
| * любая модификация этого класса может потребовать изменения проверяющего метода. | ||
| * 2. AreEqual(...) включает в себя слишком много проверок. Из-за этого мы получим | ||
| * неинформативное сообщение об ошибке в случае разности проверяемых объектов. | ||
| * 3. В методе типа AreEqual(...) легко забыть о каком-либо свойстве, т.е. допустить ошибку в сравнении. | ||
| * 4. Если у проверяемого объекта много свойств, то AreEqual(...) тяжело читать. | ||
| * 5. Возможно переполнение стека из-за рекурсивных вызовов AreEqual(...) внутри самого себя. | ||
| * 6. Для любого другого класса придётся писать новую версию метода AreEqual(...). | ||
| */ | ||
| Assert.True(AreEqual(actualTsar, expectedTsar)); | ||
| } | ||
|
|
||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Можешь объяснить какую роль выполняет этот атрибут? Почему он нужен в этой ситуации?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Этим атрибутом помечается класс, содержащий тесты.
Насколько я знаю, ему можно передавать параметры, которые прокинуться в конструктор класса.
Таким образом можно автоматически создавать несколько классов тестов с разными настройками (действие похоже на атрибут TestCase для метода).
Использование этого атрибута не является обязательным, но я решил его добавить, чтобы подчеркнуть то, что NumberValidatorTests содержит тесты. Хотя это понятно и из названия класса :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
я думаю, что нет смысла навешивать этот атрибут, когда нет прямой необходимости (например, создания тестового класса с разными параметрами или параллельный запуск тестов на уровне фикстуры), поэтому я бы убрала) Но в целом это не критично