Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
46 changes: 32 additions & 14 deletions Testing/Basic/Homework/1. ObjectComparison/ObjectComparison.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,46 @@
using NUnit.Framework;

This comment was marked as resolved.

Copy link
Author

Choose a reason for hiding this comment

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

Вопрос хороший)

По хорошему нужно было разбить на 2 коммита, но подумал, что нужно одним коммитов, т.к. одна домашка

А так понял, в будущем буду разделять по коммитам

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.

Каждый коммит отражает логически завершённый шаг, проще понять как развивалось решение, что и как менялось.

Легче делать код ревью по каждому коммиту.

Иногда сложно заранее разбить на разные коммиты

Иногда бесмыссленно для мелких задач.

Copy link

Choose a reason for hiding this comment

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

Да, в целом верно
Еще разбивка по коммитам дает тебе возможность удобно откатывать изменения, если они были ошибочны
Помимо этого легче подтягивать изменения в другие ветки через чери-пики и пр.
В общем, преимуществ много, но они не всегда нужны, ты прав
Советую попроходить игру . Там показывается и рассказывается все, что нужно для работы с гитом, думаю будет полезно, если раньше не проходил ее

using System.Text.RegularExpressions;
using FluentAssertions;
using FluentAssertions.Equivalency;
using NUnit.Framework;
using NUnit.Framework.Legacy;

namespace HomeExercise.Tasks.ObjectComparison;
public class ObjectComparison
public partial class ObjectComparison
{
[Test]
[Description("Проверка текущего царя")]
[Category("ToRefactor")]
public void CheckCurrentTsar()
public void CheckCurrentTsar_IgnoringIds()
{
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.
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);
//можно оставить так, но вдруг появиться свойство, название которого будет начинаться с Id, или иметь Id в середине наименования.
/*actualTsar
.Should()
.BeEquivalentTo(expectedTsar, opt =>
opt.Excluding(info => info.Path.EndsWith("Id")));*/
actualTsar
.Should()
.BeEquivalentTo(expectedTsar, opt =>
opt.Excluding((IMemberInfo memberInfo) => MyRegex().IsMatch(memberInfo.Name)));
}



/*
* 1. Если упадет тест мы увидим сообщение из разряда:
* Expected: True
* But was: False
* что не является информативным сообщением, и мы не поймем в чем ошибка конкретно.
* 2. Методе AreEqual реализует то, что уже имеют библиотеки, например .BeEquivalentTo, т.е. мы изобретаем велосипед заново.
* 3. Возвращаем булевый результат, из за чего теряем контекст ошибки(аналогично п.1)
* 4. Метод жестко проверяет свойство parent, и если добавить новое свойство, например Child, тогда тест будет падать, либо нужно будет добавлять новые проверки
* 5. Если добавлять впринципе новые свойства/поля тест упадет, или нужно будет добавлять новые проверки
* 6. Может уйти в бесконечную рекурсию, если ссылка parent будет идти обратно к исходному обьекту.
*/
[Test]
[Description("Альтернативное решение. Какие у него недостатки?")]
public void CheckCurrentTsar_WithCustomEquality()
Expand All @@ -49,4 +64,7 @@ private bool AreEqual(Person? actual, Person? expected)
&& actual.Weight == expected.Weight
&& AreEqual(actual.Parent, expected.Parent);
}

[GeneratedRegex(@"(^Id\b|\bId$)")]
private static partial Regex MyRegex();
}
168 changes: 147 additions & 21 deletions Testing/Basic/Homework/2. NumberValidator/NumberValidatorTests.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,157 @@

using FluentAssertions;
using NUnit.Framework;
using NUnit.Framework.Legacy;
using System.Collections;

namespace HomeExercise.Tasks.NumberValidator;

[TestFixture]
public class NumberValidatorTests
{
[Test]
public void Test()
#region Конструктор

[Test, TestCaseSource(nameof(InvalidArguments_NegativeOrScaleTooBig))]
public void Constructor_ShouldThrow_WhenArgumentsAreNegativeOrScaleAtLeastPrecision(int precision, int scale,
string expectedMessage)
{
var act = () => new NumberValidator(precision, scale);
act
.Should()
.Throw<ArgumentException>($"{expectedMessage}. "
+ $"Параметры: precision={precision}, scale={scale}");
}

[TestCase(1, 0, true)]
[TestCase(10, 5, false)]
public void Constructor_DoesNotThrow_WhenPrecisionScaleAndOnlyPositiveAreValid(int precision, int scale,
bool onlyPositive)
{
var act = () => new NumberValidator(precision, scale, onlyPositive);
act
.Should()
.NotThrow($"precision={precision}, scale={scale}, onlyPositive={onlyPositive} допустимы");
}

#endregion

#region Валидация чисел

[TestCaseSource(nameof(ValidNumbers))]
public void IsValidNumber_ShouldReturnTrue_ForValidNumbers(int precision, int scale, bool onlyPositive,
string input, string expectedMessage)
{
var validator = new NumberValidator(precision, scale, onlyPositive);
validator
.IsValidNumber(input)
.Should()
.BeTrue($"{input}. {expectedMessage}. " + $"Параметры: precision={precision}, scale={scale}, " +
$"onlyPositive={onlyPositive}");
}

[TestCaseSource(nameof(InvalidNumbers))]
public void IsValidNumber_ShouldReturnFalse_ForInvalidNumbers(int precision, int scale, bool onlyPositive,
string input, string expectedMessage)
{
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, onlyPositive);
validator
.IsValidNumber(input)
.Should()
.BeFalse($"{input}. {expectedMessage}. " + $"Параметры: precision={precision}, scale={scale}, " +
$"onlyPositive={onlyPositive}");
}

