Я был одержим графиками уже несколько лет. Эта одержимость привела к появлению трех проектов: graph-dsl , graphs и graph-wm . Графики – это отличные структуры данных, которые помогли мне в нескольких ситуациях на работе. В этом руководстве мы разработаем класс mutableDirectedGraph на Java с использованием Test Driven Development (TDD).
В этом руководстве каждый шаг начинается с цели. Цель состоит в том, чтобы либо написать неудачный тест, провести рефакторинг существующего кода, либо написать производственный код. С каждым шагом вы будете приближаться к представлению ориентированного графа с полным набором тестов, подтверждающих его работу. Эти шаги помогут вам попрактиковаться в TDD и дадут представление о том, как TDD может помочь вам писать лучший код. Прежде чем мы начнем кодировать, вот введение в графики и определения, используемые для разработки кода.
Graph
не всегда четко определен в языках программирования. В java существуют разные реализации, используемые для разных ситуаций. Мне трудно начать кодировать график без какого-либо плана. Сначала нам нужно сформулировать некоторые концепции.
Граф инкапсулирует вершины и ребра. Вершина – это значение, хранящееся в графе, в то время как ребро – это связь между этими значениями. Эта реализация будет содержать набор вершин и набор ребер.
Использование Set
подразумевает уникальность. В графе разрешены только уникальные вершины и ребра. Эти объекты должны корректно реализовывать equals
и hashCode
.
Идентификация ребер может быть сложной задачей. Это зависит от того, является ли график направленным или неориентированным. В неориентированном графе только одно ребро может находиться между любыми двумя вершинами. В ориентированном графе одно или два ребра могут находиться между любыми двумя вершинами. Если между двумя вершинами есть два ребра, то эти ребра должны быть в противоположных направлениях. Эти свойства повлияют на реализацию equals и hashCode для ребер.
На данный момент у нас достаточно информации, чтобы начать кодирование, но я хочу объяснить TDD.
Вот три закона TDD (от Clean Coder Роберта К. Мартина):
- Вам не разрешается писать какой-либо производственный код до тех пор, пока вы сначала не напишете неудачный модульный тест.
- Вам не разрешается писать больше модульного теста, чем достаточно для сбоя, а отсутствие компиляции означает сбой.
- Вам не разрешается писать больше производственного кода, достаточного для прохождения текущего неудачного модульного теста.
Эти законы подразумевают рабочий процесс, который состоит из:
- напишите тест, который завершается неудачей
- записывайте небольшие производственные изменения до тех пор, пока все тесты не пройдут
- рефакторинг производственного кода
- рефакторинг тестового кода
рефакторинг 🔄 измените код таким образом, чтобы тесты не проваливались и не изменяли api или функциональность.
Чтобы облегчить выполнение теста TDD, статус теста будет отображаться как ❌ при сбое и ✔️ при прохождении.
Рабочий процесс TDD быстро переключается между написанием тестов и написанием производственного кода. На этих этапах очень мало рефакторинга, но в будущем руководстве может быть представлен рефакторинг.
Давайте начнем.
Как и многие учебные пособия, этот раздел разбит на этапы. Шаги могут быть связаны с использованием формы “шаг-1 “, например.
[step-1](#step-1)
Цель использования шагов – помочь обсуждению в комментариях.
Класс Ориентированный граф
– это производственный код. Класс не должен быть записан до тех пор, пока не будет провален тест в соответствии с законом № 1 . Вот этот тест.
public class DirectedGraphTest { @Test void constructor() { DirectedGraph graph = new DirectedGraph(); } }
Тестовый результат: ❌ сбой, напишите производственный код
Рабочий процесс: Критерии, изложенные в законе № 1, выполнены, и теперь рабочий процесс меняется на добавление производственного кода. Сбой компиляции считается сбоем теста в соответствии с законом № 2, и поэтому больше не следует писать код модульного тестирования.
Действие: Создайте класс Ориентированный граф
. Большинство идей полезны в ситуациях, когда отсутствует класс, метод или переменная. Я использую intellij для создания класса.
public class DirectedGraph { }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Рабочий процесс: Согласно закону № 3 мы не должны писать больше производственного кода. Единственный допустимый вариант – следовать закону № 1 и написать неудачный модульный тест.
Дизайн теста: Вернемся к первому тесту, он явно неполный. Назначение конструктора – инициализировать объект. Мы уже обсуждали это Ориентированный граф
будет иметь два Набор
s. Этот тест должен показать, что график
инициализируется путем проверки того, что эти два Set
s пусты.
Действие: Добавьте неудачную проверку вершин.
@Test void constructor() { DirectedGraph graph = new DirectedGraph(); assertThat(graph.getVertices()).isEmpty(); }
Статус теста: ❌ сбой , написать производственный код
Рабочий процесс: Метод getVertices()
не компилируется, и теперь тест завершается неудачей ( закон №2 ), и можно добавить производственный код ( закон №1 ).
Действие: Метод может быть добавлен с помощью IDE, но intellij не знает, какой тип возвращаемого значения следует использовать. Вот код.
public SetgetVertices() { }
Производственный дизайн: В этом случае IDE не знает, как разработать этот метод, и я добавил возвращаемое значение в качестве универсального. Тест показывает, что эти проектные решения должны быть приняты сейчас. Я решаю, что вершины должны быть переменной-членом и возвращаться с помощью getVertices()
. Я также решаю, что вершины являются общими. Придание вершинам универсального характера также подразумевает, что Ориентированный граф
теперь является общим.
public class DirectedGraph{ public Set getVertices() { return null; } }
Статус теста: ❌ сбой , написать производственный код
Действие: Тест все равно завершится неудачей, но он компилируется. На этом этапе нам нужно вернуть пустой набор для прохождения теста.
public SetgetVertices() { return HashSet<>(); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Возврат нового Set
кажется странным, но я считаю, что это соответствует законам TDD. Вершины, скорее всего, не станут переменной-членом до тех пор, пока неудачный тест не покажет, что это необходимо.
Тест на рефакторинг: 🔄 Тест показывает некоторые предупреждения об использовании необработанных типов. Это можно исправить, добавив параметр типа.
@Test void constructor() { DirectedGraphgraph = new DirectedGraph<>(); assertThat(graph.getVertices()).isEmpty(); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Рабочий процесс: Теперь тест проходит, и требуется еще один неудачный модульный тест (закон № 2). Помните, что текущая цель – проверить конструктор. Существует тест, который проверяет вершины, но как насчет ребер?
Я хочу добавить утверждение, которое выглядит примерно так:
assertThat(graph.getEdges()).isEmpty()
Дизайн: Но что такое край? Я еще не решил, как ребра представлены в графе. На данный момент мне нужен неудачный тест, но это не обязательно должен быть тест конструктора. На данный момент мне нужен неудачный тест, но это не обязательно должен быть тест конструктора.
На данный момент мне нужен неудачный тест, но это не обязательно должен быть тест конструктора.
A Направленный край
класс должен содержать исходную вершину и целевую вершину ребра. Это конечные точки ребра. Направленное ребро
также должно использовать общий тип, чтобы граф мог указывать разные типы вершин при создании ребра. Поскольку ребра будут находиться в Набор
они должны реализовать равно
и хэш-код
. Я также решаю, что Направленный край
должен быть неизменяемым классом значений, что означает источник и target будут обязательными параметрами конструктора, и класс будет окончательным. Этой информации достаточно для создания тестов.
public class DirectedEdgeTest { @Test void constructor() { DirectedEdgeedge = new DirectedEdge<>(); } }
Статус теста: ❌ сбой , написать производственный код
Как и в случае с DirectedGraphTest
класс Directed Edge
может быть сгенерирован в среде IDE.
public final class DirectedEdge{ }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Действие: Теперь тест пройден, но верен ли конструктор? Давайте добавим проверку исходной вершины.
@Test void constructor() { DirectedEdgeedge = new DirectedEdge<>(); assertThat(edge.getSource()).isEqualTo("A"); }
Статус теста: ❌ сбой , написать производственный код
Метод getSource()
не существует, но также обратите внимание, что тест ожидает, что он будет равен “A”. Первый get Source()
может быть добавлен в DirectedEdge
.
public T getSource() { return null; }
Тест будет скомпилирован, но все равно завершится неудачей, ожидая, что источником будет “A”.
public T getSource() { return (T) "A"; }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Действие: Добавить проверку для целевой вершины.
@Test void constructor() { DirectedEdgeedge = new DirectedEdge<>(); assertThat(edge.getSource()).isEqualTo("A"); assertThat(edge.getTarget()).isEqualTo("B"); }
Статус теста: ❌ сбой , написать производственный код
Действие: Добавьте метод getTarget()
в |/DirectedEdge .
public T getTarget() { return (T) "B"; }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Действие: замените конструктор по умолчанию на требуемый конструктор args.
@Test void constructor() { DirectedEdgeedge = new DirectedEdge<>("A", "B"); assertThat(edge.getSource()).isEqualTo("A"); assertThat(edge.getTarget()).isEqualTo("B"); }
Статус теста: ❌ сбой , написать производственный код
Действие: Добавить requiredargsconstructor (удаление конструктора по умолчанию).
public final class DirectedEdge{ public DirectedEdge(T source, T target) { } public T getSource() { return (T) "A"; } public T getTarget() { return (T) "B"; } }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Действие: Добавьте новый тест конструктора с другим источником и целью.
@Test void constructor_AB() { DirectedEdgeedge = new DirectedEdge<>("A", "B"); assertThat(edge.getSource()).isEqualTo("A"); assertThat(edge.getTarget()).isEqualTo("B"); } @Test void constructor_XY() { DirectedEdge edge = new DirectedEdge<>("X", "Y"); assertThat(edge.getSource()).isEqualTo("X"); assertThat(edge.getTarget()).isEqualTo("Y"); }
Статус теста: ❌ сбой , написать производственный код
Действие: Добавьте переменные-члены и назначьте в конструкторе.
public final class DirectedEdge{ private final T source; private final T target; public DirectedEdge(T source, T target) { this.source = source; this.target = target; } public T getSource() { return "A"; } public T getTarget() { return "B"; } }
Статус теста: ❌ сбой , написать производственный код
Действие: Возвращает переменные-члены из геттеров.
public T getSource() { return source; } public T getTarget() { return target; }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Действие: Добавление нулевых проверок
Не имеет смысла разрешать нулевой источник
или цель
. Эти параметры должны быть проверены в конструкторе. Хороший способ сделать это – использовать Objects.requireNonNull
.
@Test void constructor_fails_on_null_source() { assertThatNullPointerException().isThrownBy(() -> new DirectedEdge<>(null, "B")); }
Статус теста: ❌ сбой , написать производственный код
, написать производственный код
public DirectedEdge(T source, T target) { this.source = requireNonNull(source); this.target = target; }
, написать производственный код
, написать производственный код , написать производственный код
, написать производственный код , написать производственный код . , написать производственный код
assertThatNullPointerException() .isThrownBy(() -> new DirectedEdge<>(null, "B")) .withMessage("source must not be null");
, написать производственный код , написать производственный код
, написать производственный код
, написать производственный код
this.source = requireNonNull(source, "source must not be null");
, написать производственный код
, написать производственный код
@Test void constructor_fails_on_null_target() { assertThatNullPointerException() .isThrownBy(() -> new DirectedEdge<>("A", null)) .withMessage("target must not be null"); }
, написать производственный код , написать производственный код
, написать производственный код , написать производственный код
public DirectedEdge(T source, T target) { this.source = requireNonNull(source, "source must not be null"); this.target = requireNonNull(target, "target must not be null"); }
, написать производственный код
, написать производственный код , написать производственный код .
, написать производственный код , написать производственный код
, написать производственный код , написать производственный код , написать производственный код и
целевой . Вместо того, чтобы писать все эти тесты, есть доступный инструмент, который может выполнять эти тесты за нас. Это будет сделано с помощью
equals-verifier .
@Test void equalsContract() { EqualsVerifier.forClass(DirectedEdge.class).verify(); }
Статус теста: ❌ сбой , написать производственный код
Действие: Write equals
и/| hashCode методы с использованием IDE.
Чтобы сгенерировать этот код в intellij, я использовал формат java 7+, а исходный и целевой значения не равны нулю.
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DirectedEdge> that = (DirectedEdge>) o; return source.equals(that.source) && target.equals(that.target); } @Override public int hashCode() { return Objects.hash(source, target); }
Статус теста: ❌ сбой , написать производственный код
Тест equals Contract
все равно завершится неудачей, поскольку source
и target
не являются нулевыми.
Действие: Подавите эти предупреждения.
EqualsVerifier.forClass(DirectedEdge.class).suppress(Warning.NULL_FIELDS).verify();
Тестовое задание: ✔️ прохождение, запись неудачного теста
Действие: Действие:
Теперь, когда есть Направленное ребро
класс ребер в Ориентированном графе
класс конструктора может быть проверен.
@Test void constructor() { DirectedGraphgraph = new DirectedGraph<>(); assertThat(graph.getVertices()).isEmpty(); assertThat(graph.getEdges()).isEmpty(); }
Статус теста: ❌ сбой , написать производственный код
Действие: Добавить getEdges
метод
public Set> getEdges() { return new HashSet<>(); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
На этом шаге мы проверяем, что вершины могут быть добавлены в граф.
Действие: Сделать тест для добавления вершин
@Test void vertex_adds_new_vertex() { DirectedGraphgraph = new DirectedGraph<>(); graph.vertex("A"); }
Статус теста: ❌ сбой , написать производственный код
Действие: Добавить вершину
метод
public void vertex(T vertex) { }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Действие: Добавить проверку на наличие добавленной вершины
@Test void vertex_adds_new_vertex() { DirectedGraphgraph = new DirectedGraph<>(); graph.vertex("A"); assertThat(graph.getVertices()).containsExactly("A"); }
Статус теста: ❌ сбой , написать производственный код
Этот тест доказывает, что Ориентированный граф
должен хранить вершины.
Действие: Добавьте элемент vertices и измените метод vertex
, чтобы добавить вершину. Измените getVertices() |/| чтобы вернуть вершины.
public class DirectedGraph{ private final Set vertices; public DirectedGraph() { vertices = new HashSet<>(); } public Set getVertices() { return vertices; } public Set > getEdges() { return new HashSet<>(); } public void vertex(T vertex) { vertices.add(vertex); } }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Набор, возвращаемый getVertices()
, должен быть неизменяемым.
Действие: Добавить тест, гарантирующий неизменяемость вершин.
@Test void vertices_are_unmodifiable() { DirectedGraphgraph = new DirectedGraph<>(); Set vertices = graph.getVertices(); assertThatThrownBy(() -> vertices.add(10)) .isInstanceOf(UnsupportedOperationException.class); }
Статус теста: ❌ сбой , написать производственный код
Действие: возвращает неизменяемый набор в getVertices()
.
public SetgetVertices() { return unmodifiableSet(vertices); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
То же самое можно сделать для get Edges()
.
* Действие: * Добавьте тест для getEdges()
, гарантирующий неизменяемость набора.
@Test void edges_are_unmodifiable() { DirectedGraphgraph = new DirectedGraph<>(); Set > edges = graph.getEdges(); assertThatThrownBy(() -> edges.add(new DirectedEdge<>(1, 2))) .isInstanceOf(UnsupportedOperationException.class); }
Статус теста: ❌ сбой , написать производственный код
Действие: возвращает неизменяемый набор в getEdges()
.
public Set> getEdges() { return unmodifiableSet(new HashSet<>()); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
null
вершины не должны быть разрешены.
Действие: вершина
сбой при использовании нулевого параметра.
@Test void vertex_fails_on_null_vertex() { DirectedGraphgraph = new DirectedGraph<>(); assertThatNullPointerException() .isThrownBy(() -> graph.vertex(null)) .withMessage("vertex must not be null"); }
Статус теста: ❌ сбой , написать производственный код
Действие: проверьте наличие нулевых вершин.
public void vertex(T vertex) { requireNonNull(vertex, "vertex must not be null"); vertices.add(vertex); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Теперь, когда вершины могут быть добавлены к графу, ребра должны быть добавлены к графу. Этот метод должен гарантировать, что вершины будут добавлены, если они отсутствуют в графе. На этот раз тесты начинаются с проверки на наличие ошибок. Идея состоит в том, чтобы проверить наличие ошибок перед написанием кода happy path.
* Действие: * Добавьте проверку исходного параметра null в метод edge
.
@Test void edge_fails_on_null_source() { DirectedGraphgraph = new DirectedGraph<>(); assertThatNullPointerException() .isThrownBy(() -> graph.edge(null, "B")) .withMessage("source must not be null"); }
Статус теста: ❌ сбой , написать производственный код
Метод edge
не существует и должен быть добавлен.
public void edge(T source, T target) { }
Статус теста: ❌ сбой , написать производственный код
Теперь метод edge
должен выдать исключение.
* Действие: * Добавьте NPE в метод edge
.
public void edge(T source, T target) { throw new NullPointerException("source must not be null"); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Метод edge
также должен проверять target
на наличие null.
* Действие: * Добавьте тест для нулевого целевого параметра в метод edge
.
@Test void edge_fails_on_null_target() { DirectedGraphgraph = new DirectedGraph<>(); assertThatNullPointerException() .isThrownBy(() -> graph.edge("A", null)) .withMessage("target must not be null"); }
Статус теста: ❌ сбой , написать производственный код
Метод edge
теперь должен фактически проверять источник на наличие нулевого значения.
* Действие: * Добавьте нулевые проверки для источника и цели в методе edge
.
public void edge(T source, T target) { requireNonNull(source, "source must not be null"); requireNonNull(target, "target must not be null"); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Метод edge
должен добавить исходную вершину, если ее еще нет в графе.
* Действие: * Добавьте тест для исходной вершины в графе после вызова edge
.
@Test void edge_adds_source_vertex() { DirectedGraphgraph = new DirectedGraph<>(); graph.edge("A", "B"); assertThat(graph.getVertices()).contains("A"); }
Статус теста: ❌ сбой , написать производственный код
* Действие: * Добавьте исходную вершину в граф в методе edge
.
public void edge(T source, T target) { requireNonNull(source, "source must not be null"); requireNonNull(target, "target must not be null"); vertex(source); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Целевая вершина также должна быть добавлена к графику.
* Действие: * Добавьте тест для целевой вершины в графе после вызова edge
.
@Test void edge_adds_target_vertex() { DirectedGraphgraph = new DirectedGraph<>(); graph.edge("A", "B"); assertThat(graph.getVertices()).contains("B"); }
Статус теста: ❌ сбой , написать производственный код
* Действие: * Добавьте исходную вершину в граф в методе edge
.
public void edge(T source, T target) { requireNonNull(source, "source must not be null"); requireNonNull(target, "target must not be null"); vertex(source); vertex(target); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Фактическое ребро также должно быть добавлено.
* Фактическое ребро также должно быть добавлено. Фактическое ребро также должно быть добавлено.
@Test void edge_adds_an_edge() { DirectedGraphgraph = new DirectedGraph<>(); graph.edge("A", "B"); assertThat(graph.getEdges()).containsExactly(new DirectedEdge<>("A", "B")); }
Фактическое ребро также должно быть добавлено. Фактическое ребро также должно быть добавлено.
Тест доказывает, что edges
должна быть переменной-членом, которая требует изменений в конструкторе getEdges()
и метод edge
.
* Действие: * Добавьте переменную-член edges, используйте элемент в getEdges
и ребра
метод.
public class DirectedGraph{ private final Set vertices; private final Set > edges; public DirectedGraph() { vertices = new HashSet<>(); edges = new HashSet<>(); } public Set getVertices() {...} public Set > getEdges() { return unmodifiableSet(edges); } public void vertex(T vertex) {...} public void edge(T source, T target) { requireNonNull(source, "source must not be null"); requireNonNull(target, "target must not be null"); vertex(source); vertex(target); edges.add(new DirectedEdge<>(source, target)); } }
Тестовое задание: ✔️ прохождение, запись неудачного теста
На этом этапе можно добавить метод для удаления ребер.
* Действие: * Добавьте тест для removeEdge
.
@Test void removeEdge_fails_on_null_source() { DirectedGraphgraph = new DirectedGraph<>(); assertThatNullPointerException() .isThrownBy(() -> graph.removeEdge(null, "A")) .withMessage("source must not be null"); }
Статус теста: ❌ сбой , написать производственный код
* Действие: * для компиляции необходимо добавить remove Edge
.
public void removeEdge(T source, T target) { }
Статус теста: ❌ сбой , написать производственный код
* Действие: * Добавьте проверку на наличие нулевого источника.
public void removeEdge(T source, T target) { requireNonNull(source, "source must not be null"); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
* Действие: * Напишите неудачный тест, проверяющий нулевую цель.
@Test void removeEdge_fails_on_null_target() { DirectedGraphgraph = new DirectedGraph<>(); assertThatNullPointerException() .isThrownBy(() -> graph.removeEdge("A", null)) .withMessage("target must not be null"); }
Статус теста: ❌ сбой , написать производственный код
* Действие: * Добавить проверку removeEdge
для null target
.
public void removeEdge(T source, T target) { requireNonNull(source, "source must not be null"); requireNonNull(target, "target must not be null"); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
удалить ребро
должно завершиться неудачей, если ребро не найдено.
* Действие: * Добавьте тест, ожидающий исключения, когда edge не существует.
@Test void removeEdge_fails_on_missing_edge() { DirectedGraphgraph = new DirectedGraph<>(); assertThatIllegalArgumentException() .isThrownBy(() -> graph.removeEdge("A", "B")) .withMessage("edge with source \"A\" and target \"B\" does not exist"); }
Статус теста: ❌ сбой , написать производственный код
* Действие: * Добавьте проверку, чтобы увидеть, содержит ли ребро ребро, и выдайте исключение, если оно отсутствует.
public void removeEdge(T source, T target) { requireNonNull(source, "source must not be null"); requireNonNull(target, "target must not be null"); if(!edges.contains(new DirectedEdge<>(source, target))) { throw new IllegalArgumentException(String.format("edge with source \"%s\" and target \"%s\" does not exist", source, target)); } }
Тестовое задание: ✔️ прохождение, запись неудачного теста
* Действие: * Добавьте тест для удаления и ребра из графика.
@Test void removeEdge_removes_edge() { DirectedGraphgraph = new DirectedGraph<>(); graph.edge("A", "B"); graph.removeEdge("A", "B"); assertThat(graph.getEdges()).isEmpty(); }
Статус теста: ❌ сбой , написать производственный код
Вместо вызова содержит
код может вызвать удалить
. Это приведет к удалению ребра и все равно вызовет исключение, если ребро не существует в графике.
* Действие: * Используйте удалить
вместо содержит
.
public void removeEdge(T source, T target) { requireNonNull(source, "source must not be null"); requireNonNull(target, "target must not be null"); if(!edges.remove(new DirectedEdge<>(source, target))) { throw new IllegalArgumentException(String.format("edge with source \"%s\" and target \"%s\" does not exist", source, target)); } }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Теперь необходим метод для удаления вершин. Давайте напишем для этого тест.
* Действие: * Добавьте тест для удаления вершин.
@Test void removeVertex_removes_vertex() { DirectedGraphgraph = new DirectedGraph<>(); graph.vertex("A"); graph.removeVertex("A"); assertThat(graph.getVertices()).isEmpty(); }
Тест не будет скомпилирован.
Статус теста: ❌ сбой , написать производственный код
* Действие: * Добавить удалить вершину
метод.
public void removeVertex(T vertex) { }
Статус теста: ❌ сбой , написать производственный код
* Действие: * удалить вершину.
public void removeVertex(T vertex) { vertices.remove(vertex); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
* Действие: * добавьте тест для параметра null vertex в |/removeVertex .
@Test void removeVertex_fails_on_null_vertex() { DirectedGraphgraph = new DirectedGraph<>(); assertThatNullPointerException() .isThrownBy(() ->graph.removeVertex(null)) .withMessage("vertex must not be null"); }
Статус теста: ❌ сбой , написать производственный код
* Действие: * Добавьте нулевую проверку к методу.
public void removeVertex(T vertex) { requireNonNull(vertex, "vertex must not be null"); vertices.remove(vertex); }
Тестовое задание: ✔️ прохождение, запись неудачного теста
Существует еще одно условие отказа при удалении вершин. Если удаляемая вершина имеет смежные ребра, эти ребра также должны быть удалены.
Действие: Добавьте тест, проверяющий, удаляются ли соседние ребра при удалении вершины.
@Test void removeVertex_removes_adjacent_edges() { DirectedGraphgraph = new DirectedGraph<>(); graph.edge("A", "B"); graph.removeVertex("A"); assertThat(graph.getEdges()).isEmpty(); }
Статус теста: ❌ сбой , написать производственный код
Действие: Найдите все смежные ребра и удалите их в removeVertex
.
public void removeVertex(T vertex) { requireNonNull(vertex, "vertex must not be null"); Iterator> i = edges.iterator(); while(i.hasNext()) { DirectedEdge next = i.next(); if(next.getSource().equals(vertex) || next.getTarget().equals(vertex)) { i.remove(); } } vertices.remove(vertex); }
Тестовые задания: ✔ passing прохождение, рефакторинг 🔄 изменение кода таким образом, чтобы тесты не проваливались
Сейчас самое время провести рефакторинг. Рефакторинг на самом деле предложен Intellij. Он заменяет итератор вызовом remove If |/с использованием лямбда-выражения.
Действие: Рефакторинг removeVertex
для использования удалить, если
при удалении смежных ребер.
public void removeVertex(T vertex) { requireNonNull(vertex, "vertex must not be null"); edges.removeIf(next -> next.getSource().equals(vertex) || next.getTarget().equals(vertex)); vertices.remove(vertex); }
удалить, если при удалении смежных ребер.
Класс Ориентированный граф
теперь может создавать и удалять вершины и ребра. В этом руководстве описан дизайн для графиков и представлено пошаговое руководство по созданию Ориентированный граф
с использованием TDD.
Есть еще несколько проблем, которые необходимо решить, но это хорошее начало в представлении графика в виде кода.
Оригинал: “https://dev.to/moaxcp/lets-code-graphs-in-java-1kem”