Skip to content
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

Шестопалов Андрей #18

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
21 changes: 11 additions & 10 deletions Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using NUnit.Framework;
using NUnit.Framework.Legacy;
using FluentAssertions;

namespace HomeExercise.Tasks.ObjectComparison;
public class ObjectComparison
Expand All @@ -14,16 +15,10 @@ 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(x => x.Path.EndsWith("Id"))
.AllowingInfiniteRecursion());
}

[Test]
Expand All @@ -35,6 +30,12 @@ public void CheckCurrentTsar_WithCustomEquality()
new Person("Vasili III of Russia", 28, 170, 60, null));

// Какие недостатки у такого подхода?
/*
* 1) Отсутствие информации о причинах падения теста (В моей проверке, выводятся различающиеся свойства).
*
* 2) При изменении свойств класса Person, необходимо дописывать проверку новых свойств (В моей проверке, сравниваются все public свойства
* и дописывать изменения в тест необходимо надо будет лишь в том случае, если появилось новое свойство, которое разное у всех объектов).
*/
ClassicAssert.True(AreEqual(actualTsar, expectedTsar));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
179 changes: 159 additions & 20 deletions Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,170 @@

using NUnit.Framework;
using NUnit.Framework.Legacy;
using FluentAssertions;

namespace HomeExercise.Tasks.NumberValidator;

