Статьи

Учебник по Java с помощью Katas: Mars Rover

Программирование ката — это упражнение, которое помогает программисту отточить свои навыки посредством практики и повторения.

Эта статья является частью серии Java Tutorial Through Katas .

В статье предполагается, что читатель уже имеет опыт работы с Java, что он знаком с основами модульных тестов и знает, как запускать их из своей любимой среды IDE (моя — IntelliJ IDEA ).

Тесты, которые доказывают, что решение является правильным, показаны ниже. Рекомендуемый способ решения этой проблемы заключается в использовании подхода к разработке на основе тестов (напишите реализацию для первого теста, подтвердите, что он прошел, и перейдите к следующему). Как только все тесты пройдены, ката может считаться решенной. Для получения дополнительной информации о передовых практиках, пожалуйста, прочитайте Тестирование по разработке (TDD): Лучшие практики с использованием примеров Java .

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

Марсоход

Разработайте API, который перемещает ровер по сетке.

Правила:

  • Вам дана начальная начальная точка (x, y) ровера и направление (N, S, E, W), на которое он направлен.
  • Ровер получает массив символов команд.
  • Реализуйте команды, которые перемещают ровер вперед / назад (f, b).
  • Реализуйте команды, которые поворачивают ровер влево / вправо (l, r).
  • Реализация переноса от одного края сетки к другому. (планеты это сферы в конце концов)
  • Реализуйте обнаружение препятствий перед каждым переходом на новый квадрат. Если данная последовательность команд встречает препятствие, ровер перемещается до последней возможной точки и сообщает о препятствии.

тесты

Ниже приведен набор модульных тестов, которые можно использовать для решения этого ката способом TDD .

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.technologyconversations.kata.marsrover;
 
import org.junit.Before;
import org.junit.Test;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
import static org.assertj.core.api.Assertions.*;
 
/*
 
Develop an api that moves a rover around on a grid.
* You are given the initial starting point (x,y) of a rover and the direction (N,S,E,W) it is facing.
* - The rover receives a character array of commands.
* - Implement commands that move the rover forward/backward (f,b).
* - Implement commands that turn the rover left/right (l,r).
* - Implement wrapping from one edge of the grid to another. (planets are spheres after all)
* - Implement obstacle detection before each move to a new square.
*   If a given sequence of commands encounters an obstacle, the rover moves up to the last possible point and reports the obstacle.
*/
public class RoverSpec {
 
    private Rover rover;
    private Coordinates roverCoordinates;
    private final Direction direction = Direction.NORTH;
    private Point x;
    private Point y;
    private List<Obstacle> obstacles;
 
    @Before
    public void beforeRoverTest() {
        x = new Point(1, 9);
        y = new Point(2, 9);
        obstacles = new ArrayList<Obstacle>();
        roverCoordinates = new Coordinates(x, y, direction, obstacles);
        rover = new Rover(roverCoordinates);
    }
 
    @Test
    public void newInstanceShouldSetRoverCoordinatesAndDirection() {
        assertThat(rover.getCoordinates()).isEqualToComparingFieldByField(roverCoordinates);
    }
 
    @Test
    public void receiveSingleCommandShouldMoveForwardWhenCommandIsF() throws Exception {
        int expected = y.getLocation() + 1;
        rover.receiveSingleCommand('F');
        assertThat(rover.getCoordinates().getY().getLocation()).isEqualTo(expected);
    }
 
    @Test
    public void receiveSingleCommandShouldMoveBackwardWhenCommandIsB() throws Exception {
        int expected = y.getLocation() - 1;
        rover.receiveSingleCommand('B');
        assertThat(rover.getCoordinates().getY().getLocation()).isEqualTo(expected);
    }
 
    @Test
    public void receiveSingleCommandShouldTurnLeftWhenCommandIsL() throws Exception {
        rover.receiveSingleCommand('L');
        assertThat(rover.getCoordinates().getDirection()).isEqualTo(Direction.WEST);
    }
 
    @Test
    public void receiveSingleCommandShouldTurnRightWhenCommandIsR() throws Exception {
        rover.receiveSingleCommand('R');
        assertThat(rover.getCoordinates().getDirection()).isEqualTo(Direction.EAST);
    }
 
