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

Дмитрий Леонтьев #248

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

Dary0302
Copy link

No description provided.

Copy link

@Buskervil Buskervil left a comment

Choose a reason for hiding this comment

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

Тэкс, ну раз тема tdd, давай про тесты поговорим).

Давай еще раз поглядим на требования:

Некоторые требования к раскладке:

Форма итогового облака должна быть близка к кругу с центром в точке center.
Прямоугольники не должны пересекаться друг с другом.
Облако должно быть плотным, чем плотнее, тем лучше.

Надо постараться проверить по требованиям:

  • раскладка получается круглой
  • центр облака находится в центре
  • раскладка плотная, насколько?
  • нет пересечений (это у тебя есть)

И добавь тесты на все классы, которые у тебя есть (экстеншны не протестировал)

{
while (newRectangle.IsNotIntersectOthersRectangles(Rectangles) && desiredPosition != currentPosition)
{
currentPosition += currentPosition < desiredPosition ? 1 : -1;

Choose a reason for hiding this comment

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

Размазал логику определения того, в какую сторону смещение происходит. Тут считаешь текущую позицию, а реальное направление смещения задано вне этого метода в 32 строке. Надо объединить 2 кусочка в один

Choose a reason for hiding this comment

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

В плане направления смещения логику подсобрал - стало лучше

new(rectangle.Location.X + rectangle.Width / 2, rectangle.Location.Y + rectangle.Height / 2);

public static bool IsNotIntersectOthersRectangles(this Rectangle rectangle, IEnumerable<Rectangle> rectangles) =>
rectangles.All(rect => !rectangle.IntersectsWith(rect));

Choose a reason for hiding this comment

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

Тут можно оптимальнее сделать: если ты уже встретил хотя бы одно пересечение, остальные прямоугольники проверять нет смысла

Choose a reason for hiding this comment

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

resolved

public static Point Add(this Point selfPoint, Point otherPoint) =>
new(selfPoint.X + otherPoint.X, selfPoint.Y + otherPoint.Y);

public static Point Subtract(this Point selfPoint, Point otherPoint) =>

Choose a reason for hiding this comment

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

класс называется RectangleExtension, а расширение для точки

Choose a reason for hiding this comment

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

теперь в PointerExtensions лежит расширение для Rectangle)

{
var newRectangle = MoveRectangleAxis(rectangle, rectangle.GetCenter().X, center.X,
new(rectangle.GetCenter().X < center.X ? 1 : -1, 0));
newRectangle = MoveRectangleAxis(newRectangle, newRectangle.GetCenter().Y, center.Y,

Choose a reason for hiding this comment

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

Интересно будет посмотреть как это выглядит). Подозреваю, что по оси Х плотность будет выше)

Choose a reason for hiding this comment

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

Судя по демо рисункам, мои подозрения не оправдались)

Choose a reason for hiding this comment

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

resolved

public void GetNextPoint_WithCorrectParameters_ReturnsCorrectPoint()
{
var spiral = new Spiral(new(0, 0), 1);
spiral.GetNextPoint().Should().Be(new Point(0, 0));

Choose a reason for hiding this comment

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

Думаю проверку, что первая точка это "центр" можно вынести в отдельный тест


for (var i = 0; i < countRectangles; i++)
{
var size = new Size(random.Next(20, 100), random.Next(40, 100));

Choose a reason for hiding this comment

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

Ну вот, а если бы класс RectangleGenerator был написан удобно, ты бы использовал здесь его)

}
isIntersectsWith.Should().Be(countRectangles);

rectangles.Clear();

Choose a reason for hiding this comment

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

Зачем очистка, она тут только AAA нарушает. Надо написать так, чтобы тесты не аффектили друг друга.