[TestFixture]
public class NumberValidatorTests
{
[Test]
public void Test()
#region ConstructorTests
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предлагаю убрать использование #region.
Это вызывает затруднение при навигации, лишние клики
Большинство Ide поддерживают скрытие блоков кода, если в этом есть необходимость.
И на практике использование region указывает на плохо декомпозированный код.
Альтернативой может послужить рефакторинг кода, переиспользуемые методы, вынесение в отдельный или partial класс

[TestCase(0,0, "precision must be a positive number",
TestName = "Constructor_PrecisionIsZero_ThrowArgumentException (precision = 0, scale = 0)")]
[TestCase(-1, 0, "precision must be a positive number",
TestName = "Constructor_PrecisionIsNegative_ThrowArgumentException (precision = -1, scale = 0)")]

[TestCase(1, -1, "scale must be a non-negative number less or equal than precision",
TestName = "Constructor_ScaleIsNegative_ThrowArgumentException (precision = 1, scale = -1)")]
[TestCase(5, 5, "scale must be a non-negative number less or equal than precision",
TestName = "Constructor_PrecisionEqualsScale_ThrowArgumentException (precision = 5, scale = 5)")]
[TestCase(1, 2, "scale must be a non-negative number less or equal than precision",
TestName = "Constructor_PrecisionIsGreaterThanPrecision_ThrowArgumentException (precision = 1, scale = 2)")]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PrecisionIsGreaterThanPrecision - опечатка?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да.

[Category("InitializationTests")]
public void Constructor_InvalidArguments_ThrowArgumentException(int precision, int scale, string expectedMessage)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Проверять сообщение ошибки в тесте не самая лучшая практика. Тест становиться не устойчивым к законным изменениям в NumberValidator. При каждом изменении сообщения придется править тест

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предлагаю отрефакторить имена тестов
Метод назвать ThrowException_When, а тесты PrecisionEqualsScale и т.д
Повысим читаемость, уберём дублирование

{
Action action = () => new NumberValidator(precision, scale);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action -> var. И по смыслу это Func так как возвращает результат.
Предпочтительно использовать ключевое слово var. Явно указывать тип излишне, компилятор умеет определять тип динамически. Так же это позволит сократить код и сделать его более читаемым


action.Should().Throw<ArgumentException>().WithMessage(expectedMessage);
}

[TestCase(1, 0,
TestName = "Constructor_PrecisionIsGreaterThanScaleAndScaleIsZero_DoesNotThrowException (precision = 1, scale = 0)")]
[TestCase(5, 2,
TestName = "Constructor_PrecisionGreaterThanScaleAndScaleGreaterThanZero_DoesNotThrowException (precision = 5, scale = 2)")]
[Category("InitializationTests")]
public void Constructor_ValidArguments_NotThrowException(int precision, int scale)
{
Action action = () => new NumberValidator(precision, scale);

action.Should().NotThrow();
}
#endregion

#region IntegersTests
[TestCase(1, 0, "0",
TestName = "IsValidNumber_IntegerWithoutSign_LengthEqualsPrecision_ReturnsTrue (precision = 1, scale = 0, onlyPositive = false, valueForValidation = \"0\")")]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Указывать параметры тест-кейса еще и в нейминге - излишнее дублирование. Это затрудняет читаемость теста и смещает фокус с самого главного - что проверяет тест. Если тест падает, то разработчик пойдет чинить и увидит сигнатуру теста

[TestCase(5, 0, "40",
TestName = "IsValidNumber_IntegerWithoutSign_LengthLessThanPrecision_ReturnsTrue (precision = 5, scale = 0, onlyPositive = false, valueForValidation = \"40\")")]
[TestCase(2, 0, "+0",
TestName = "IsValidNumber_PositiveZeroWithSign_ReturnsTrue (precision = 2, scale = 0, onlyPositive = false, valueForValidation = \"+0\")")]

[TestCase(2, 0, "-0",
TestName = "IsValidNumber_NegativeZeroWithSign_ReturnsTrue (precision = 2, scale = 0, onlyPositive = false, valueForValidation = \"-0\")")]
[TestCase(3, 0, "+21",
TestName = "IsValidNumber_IntegerWithSign_LengthEqualsPrecision_ReturnsTrue (precision = 3, scale = 0, onlyPositive = false, valueForValidation = \"+21\")")]
[TestCase(5, 0, "-40",
TestName = "IsValidNumber_IntegerWithSign_LengthLessThanPrecision_ReturnsTrue (precision = 5, scale = 0, onlyPositive = false, valueForValidation = \"-40\")")]

[TestCase(3, 1, "0",
TestName = "IsValidNumber_IntegerWithoutSign_ScaleIsGreaterThanZero_ReturnsTrue (precision = 3, scale = 1, onlyPositive = false, valueForValidation = \"6\")")]
[TestCase(5, 2, "+21",
TestName = "IsValidNumber_IntegerWithSign_ScaleIsGreaterThanZero_ReturnsTrue (precision = 5, scale = 2, onlyPositive = false, valueForValidation = \"+37\")")]
[Category("IntegersTests")]
public void IsValidNumber_IntegerWithinPrecisionAndScale_ReturnTrue(int precision, int scale, string valueForValidation)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тест дублирует логику теста ниже. За исключением onlyPositive в конструкторе NumberValidator.
Предлагаю передавать значение onlyPositive в TestCase

{
var validator = new NumberValidator(precision, scale, false);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В текущей реализации излишне явно проставлять onlyPositive false. Оно и так определено как false по умолчанию в конструкторе

var isValid = validator.IsValidNumber(valueForValidation);

isValid.Should().BeTrue();
}
#endregion

#region DecimalsTests
[TestCase(5, 2, "123.45",
TestName = "IsValidNumber_DecimalWithoutSign_FractionalPartLengthEqualsScale_ReturnsTrue (precision = 5, scale = 2, onlyPositive = false, valueForValidation = \"123.45\")")]
[TestCase(7, 4, "537.23",
TestName = "IsValidNumber_DecimalWithoutSign_FractionalPartLengthLessThanScale_ReturnsTrue (precision = 7, scale = 4, onlyPositive = false, valueForValidation = \"537.23\")")]

[TestCase(3, 2, "0.0",
TestName = "IsValidNumber_DecimalZeroWithinPrecisionAndScale_ReturnsTrue (precision = 3, scale = 2, valueForValidation = \"0.0\")")]

[TestCase(6, 2, "+237.89",
TestName = "IsValidNumber_DecimalWithSign_FractionalPartLengthEqualsScale_ReturnsTrue (precision = 6, scale = 2, onlyPositive = false, valueForValidation = \"+237.89\")")]
[TestCase(7, 4, "-445.12",
TestName = "IsValidNumber_DecimalWithoutSign_FractionalPartLengthLessThanScale_ReturnsTrue (precision = 7, scale = 4, onlyPositive = false, valueForValidation = \"-445.12\")")]

[TestCase(5, 2, "371,47",
TestName = "IsValidNumber_DecimalWithoutSign_WithCommaSeparator_ReturnsTrue (precision = 5, scale = 2, valueForValidation = \"371,47\")")]
[TestCase(6, 2, "+111,11",
TestName = "IsValidNumber_DecimalWithSign_WithCommaSeparator_ReturnsTrue (precision = 6, scale = 2, valueForValidation = \"111,11\")")]
[TestCase(6, 2, "-984,75",
TestName = "IsValidNumber_DecimalWithSign_WithCommaSeparator_ReturnsTrue (precision = 6, scale = 2, valueForValidation = \"-984,75\")")]
[Category("DecimalsTests")]
public void IsValidNumber_DecimalWithinPrecisionAndScale_ReturnsTrue(int precision, int scale, string valueForValidation)
{
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));

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"));
var validator = new NumberValidator(precision, scale, false);
var isValid = validator.IsValidNumber(valueForValidation);

isValid.Should().BeTrue();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Дублирование кода метода ниже за исключением ожидаемого результата. Предлагаю передавать ожидаемый результат в параметрах теста

}
#endregion

#region InvalidConfigurationTests

[TestCase(1, 0, "11",
TestName = "IsValidNumber_IntegerWithoutSign_ExceedsPrecision_ReturnsFalse (precision = 1, scale = 0, onlyPositive = true, valueForValidation = \"11\")")]
[TestCase(3, 1, "112.3",
TestName = "IsValidNumber_DecimalWithoutSign_ExceedsPrecision_ReturnsFalse (precision = 3, scale = 1, onlyPositive = true, valueForValidation = \"112.3\")")]