#endregion

private static IEnumerable<TestCaseData> InvalidNumbers()
{
yield return new TestCaseData(3, 2, true, "+0.00", "число превышает допустимое количество знаков")
.SetName("InvalidNumber_TooManyDigits_PositiveZero");

yield return new TestCaseData(7, 2, true, "-1.231",
"число превышает допустимое количество знаков после запятой")
.SetName("InvalidNumber_TooManyDecimals_Negative");

yield return new TestCaseData(3, 2, true, "a.sd", "содержит недопустимые символы")
.SetName("InvalidNumber_InvalidCharacters");

yield return new TestCaseData(3, 2, true, "", "пустая строка")
.SetName("InvalidNumber_EmptyString");

yield return new TestCaseData(3, 2, true, " ", "строка содержит только пробел")
.SetName("InvalidNumber_WhitespaceOnly");

yield return new TestCaseData(3, 2, true, "+", "строка содержит только знак без числа")
.SetName("InvalidNumber_SignOnlyPlus");

yield return new TestCaseData(3, 2, true, "-", "строка содержит только знак без числа")
.SetName("InvalidNumber_SignOnlyMinus");

yield return new TestCaseData(2, 0, true, "211", "число превышает допустимую длину целой части")
.SetName("InvalidNumber_IntegerTooLong");

yield return new TestCaseData(3, 0, true, "0.1", "число превышает допустимую длину дробной части")
.SetName("InvalidNumber_ScaleTooLong");

yield return new TestCaseData(17, 2, true, "0.000", "число превышает допустимую длину дробной части")
.SetName("InvalidNumber_ScaleTooLong2");

yield return new TestCaseData(3, 0, true, "0.-12", "недопустимый знак внутри числа")
.SetName("InvalidNumber_InternalSignMinus");

yield return new TestCaseData(3, 0, true, "0.+12", "недопустимый знак внутри числа")
.SetName("InvalidNumber_InternalSignPlus");

yield return new TestCaseData(3, 0, true, "0.12.12", "число содержит несколько разделителей")
.SetName("InvalidNumber_MultipleSeparators");

yield return new TestCaseData(3, 0, true, "0.,12.12", "число содержит несколько разделителей")
.SetName("InvalidNumber_MixedSeparators1");

yield return new TestCaseData(3, 0, true, "0.2,12", "число содержит несколько разделителей")
.SetName("InvalidNumber_MixedSeparators2");

yield return new TestCaseData(3, 0, true, "0,2.12", "число содержит несколько разделителей")
.SetName("InvalidNumber_MixedSeparators3");

yield return new TestCaseData(3, 2, true, null, "значение null недопустимо")
.SetName("InvalidNumber_NullValue");
}


private static IEnumerable<TestCaseData> ValidNumbers()
{
yield return new TestCaseData(
17, 2, true, "0.0",
"целая часть + дробная часть ≤ precision, дробная часть ≤ scale, число положительное"
).SetName("ValidNumber_ZeroPointZero");

yield return new TestCaseData(
4, 2, true, "+1.23",
"целая часть + дробная часть ≤ precision, дробная часть ≤ scale, число положительное"
).SetName("ValidNumber_PositiveWithTwoDecimals");

yield return new TestCaseData(
4, 2, false, "-1.23",
"целая часть + дробная часть ≤ precision, дробная часть ≤ scale, отрицательные числа разрешены"
).SetName("ValidNumber_NegativeWithTwoDecimals");

yield return new TestCaseData(
5, 0, true, "12345",
"целая часть + дробная часть ≤ precision, дробная часть ≤ scale, число положительное"
).SetName("ValidNumber_Integer");
}

private static IEnumerable<TestCaseData> InvalidArguments_NegativeOrScaleTooBig()
{
yield return new TestCaseData(-1, 2, "отрицательный precision недопустим")
.SetName("Constructor_Invalid_NegativePrecision");

yield return new TestCaseData(1, -1, "отрицательный scale недопустим")
.SetName("Constructor_Invalid_NegativeScale");

yield return new TestCaseData(3, 3, "scale должен быть меньше чем precision")
.SetName("Constructor_Invalid_ScaleEqualsPrecision");

yield return new TestCaseData(3, 4, "scale должен быть меньше чем precision")
.SetName("Constructor_Invalid_ScaleGreaterThanPrecision");
}
}