public void SetUp()
{
center = new Point(0, 0);
layouter = new CircularCloudLayouter(center);

Choose a reason for hiding this comment

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

При параллельном запуске перетираешь себе данные


namespace TagsCloudVisualization.CloudLayouter;

public static class RectangleGenerator

Choose a reason for hiding this comment

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

Возможно, этот класс стоит в сборку с тестами унести


for (var i = 0; i < count; i++)
{
var size = new Size(random.Next(20,100), random.Next(20, 80));

Choose a reason for hiding this comment

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

Вроде разброс по высоте меньше, чем по ширине, но все равно может быть такое, что ширина 20, а высота 79. Вряд ли есть слова, у которых высота в 4 раза больше ширины, так что тут алгоритм генерации не совсем соответствует предметной области.

public readonly List<Rectangle> Rectangles = new();
private readonly Spiral spiral = new(center, 2);

public Rectangle PutNextRectangle(Size sizeRectangle)

Choose a reason for hiding this comment

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

Уровень абстракции этого метода какой-то неравномерный. Он часть работы делает сам, часть делегирует.
По красоте оставить бы в нем что-нибудь такое
...
var rectangle = FindNextValidRectanglePosition(sizeRectangle);
rectangle = MoveRectangleCloserToCenter(rectangle);
rectangles.Add(rectangle);
...

Choose a reason for hiding this comment

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

resolved

{
[TestCase(0, 0, 1)]
[TestCase(2, 10, 2)]
public void Constructor_WithCorrectParameters_NotThrow(int x, int y, double step)

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.

можна)

Choose a reason for hiding this comment

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

а вот отрицательный шаг, думаю, что нельзя

return newRectangle;
}

public void PutRectangles(IEnumerable<Rectangle> rectangles)

Choose a reason for hiding this comment

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

Старайся соблюдать порядок: сначала все публичные методы, затем все приватные

return newRectangle;
}

public void PutRectangles(IEnumerable<Rectangle> rectangles)

Choose a reason for hiding this comment

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

Вот это можно унести в Extension метод для ILayouter.

  • класс будет чище
  • метод можно будет использовать для разных Lyouter-ов (он по природе своей универсальный)


public class CircularCloudLayouter(Point center) : ILayouter
{
public readonly List<Rectangle> Rectangles = new();

Choose a reason for hiding this comment

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

Можно лучше инкапсулировать: этот список сейчас доступен для изменения снаружи класса, это не очень хорошо. Вообще, в большинстве случаев, публичные поля это не хорошо, публичные свойства это норм)

Choose a reason for hiding this comment

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

Сделал свойство, но инкапсуляция все равно не получилось. Проблема с листом в том, что имея на него ссылку снаружи, можно закидывать в него элементы, удалять, очищать и т. д. Все это снаружи класса не должно быть доступно, нужно оставить только чтение.

На практике так заморачиваются не всегда, но хорошо инкапсулированные коллекции в классе выглядят вот так:

private readonly List<Rectangle> _rectangles = new();
public IReadOnlyList<Rectangle> Rectangles => _rectangles.AsReadOnly();

Здесь внутри класса доступны все операции, а снаружи можно только читать.

Можно еще публиковать коллекцию вот так
public IReadOnlyList<Rectangle> Rectangles => _rectangles
Почти то же самое, но тут мы отдаем объект листа как IReadOnlyList. Снаружи можно будет сделать каст к листу и снова портить инкапсуляцию. А в первом варианте так сделать не получится, потому что возвращатеся объект ReadOnlyCollection у которого кроме чтения методов нет и кастовать к листу уже не получится.

public readonly List<Rectangle> Rectangles = new();
private readonly Spiral spiral = new(center, 2);

public Rectangle PutNextRectangle(Size sizeRectangle)

Choose a reason for hiding this comment

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

resolved


private static double GetAreaOfCircle(double radius) => Math.PI * Math.Pow(radius, 2);

private static int GetAreaOfRectangles(CircularCloudLayouter layouter) =>

Choose a reason for hiding this comment

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

Выше у тебя везеде IEnumerable, а тут вдруг layouter. Перечисление лучше

double areaOfCircle,
double averageDistance,
double radius) =>
areaOfRectangles / areaOfCircle * (1 - averageDistance / radius);

Choose a reason for hiding this comment

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

А первый множитель расчитан неправильно. Перепроверь пожалуйста.
Вообще я посмотрел. У тебя плотность на твоих тестовых данных выше 75% везде. Это хороший результат 🔥

}

[Test]
public void PutNextRectangle_CheckIntersectRectangles_ReturnFalse()