[TestCase(2, 0, "+42",
TestName = "IsValidNumber_IntegerWithSign_ExceedsPrecision_ReturnFalse (precision = 2, scale = 0, onlyPositive = true, valueForValidation = \"+42\")")]
[TestCase(4, 2, "+42.75",
TestName = "IsValidNumber_DecimalWithSign_ExceedsPrecision_ReturnFalse (precision = 4, scale = 2, onlyPositive = true, valueForValidation = \"+42.75\")")]

[TestCase(2, 0,"5.0",
TestName = "IsValidNumber_DecimalProvided_ScaleIsZero_ReturnFalse (precision = 2, scale = 0, onlyPositive = true, valueForValidation = \"5.0\")")]
[TestCase(5, 0, "1.23",
TestName = "IsValidNumber_DecimalProvided_ScaleIsZero_ReturnFalse (precision = 5, scale = 0, onlyPositive = true, valueForValidation = \"1.23\")")]

[TestCase(3, 1, "5.67",
TestName = "IsValidNumber_DecimalExceedsScale_ReturnFalse (precision = 3, scale = 1, onlyPositive = true, valueForValidation = \"5.67\")")]

[TestCase(7, 3, "-0",
TestName = "IsValidNumber_NegativeZero_IsOnlyPositive_ReturnFalse (precision = 7, scale = 3, onlyPositive = true, valueForValidation = \"-0\")")]
[TestCase(7, 3, "-10",
TestName = "IsValidNumber_NegativeInteger_IsOnlyPositive_ReturnFalse (precision = 7, scale = 3, onlyPositive = true, valueForValidation = \"-10\")")]
[TestCase(7, 3, "-1.23",
TestName = "IsValidNumber_NegativeDecimal_IsOnlyPositive_ReturnFalse (precision = 7, scale = 3, onlyPositive = true, valueForValidation = \"-1.23\")")]
[Category("InvalidConfigurationTests")]
public void IsValidNumber_InvalidConfigurations_ReturnFalse(int precision, int scale, string valueForValidation)
{
var validator = new NumberValidator(precision, scale, true);
var isValid = validator.IsValidNumber(valueForValidation);

isValid.Should().BeFalse();
}
#endregion

#region InvalidInputsTests
[TestCase(5, 2, null,
TestName = "IsValidNumber_InputIsNull_ReturnFalse (precision = 5, scale = 2, onlyPositive = false, valueForValidation = null)")]
[TestCase(5, 2, "",
TestName = "IsValidNumber_InputIsEmpty_ReturnFalse (precision = 5, scale = 2, onlyPositive = false, valueForValidation = \"\")")]
[TestCase(5, 2, " ",
TestName = "IsValidNumber_InputIsWhitespace_ReturnFalse (precision = 5, scale = 2, onlyPositive = false, valueForValidation = \" \")")]
[TestCase(5, 2, "\t",
TestName = "IsValidNumber_InputIsEscapeSequences_ReturnFalse (precision = 5, scale = 2, onlyPositive = false, valueForValidation = \"\\t\")")]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лишние пробелы между атрибутами

[TestCase(5,2, "a.sd",
TestName = "IsValidNumber_InputContainsLetters_ReturnsFalse (precision = 5, scale = 2, onlyPositive = false, valueForValidation = \"a.sd\")")]
[TestCase(5, 2, "10O",
TestName = "IsValidNumber_InputContainsLetters_ReturnsFalse (precision = 5, scale = 2, onlyPositive = false, valueForValidation = \"10O\")")]

[TestCase(7, 2, "12_34",
TestName = "IsValidNumber_InputContainsIncorrectSeparator_ReturnsFalse (precision = 5, scale = 2, onlyPositive = false, valueForValidation = \"12_34\")")]

[TestCase(5, 0, "871.",
TestName = "IsValidNumber_DecimalWithoutFraction_ScaleZero_ReturnsFalse (precision = 5, scale = 0, onlyPositive = false, valueForValidation = \"871.\")")]
[TestCase(5, 2, "636.",
TestName = "IsValidNumber_DecimalWithoutFraction_ScaleIsPositive_ReturnsFalse (precision = 5, scale = 2, onlyPositive = false, valueForValidation = \"636.\")")]

[Category("InvalidInputsTests")]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Расскажешь для чего используется атрибут Category?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Как написано в интернете, через него можно запускать отдельные группы тестов

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Все верно, можно запускать прогон тестов по отдельным категориям. Не нашел кода который бы это как-то использовал и для чего тебе это тут понадобилось, если, скорее всего, нужно прогнать все тесты разом?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Согласен, в данном случае это не использую.
В первой задаче (ObjectComparison) на тестовом методе был атрибут [Category("ToRefactor")]. Я тоже не нашёл где бы он использовался, но посчитал, что раз он использован в показательном примере первой задачи, то можно добавить этот атрибут и в моих методах.

public void IsValidNumber_InvalidInputs_ReturnFalse(int precision, int scale, string valueToValidator)
{
var validator = new NumberValidator(precision, scale);
var isValid = validator.IsValidNumber(valueToValidator);

isValid.Should().BeFalse();
}
#endregion
}