    @Test
    public void receiveSingleCommandShouldIgnoreCase() throws Exception {
        rover.receiveSingleCommand('r');
        assertThat(rover.getCoordinates().getDirection()).isEqualTo(Direction.EAST);
    }
 
    @Test(expected = Exception.class)
    public void receiveSingleCommandShouldThrowExceptionWhenCommandIsUnknown() throws Exception {
        rover.receiveSingleCommand('X');
    }
 
    @Test
    public void receiveCommandsShouldBeAbleToReceiveMultipleCommands() throws Exception {
        int expected = x.getLocation() + 1;
        rover.receiveCommands("RFR");
        assertThat(rover.getCoordinates().getX().getLocation()).isEqualTo(expected);
        assertThat(rover.getCoordinates().getDirection()).isEqualTo(Direction.SOUTH);
    }
 
    @Test
    public void receiveCommandShouldWhatFromOneEdgeOfTheGridToAnother() throws Exception {
        int expected = x.getMaxLocation() + x.getLocation() - 2;
        rover.receiveCommands("LFFF");
        assertThat(rover.getCoordinates().getX().getLocation()).isEqualTo(expected);
    }
 
    @Test
    public void receiveCommandsShouldStopWhenObstacleIsFound() throws Exception {
        int expected = x.getLocation() + 1;
        rover.getCoordinates().setObstacles(Arrays.asList(new Obstacle(expected + 1, y.getLocation())));
        rover.getCoordinates().setDirection(Direction.EAST);
        rover.receiveCommands("FFFRF");
        assertThat(rover.getCoordinates().getX().getLocation()).isEqualTo(expected);
        assertThat(rover.getCoordinates().getDirection()).isEqualTo(Direction.EAST);
    }
 
    @Test
    public void positionShouldReturnXYAndDirection() throws Exception {
        rover.receiveCommands("LFFFRFF");
        assertThat(rover.getPosition()).isEqualTo("8 X 4 N");
    }
 
    @Test
    public void positionShouldReturnNokWhenObstacleIsFound() throws Exception {
        rover.getCoordinates().setObstacles(Arrays.asList(new Obstacle(x.getLocation() + 1, y.getLocation())));
        rover.getCoordinates().setDirection(Direction.EAST);
        rover.receiveCommands("F");
        assertThat(rover.getPosition()).endsWith(" NOK");
    }
 
}

Одним из возможных решений является следующее.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.technologyconversations.kata.marsrover;
 
/*
Method receiveCommands should be used to transmit commands to the rover.
 */
public class Rover {
 
    private Coordinates coordinates;
    public void setCoordinates(Coordinates value) {
        coordinates = value;
    }
    public Coordinates getCoordinates() {
        return coordinates;
    }
 
    public Rover(Coordinates coordinatesValue) {
        setCoordinates(coordinatesValue);
    }
 
    public void receiveCommands(String commands) throws Exception {
        for (char command : commands.toCharArray()) {
            if (!receiveSingleCommand(command)) {
                break;
            }
        }
    }
 
    public boolean receiveSingleCommand(char command) throws Exception {
        switch(Character.toUpperCase(command)) {
            case 'F':
                return getCoordinates().moveForward();
            case 'B':
                return getCoordinates().moveBackward();
            case 'L':
                getCoordinates().changeDirectionLeft();
                return true;
            case 'R':
                getCoordinates().changeDirectionRight();
                return true;
            default:
                throw new Exception("Command " + command + " is unknown.");
        }
    }
 
    public String getPosition() {
        return getCoordinates().toString();
    }
 
}

Полный источник находится в репозитории GitHub [https://github.com/vfarcic/mars-rover-kata-java). Над кодом представлен только код основного класса. Есть несколько других классов / объектов с их соответствующей спецификацией. Помимо тестов и реализации, репозиторий включает build.gradle, который может использоваться, среди прочего, для загрузки зависимостей AssertJ и запуска тестов. README.md содержит краткие инструкции по настройке проекта.

Каково было ваше решение? Опубликуйте это как комментарий, чтобы мы могли сравнить различные способы решения этой ката.

Ссылка: Учебник по Java с помощью Katas: Mars Rover от нашего партнера по JCG Виктора Фарчича в блоге по технологиям .