Choose a reason for hiding this comment

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

плохое название

Choose a reason for hiding this comment

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

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

[TestCase(50)]
[TestCase(100)]
[TestCase(1000)]
public void PlacedRectangles_WhenCorrectNotIntersects_ReturnTrue(int countRectangles)

Choose a reason for hiding this comment

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

плохое название

[TestCase(-100, 100, 10, Description = "Center in the fourth quadrant of the coordinate axis")]
[TestCase(-100, 100, 100, Description = "Center in the fourth quadrant of the coordinate axis")]
[TestCase(-100, 100, 1000, Description = "Center in the fourth quadrant of the coordinate axis")]
public void RectanglesDensity_WithDifferentNumberRectanglesAndDifferentCenters(

Choose a reason for hiding this comment

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

Вот тут как раз не хватает 3й части в названии. Надо что-то типа такого:
PutNextRectangle_CreateLaoyoutWithOver75PercentDencity_OnRandomNumberRectangles

Dary0302 and others added 2 commits November 18, 2024 19:03
add more tests, the tests have been improved, add RectangleExtension,…
}

[Test]
public void PutNextRectangle_ReturnsFirstRectangleThatIsInCenterOfCloud()

Choose a reason for hiding this comment

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

Название хорошее, но не правдивое. Действительно возвращает, действительно в центре, но только когда это первый прямоугольник. Лучше PutNextRectangle_PlaceFirstRectangleAtCenter

[TestCase(50)]
[TestCase(100)]
[TestCase(1000)]
public void PutNextRectangle_CheckIntersectOfRectangles(int countRectangles)

Choose a reason for hiding this comment

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

Плохое название. Название теста должно утверждать что-то о поведении класса.

return point1.Add(point2);
}

private static IEnumerable<TestCaseData> PointerSumTestCases()

Choose a reason for hiding this comment

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

Потренировался - молодец)

}

[TestCase(null)]
public void CreateImage_OnInvalidParameters_ThrowsArgumentException(string filename)

Choose a reason for hiding this comment

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

Ввыкинь тесты на сохранение. Это не ответственность класса, который рисует

public class RectangleGeneratorTest
{
[Test]
public void GenerateRandomRectangles_CheckNumberRectangles_NumberRectanglesGeneratedMatchesRequested()

Choose a reason for hiding this comment

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

Просто утверждение о поведении. CheckNumberRectangles лишнее. Лучше GenerateRandomRectangles_ReturnGeneratedRectanglesNumberMatchesRequested

{
[TestCase(0, 0, 1)]
[TestCase(2, 10, 2)]
public void Constructor_WithCorrectParameters_NotThrow(int x, int y, double step)

Choose a reason for hiding this comment

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

а вот отрицательный шаг, думаю, что нельзя

namespace TagsCloudVisualizationTests;

[TestFixture]
public class SpiralTest

Choose a reason for hiding this comment

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

В этом классе все имена тестов хорошие

using TagsCloudVisualization.Extension;
using TagsCloudVisualization.RectangleGenerator;

var center = new Point(0, 0);

Choose a reason for hiding this comment

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

Обсуждали, что лучше бы сделать сборку class library

Choose a reason for hiding this comment

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

В принципе можешь это потом сделать. Сейчас забьем

{
public static IEnumerable<Rectangle> GenerateRandomRectangles(int countRectangles)
{
var rectangles = new List<Rectangle>(countRectangles);

Choose a reason for hiding this comment

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

Отдаешь IEnumerable, но под капотом держишь list. Можно сделать ленивую генерацию

[TestFixture]
public class CircularCloudLayouterTest
{
private List<Rectangle> rectanglesForCrashTest = new();

Choose a reason for hiding this comment

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

  1. Надо не забывать во всех тестах писать сюда данные)
  2. Может возникать проблема при параллельном запуске тестов (ты уже встречался с этим). Для решения этой проблемы можно сделать поле статическим и обернуть его в ThreadLocal. Тогда для каждого потока rectanglesForCrashTest будет свой

Choose a reason for hiding this comment

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

можно в общем-то забить. Просто отметил на подумать

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants