Статьи

Сборка игры Mancala в микросервисах с использованием Spring Boot (часть 4: тестирование)

Манкала для всех! 

В первой статье « Создание игры Mancala на микросервисах с использованием Spring Boot (Solution Architecture) » я объяснил общую архитектуру решения, принятого для реализации игры Mancala с использованием подхода Microservices.

Во второй части ( Построение игры Mancala на микросервисах с использованием Spring Boot (реализация API Mancala )) я подробно рассмотрел реализацию микросервиса mancala-api .

В части 3 ( Создание игры Mancala в микросервисах с использованием Spring Boot (реализация микросервиса веб-клиента ) я предоставил подробности реализации микросервиса mancala-web ‘, который является единственным клиентом для службыmancala-api ‘ в нашем примере.

Теперь в этой статье я собираюсь обсудить важность подхода TDD  (Test Driven Development) и разработки различных сред Tests для нашей реализации Mancala Game.

Полный исходный код этой статьи доступен в моем GitHub

Чтобы создать и запустить это приложение, пожалуйста, следуйте инструкциям, которые я предоставил здесь

1 — TDD и его значение

В настоящее время проектирование и разработка надежного API для сервисов имеет решающее значение, зная, что когда мы используем архитектуру Microservices, мы можем иметь дело с сотнями, а в некоторых случаях тысячи микросервисов взаимодействуют друг с другом через свои открытые API или События, а не через старые монолитные интерфейсы. -процессный механизм связи. Следовательно, существует необходимость в разработке более надежных API с учетом подхода TDD. 

Существуют различные инструменты и инфраструктуры, помогающие разработчикам создавать более совершенные API-интерфейсы для своих микросервисов, такие как Swagger.io , ApiBuilder.io , Apicuri.io и т. Д. Однако использование методологии TDD (Test Driven Development) для разработки надежных API-интерфейсов и их влияния Предоставление готовых к использованию API-интерфейсов неизбежно для всех в этой области. Поэтому с ростом популярности микросервисов TDD является одной из тех методологий, которая является доминирующей среди большинства ИТ-компаний из-за ее большого влияния на создание таких надежных API для микросервисов. 

В этой статье я написал, что подробно рассмотрел все аспекты тестирования для наших обоих микроуслуг , « mancala-api » и « mancala-web », чтобы вы могли применять их аналогичным образом в своих собственных проектах. 

2 — Модульное тестирование с использованием Spring Boot Test

Spring Boot предоставляет ряд утилит и аннотаций, которые помогут при тестировании вашего приложения. 

Поддержка тестирования обеспечивается двумя модулями: spring-boot-testсодержит основные элементы и spring-boot-test-autoconfigureподдерживает автоматическую настройку для тестов.

Я использовал spring-boot-starter-test«Starter», который импортирует как тестовые модули Spring Boot, так и JUnit, AssertJ, Hamcrest и ряд других полезных библиотек.

Когда мы делаем TDD, у нас есть два подхода:

  1. Одним из них является тестирование наизнанку .

  2. Другой — вне  тестирования.

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

3 — Дизайн API

При разработке API игры Mancala, основанного на принципах SOLID , я остановился на следующих интерфейсах и классах для микросервиса mancala-api’ :

Модельные классы

KalahaGame  класс

Этот класс содержит полную информацию об одной игре Mancala, включая идентификатор, ямы и ход игрока:


@Document(collection = "games")
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class KalahaGame implements Serializable{

    @Id
    private String id;

    private List<KalahaPit> pits;

    private PlayerTurns playerTurn;

    public KalahaGame(int pitStones) 
    { 
      // implementation goes here 
    }
}

КалахаПит  класс

Этот класс представляет одну Яму в игре Манкала:

@AllArgsConstructor
@NoArgsConstructor
@Data
public class KalahaPit implements Serializable {

    private Integer id;
    private Integer stones;

    @Override
    public String toString() {
        return  id.toString() +
                ":" +
                stones.toString() ;
    }
}

KalahaHouse  класс

Этот класс является подклассом KalahaPit, представляющим один Kalaha House:


public class KalahaHouse extends KalahaPit {
    public KalahaHouse(Integer id) {
        super(id , 0);
    }
}

API-интерфейсы

KalahaGameApi

public interface KalahaGameApi {
    KalahaGame createGame(int stones);
}

KalahaSowingApi

public interface KalahaSowingApi {
    KalahaGame sow (KalahaGame game, int pitIndex);
}

Классы реализации API

MancalaGameService

Этот класс обеспечивает реализацию для интерфейса KalahaGameApi: 

@Service
public class KalahaGameService implements KalahaGameApi {

    @Autowired
    private KalahaGameRepository kalahaGameRepository;

    @Override
    public KalahaGame createGame(int pitStones) {

      KalahaGame kalahaGame = new KalahaGame(pitStones);

       // implementation goes here

        return kalahaGame;
    }
}

MancalaSowingService

Этот класс обеспечивает реализацию для интерфейса KalahaSowingApi:


@Service
public class MancalaSowingService implements KalahaSowingApi {

    // This method perform sowing the game on specific pit index
    @Override
    public KalahaGame sow(KalahaGame game, int requestedPitId) {
// implementation goes here
        return game;
    }      
}

Интерфейсы репозиториев

KalahaGameRepository

Этот интерфейс представляет собой реализацию Spring Boot хранилища MongoDB, отвечающего за операции CRUD экземпляров KalahaGame:

public interface KalahaGameRepository extends MongoRepository<KalahaGame, String> {
}

Классы контроллера

MancalaController

Этот класс предоставляет конечную точку для клиентов, получающих доступ к реализации Mancala API:

@Slf4j
@RestController
@RequestMapping("/games")
public class MancalaController {

    @Autowired
    private KalahaGameService mancalaGameService;

    @Autowired
    private MancalaSowingService mancalaSowingService;

    @Value("${mancala.pit.stones}")
    private Integer pitStones;

    @PostMapping
    public ResponseEntity<KalahaGame> createGame() throws Exception {

        KalahaGame game = mancalaGameService.createGame(pitStones);

        // the rest of implementation goes here

        return ResponseEntity.ok(game);
    }

    @PutMapping(value = "{gameId}/pits/{pitId}")    
    public ResponseEntity<KalahaGame> sowGame(            
            @PathVariable(value = "gameId") String gameId,
            @PathVariable(value = "pitId") Integer pitId) throws Exception {

        KalahaGame kalahaGame = mancalaGameService.loadGame(gameId);

        kalahaGame = mancalaSowingService.sow(kalahaGame, pitId);

        // the rest of implementation goes here

        return ResponseEntity.ok(kalahaGame);
    }
}

4 — Дорожная карта тестирования

Я начну писать тесты в следующем порядке:

  1. Начало тестирования  реализации игра  классов , которые обеспечивают правильное выполнение логики Манкала игры первым.

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

  3. Затем написание тестов Business Services, которые проверят правильность Business Services  .

  4.   Затем Контроллер  проверяет,  проверяет ли мой контроллер правильный ответ на запросы каждого клиента.
  5. Затем написание тестов интеграции  для микросервиса ‘mancala-web’, который является единственным клиентом для микросервиса ‘mancala-api’ в этом примере с использованием Wiremock .

  6. Наконец, написание тестов Consumer Driven Contract (CDC), чтобы убедиться, что реализация API для Mancala Game, предоставляемая микросервисом ‘mancala-api’, остается синхронизированной с веб-клиентом Mancala на основе определенных контрактов.

5 — Манкала игровые юнит-  тесты

Этот класс будет реализовывать логику сева для игры Mancala, и поэтому мы должны рассмотреть различные ситуации, когда игра может вести себя по-разному, в соответствии с правилами игры Mancala. Я использую Mockito для этих тестов:

@RunWith(MockitoJUnitRunner.class)
@DirtiesContext
public class MancalaSowingServiceTests {

    private static final Integer defaultPitStones = 6;

    private KalahaGame game;

    private MancalaSowingService mancalaSowingService;

    @Before
    public void setupTest (){
        this.game = new KalahaGame(defaultPitStones);
        this.mancalaSowingService = new MancalaSowingService();
    }

    @Test
    public void testGameCreation () {
        Assert.assertNotNull(this.game);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
    }

    @Test
    public void testSowOfSecondPitPlayerA(){
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
    }

    @Test
    public void testSowOfSecondPitPlayerB(){
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
    }


    @Test
    public void testSowOfSixthPitPlayerA(){
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 6);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
    }

    @Test
    public void testWrongTurnByPlayerA(){
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 6);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        // This is a wrong turn therefore the output remains the same as well as player turn
        this.game = mancalaSowingService.sow(this.game, 1);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
    }

    @Test
    public void testSowOfSixthPitPlayerB(){
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 6);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 13);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:8, 4:8, 5:8, 6:1, 7:2, 8:9, 9:1, 10:8, 11:8, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
    }

    @Test
    public void testWrongTurnByPlayerB(){
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 6);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 13);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:8, 4:8, 5:8, 6:1, 7:2, 8:9, 9:1, 10:8, 11:8, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        // This is a wrong turn therefore the output remains the same as well as player turn
        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:8, 4:8, 5:8, 6:1, 7:2, 8:9, 9:1, 10:8, 11:8, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
    }

    @Test
    public void testSowOfCollectingOppositePitStoneByPlayerA(){
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 6);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 13);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:8, 4:8, 5:8, 6:1, 7:2, 8:9, 9:1, 10:8, 11:8, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 3);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:0, 4:9, 5:9, 6:2, 7:3, 8:10, 9:2, 10:9, 11:9, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:0, 4:9, 5:9, 6:2, 7:3, 8:10, 9:0, 10:10, 11:10, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:0, 3:0, 4:9, 5:9, 6:2, 7:14, 8:10, 9:0, 10:10, 11:0, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
    }

    @Test
    public void testSowOfCollectingOppositePitStoneByPlayerB(){
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 6);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 13);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:8, 4:8, 5:8, 6:1, 7:2, 8:9, 9:1, 10:8, 11:8, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 3);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:0, 4:9, 5:9, 6:2, 7:3, 8:10, 9:2, 10:9, 11:9, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:0, 4:9, 5:9, 6:2, 7:3, 8:10, 9:0, 10:10, 11:10, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:0, 3:0, 4:9, 5:9, 6:2, 7:14, 8:10, 9:0, 10:10, 11:0, 12:8, 13:0, 14:2]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 8);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:9, 2:1, 3:1, 4:10, 5:9, 6:2, 7:14, 8:0, 9:1, 10:11, 11:1, 12:9, 13:1, 14:3]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 1);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:0, 2:2, 3:2, 4:11, 5:10, 6:3, 7:15, 8:1, 9:2, 10:12, 11:1, 12:9, 13:1, 14:3]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 9);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:0, 2:2, 3:2, 4:11, 5:10, 6:3, 7:15, 8:1, 9:0, 10:13, 11:2, 12:9, 13:1, 14:3]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");

        this.game = mancalaSowingService.sow(this.game, 2);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:0, 2:0, 3:3, 4:12, 5:10, 6:3, 7:15, 8:1, 9:0, 10:13, 11:2, 12:9, 13:1, 14:3]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");

        this.game = mancalaSowingService.sow(this.game, 8);
        Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:0, 2:0, 3:3, 4:12, 5:0, 6:3, 7:15, 8:0, 9:0, 10:13, 11:2, 12:9, 13:1, 14:14]");
        Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
    }
}

MongoDB Репозиторий Тесты

Я использовал Embedded MongoDB для точного тестирования репозитория MongoDB и связанных служб. Поэтому я включил следующие зависимости в файл pom.xml:

<!-- Embedded MongoDB used for Tests -->
<dependency>
  <groupId>de.flapdoodle.embed</groupId>
  <artifactId>de.flapdoodle.embed.mongo</artifactId>
  <scope>test</scope>
</dependency>

@DataMongoTest
@RunWith(SpringRunner.class)
@DirtiesContext
public class KalahaGameRepositoryTests {

    @Autowired
    KalahaGameRepository gameRepository;

    @Test
    public void testRepository() throws  Exception {
        this.gameRepository.deleteAll();

        KalahaGame saved= this.gameRepository.save(new KalahaGame(6));

        Assert.assertNotNull(saved.getId());

        List<KalahaGame> list  = this.gameRepository.findAll();

        Assert.assertEquals(1, list.size());
    }
}

KalahaGameService Тесты

Этот класс проверяет функциональность класса KalahaGameService, который отвечает за управление экземплярами KalahaGame с нашим постоянным хранилищем MongoDB, а также за загрузку и хранение их в кэш-системе Redis. Для запуска этих тестов нам нужно добавить встроенный сервер Redis в наш проект, как описано ниже.

Встроенный Redis

Шаг 1:  Добавление приведенной ниже зависимости в файл проекта pom.xml:

<!-- Redis Embedded server -->
<dependency>
  <groupId>com.github.kstyrc</groupId>
  <artifactId>embedded-redis</artifactId>
  <version>0.6</version>
</dependency>

Шаг 2. Добавление класса ниже для управления операциями запуска и завершения сервера Redis в нашей тестовой базе кода:

@Component
public class EmbeddedRedis implements ExitCodeGenerator, DisposableBean{

    @Value("${spring.redis.port}")
    private int redisPort;

    private RedisServer redisServer;

    @PostConstruct
    public void startRedis() throws IOException {
        redisServer = RedisServer.builder()
                .port(redisPort)
                .setting("maxmemory 128M") //maxheap 128M
                .build();
        redisServer.start();
    }

    @PreDestroy
    public void stopRedis() {
        redisServer.stop();
    }

    @Override
    public int getExitCode() {
        redisServer.stop();
        return 0;
    }

    @Override
    public void destroy() throws Exception {
        redisServer.stop();
    }
}

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

KalahaGameServiceTests

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

package com.dzone.mancala.game.service;

import com.dzone.mancala.game.model.KalahaGame;
import com.dzone.mancala.game.model.KalahaPit;
import com.dzone.mancala.game.repository.KalahaGameRepository;
import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

@SpringBootTest
@DirtiesContext (classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@RunWith(SpringRunner.class)
public class KalahaGameServiceTests {

    @MockBean
    private KalahaGameRepository kalahaGameRepository;

    @Autowired
    private KalahaGameService kalahaGameService;

    @Test
    public void testCreatingNewlyGameInstance () throws Exception {
        KalahaGame gameInstance = kalahaGameService.createGame(6);

        BDDAssertions.then(gameInstance.getPlayerTurn()).isNull();
        BDDAssertions.then (gameInstance.getPits()).size().isEqualTo(14);
        BDDAssertions.then (gameInstance.getPit(1).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(2).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(3).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(4).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(5).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(6).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(7).getStones()).isEqualTo(0);
        BDDAssertions.then (gameInstance.getPit(8).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(9).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(10).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(11).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(12).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(13).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(14).getStones()).isEqualTo(0);
    }

    @Test
    public void testLoadingGameInstanceFromRepository () throws Exception {

        KalahaGame expectedGame = new KalahaGame("5d414dcd24e4990006e7c9d3" , 6);
        List<KalahaPit> kalahaPits = Arrays.asList(
                new KalahaPit(1 , 6),
                new KalahaPit(2 , 6),
                new KalahaPit(3 , 6),
                new KalahaPit(4 , 6),
                new KalahaPit(5 , 6),
                new KalahaPit(6 , 6),
                new KalahaPit(7 , 0),
                new KalahaPit(8 , 6),
                new KalahaPit(9 , 6),
                new KalahaPit(10 , 6),
                new KalahaPit(11 , 6),
                new KalahaPit(12 , 6),
                new KalahaPit(13 , 6),
                new KalahaPit(14 , 0));
        expectedGame.setPits(kalahaPits);
        Optional<KalahaGame> gameOptional= Optional.of(expectedGame);
        Mockito.when(kalahaGameRepository.findById("5d414dcd24e4990006e7c9d3")).thenReturn(gameOptional);

        KalahaGame gameInstance = kalahaGameService.loadGame("5d414dcd24e4990006e7c9d3");

        BDDAssertions.then(gameInstance.getId()).isEqualTo("5d414dcd24e4990006e7c9d3");
        BDDAssertions.then(gameInstance.getPlayerTurn()).isNull();
        BDDAssertions.then (gameInstance.getPits()).size().isEqualTo(14);
        BDDAssertions.then (gameInstance.getPit(1).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(2).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(3).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(4).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(5).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(6).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(7).getStones()).isEqualTo(0);
        BDDAssertions.then (gameInstance.getPit(8).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(9).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(10).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(11).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(12).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(13).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(14).getStones()).isEqualTo(0);
    }

    @Test
    public void testUpdatingGameInstanceIntoRepository () throws Exception {

        KalahaGame expectedGame = new KalahaGame("5d414dcd24e4990006e7c9d3" , 6);
        List<KalahaPit> kalahaPits = Arrays.asList(
                new KalahaPit(1 , 6),
                new KalahaPit(2 , 6),
                new KalahaPit(3 , 6),
                new KalahaPit(4 , 6),
                new KalahaPit(5 , 6),
                new KalahaPit(6 , 6),
                new KalahaPit(7 , 0),
                new KalahaPit(8 , 6),
                new KalahaPit(9 , 6),
                new KalahaPit(10 , 6),
                new KalahaPit(11 , 6),
                new KalahaPit(12 , 6),
                new KalahaPit(13 , 6),
                new KalahaPit(14 , 0));
        expectedGame.setPits(kalahaPits);
        Mockito.when(kalahaGameRepository.save(expectedGame)).thenReturn(expectedGame);

        KalahaGame gameInstance = kalahaGameService.updateGame(expectedGame);

        BDDAssertions.then(gameInstance.getId()).isEqualTo("5d414dcd24e4990006e7c9d3");
        BDDAssertions.then(gameInstance.getPlayerTurn()).isNull();
        BDDAssertions.then (gameInstance.getPits()).size().isEqualTo(14);
        BDDAssertions.then (gameInstance.getPit(1).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(2).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(3).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(4).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(5).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(6).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(7).getStones()).isEqualTo(0);
        BDDAssertions.then (gameInstance.getPit(8).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(9).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(10).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(11).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(12).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(13).getStones()).isEqualTo(6);
        BDDAssertions.then (gameInstance.getPit(14).getStones()).isEqualTo(0);
    }

}

Тесты MancalaController

Spring Boot предоставляет простой и понятный способ тестирования классов вашего контроллера. В рамках проекта весенней загрузки вы можете использовать эту функцию, добавив аннотацию @AutoConfigureMockMvc  в свой класс Test, а затем используйте аннотацию @Autowired для подключения объекта MockMvc к вашему классу Test, как показано  ниже, и начните писать тесты для операций вашего контроллера.

Как вы можете видеть из приведенного ниже кода, я использовал аннотацию @MockBea n, чтобы смоделировать класс MancalaGameService, который используется внутри нашего класса контроллера для обеспечения доступа к нашему механизму хранения данных для экземпляров KalahaGame. Опять же, ниже приведено лишь несколько примеров тестов, которые можно написать для тестирования класса MancalaController,  и вы определенно можете добавить еще много тестов в этот класс, чтобы убедиться, что все различные сценарии были тщательно рассмотрены:


@AutoConfigureMockMvc
@SpringBootTest (webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "server.port=0")
@RunWith(SpringRunner.class)
@DirtiesContext
public class KalahaGameControllerTests {

    @MockBean
    KalahaGameService mancalaGameService;

    private final Resource jsonOfJustCreatedKalahaGame = new ClassPathResource("mancala-creation.json");
    private final Resource jsonOfKalahaGameSowPit2JustAfterCreation = new ClassPathResource("mancala-sow-2.json");

    @Autowired
    private MockMvc mockMvc;

    @SneakyThrows
    private String asJson(Resource resource) {
        return StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset());
    }

    @Test
    public void testGameCreation() throws Exception {

        KalahaGame expectedGame = new KalahaGame("5d414dcd24e4990006e7c900", 6);

        Mockito.when(this.mancalaGameService.createGame(6))
                .thenReturn(expectedGame);

        this.mockMvc.perform(post("/games"))
                .andExpect(status().is2xxSuccessful())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
                .andExpect(content().json(asJson(jsonOfJustCreatedKalahaGame), false))
                .andReturn();
    }

    @Test
    public void testSowPitIndex2() throws Exception {

        KalahaGame expectedGame = new KalahaGame("5d414dcd24e4990006e7c900", 6);

        Mockito.when(mancalaGameService.loadGame("5d414dcd24e4990006e7c900"))
                .thenReturn(expectedGame);

        this.mockMvc.perform(put("/games/5d414dcd24e4990006e7c900/pits/2"))
                .andExpect(status().is2xxSuccessful())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
                .andExpect(content().json(asJson(jsonOfKalahaGameSowPit2JustAfterCreation)))
                .andReturn();
    }

    @Test
    public void testSowingTheGameOfInvalidId() throws Exception {

        Mockito.when(mancalaGameService.loadGame("5d414dcd24e4990006e7c902"))
                .thenThrow(new ResourceNotFoundException("Game id 5d414dcd24e4990006e7c902 not found!"));

        this.mockMvc.perform(put("/games/5d414dcd24e4990006e7c902/pits/2"))
                .andExpect(status().is4xxClientError())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
                .andExpect(jsonPath("@.message").value("Game id 5d414dcd24e4990006e7c902 not found!"))
                .andReturn();
    }

    @Test
    public void testSowingTheGameAtInvalidPitIndex() throws Exception {
        this.mockMvc.perform(put("/games/5d414dcd24e4990006e7c900/pits/7"))
                .andExpect(status().is5xxServerError())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
                .andExpect(jsonPath("@.message").value("Invalid pit Index!. It should be between 1..6 or 8..13"))
                .andReturn();
    }
}

Теперь нам нужно создать несколько интеграционных тестов для наших проектов реализации игр Mancala, чтобы убедиться, что два микросервиса правильно взаимодействуют через API, разработанные в микросервисе Mancala-api .

Интеграционные тесты

Этот класс выполняет различные интеграционные тесты между микросервисамиmancala-web ‘ и ‘ mancala-api ‘, используя Wiremock . «WireMock — это симулятор для API-интерфейсов на основе HTTP. Некоторые могут считать его инструментом виртуализации сервисов или фиктивным сервером. Он позволяет вам оставаться продуктивным, когда API, от которого вы зависите, не существует или не является полным».

Spring предоставляет простой способ интеграции Wiremock в наш проект с помощью приведенной ниже зависимости от файла pom.cml для проекта микросервиса mancala-web ‘:


<!-- WireMock tests -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-contract-wiremock</artifactId>
  <scope>test</scope>
</dependency>

и затем добавляем аннотацию @AutoConfigureWireMock в наш тестовый класс. 

@SpringBootTest (classes = MancalaWebApplication.class)
@RunWith(SpringRunner.class)
@AutoConfigureWireMock (port = 8080)
@DirtiesContext
public class MancalaIntegrationTests {

Как вы можете видеть в приведенном выше фрагменте кода, мы настроили Wiremock для работы на порту 8080, что означает, что мы моделируем нашу службу ‘mancala-api’ на имитируемом сервере, работающем на порту 8080. Теперь давайте обсудим, как написать интеграционный тест. используя Wiremock:

В микросервисе mancala-web ‘ мы использовали балансировку нагрузки клиента ленты  для взаимодействия с микросервисом ‘mancala-api’, и поэтому мы создали класс MancalaClientConfig, как показано ниже, для предоставления динамического URL-адреса для службы REST ‘mancala-api’ на основе доступности случаев в реестре услуг Консул :

package com.dzone.mancala.web.client;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Component;

@Component
public class MancalaClientConfig {

    private LoadBalancerClient loadBalancer;

    @Value("${mancala.api.service.id}")
    private final String apiServiceId = "mancala-api";

    @Autowired
    public void setLoadBalancer(LoadBalancerClient loadBalancer) {
        this.loadBalancer = loadBalancer;
    }

    public String getNewMancalaGameUrl(){
        ServiceInstance instance = this.loadBalancer.choose(apiServiceId);

        String url = String.format("http://%s:%s/games", instance.getHost(), instance.getPort());

        return url;
    }

    public String getSowMancalaGameUrl(String gameId, Integer pitIndex){
        ServiceInstance instance = this.loadBalancer.choose(apiServiceId);

        String url = String.format("http://%s:%s/games/%s/pits/%s", instance.getHost(), instance.getPort(), gameId, pitIndex);

        return url;
    }
}

 а затем мы создали класс MancalaClient,  чтобы использовать вышеуказанный класс для получения URL-адреса службы и вызова службы ‘mancala-api’ с помощью класса Spring RestTemplate.

package com.dzone.mancala.web.client;

import com.dzone.mancala.web.model.KalahaGame;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

/*
    This class performs the api invocation for the web client
 */
@Component
@Slf4j
public class MancalaClient {

    private RestTemplate restTemplate;

    @Autowired
    private MancalaClientConfig mancalaClientConfig;

    public MancalaClient(@Autowired RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public KalahaGame startNewMancalaGame() throws Exception {

        String url = mancalaClientConfig.getNewMancalaGameUrl();

        log.info("calling:" + url);

        ResponseEntity<KalahaGame> gameResponse = this.restTemplate.postForEntity(url, null, KalahaGame.class);

        log.info("response: " + new ObjectMapper().writerWithDefaultPrettyPrinter().
                writeValueAsString(gameResponse.getBody()));

        return gameResponse.getBody();
    }

    public KalahaGame sowMancalaGame(String gameId, Integer pitIndex) throws Exception {

        String url = mancalaClientConfig.getSowMancalaGameUrl(gameId, pitIndex);

        log.info("calling: " + url);

        ResponseEntity<KalahaGame> response = restTemplate.exchange(url, HttpMethod.PUT, null, KalahaGame.class);

        log.info("response: " + new ObjectMapper().writerWithDefaultPrettyPrinter().
                writeValueAsString(response.getBody()));

        return response.getBody();
    }
}

Поэтому для создания интеграционных тестов нам нужно будет смоделировать класс MancalaClientConfig с помощью аннотации @MockBean:

@MockBeanMancalaClientConfig mancalaClientConfig;

Мы также добавили два образца JSON-файла только для того, чтобы написать ниже тесты в папке ресурсов  в пакете test’  и использовать приведенный ниже фрагмент кода для доступа к ним:

  private final Resource mancalaCreate = new ClassPathResource("mancala-creation.json");

    private final Resource mancalaSow2 = new ClassPathResource("mancala-sow-2.json");

    @SneakyThrows
    private String asJson(Resource resource) {
        return StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset());
    }

и вот последний класс MancalaIntegrationTests . Обратите внимание на приведенный ниже фрагмент кода:

Mockito.when(mancalaClientConfig.getNewMancalaGameUrl()).
  thenReturn("http://localhost:8080/games");

который возвращает URL-адрес API на основе конфигурации Wiremock, которую мы сделали для порта 8080. Ниже фрагмент кода обучает симулятор службы Wiremock возвращать ожидаемый результат при вызове определенного URL-адреса с заданными параметрами:

WireMock.stubFor(WireMock.post("/games")
                 .willReturn(WireMock.aResponse()
                             .withHeader(HttpHeaders.CONTENT_TYPE, 
                                         MediaType.APPLICATION_JSON_UTF8_VALUE)
                             .withStatus(HttpStatus.OK.value())
                             .withBody(asJson(mancalaCreate))
                            ));

Теперь, когда мы вызываем API службы через MancalaClient , мы ожидаем получить аналогичный ответ, и поэтому теперь мы можем разместить необходимую диссертацию с использованием класса BDDAssertions: 

/*
    Using Spring Cloud Contract Wiremock for inter-service communication testing between Mancala microservices
 */

@SpringBootTest (classes = MancalaWebApplication.class)
@RunWith(SpringRunner.class)
@AutoConfigureWireMock (port = 8080)
@DirtiesContext
public class MancalaIntegrationTests {

    private final Resource mancalaCreate = new ClassPathResource("mancala-creation.json");

    private final Resource mancalaSow2 = new ClassPathResource("mancala-sow-2.json");

    @MockBean
    MancalaClientConfig mancalaClientConfig;

    @Autowired
    private MancalaClient mancalaClient;

    @SneakyThrows
    private String asJson(Resource resource) {
        return StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset());
    }

    @Test
    public void testManacalaCreation () throws Exception{

        Mockito.when(mancalaClientConfig.getNewMancalaGameUrl()).thenReturn("http://localhost:8080/games");

        WireMock.stubFor(WireMock.post("/games")
                .willReturn(WireMock.aResponse()
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
                        .withStatus(HttpStatus.OK.value())
                        .withBody(asJson(mancalaCreate))
                ));

        KalahaGame kalahaGame = mancalaClient.startNewMancalaGame();

        List<KalahaPit> kalahaPits = Arrays.asList(
                new KalahaPit(1 , 6),
                new KalahaPit(2 , 6),
                new KalahaPit(3 , 6),
                new KalahaPit(4 , 6),
                new KalahaPit(5 , 6),
                new KalahaPit(6 , 6),
                new KalahaPit(7 , 0),
                new KalahaPit(8 , 6),
                new KalahaPit(9 , 6),
                new KalahaPit(10 , 6),
                new KalahaPit(11 , 6),
                new KalahaPit(12 , 6),
                new KalahaPit(13 , 6),
                new KalahaPit(14 , 0));

        BDDAssertions.then(kalahaGame.getPlayerTurn()).isNull();
        BDDAssertions.then(kalahaGame.getPits()).isEqualTo(kalahaPits);
        BDDAssertions.then(kalahaGame.getLeftHouseStones()).isEqualTo(0);
        BDDAssertions.then(kalahaGame.getRightHouseStones()).isEqualTo(0);
    }
}

Мы можем сделать то же самое для тестирования работы посева нашего сервиса ‘mancala-api’. Как видно из приведенного ниже кода, чтобы протестировать операцию посева, нам нужно сначала повторить тест создания игры Mancala, а затем выполнить посев этого экземпляра игры:

/*
        We first need to run the Mancala Game creation test and use the game id generated to sow the game
     */
    @Test
    public void testManacalaSowPitIndex2 () throws Exception {

        // 1. Run the Mancala Creation Test
        Mockito.when(mancalaClientConfig.getNewMancalaGameUrl()).thenReturn("http://localhost:8080/games");

        WireMock.stubFor(WireMock.post("/games")
                .willReturn(WireMock.aResponse()
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
                        .withStatus(HttpStatus.OK.value())
                        .withBody(asJson(mancalaCreate))
                ));

        KalahaGame kalahaGame = mancalaClient.startNewMancalaGame();

        List<KalahaPit> kalahaPits = Arrays.asList(
                new KalahaPit(1 , 6),
                new KalahaPit(2 , 6),
                new KalahaPit(3 , 6),
                new KalahaPit(4 , 6),
                new KalahaPit(5 , 6),
                new KalahaPit(6 , 6),
                new KalahaPit(7 , 0),
                new KalahaPit(8 , 6),
                new KalahaPit(9 , 6),
                new KalahaPit(10 , 6),
                new KalahaPit(11 , 6),
                new KalahaPit(12 , 6),
                new KalahaPit(13 , 6),
                new KalahaPit(14 , 0));

        BDDAssertions.then(kalahaGame.getPlayerTurn()).isNull();
        BDDAssertions.then(kalahaGame.getPits()).isEqualTo(kalahaPits);
        BDDAssertions.then(kalahaGame.getLeftHouseStones()).isEqualTo(0);
        BDDAssertions.then(kalahaGame.getRightHouseStones()).isEqualTo(0);

        // 2. Run the Mancala Sow test for pit 2

        Mockito.when(mancalaClientConfig.getSowMancalaGameUrl(kalahaGame.getId(), 2)).
                thenReturn("http://localhost:8080/games/"+kalahaGame.getId()+"/pits/2");

        WireMock.stubFor(WireMock.put("/games/"+kalahaGame.getId()+"/pits/2")
                .willReturn(WireMock.aResponse()
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
                        .withStatus(HttpStatus.OK.value())
                        .withBody(asJson(mancalaSow2))
                ));

        KalahaGame kalahaGameAfterSowingPit2 = mancalaClient.sowMancalaGame(kalahaGame.getId(), 2);

        System.out.println(kalahaGameAfterSowingPit2);

        List<KalahaPit> newKalahaPits = Arrays.asList(
                new KalahaPit(1 , 6),
                new KalahaPit(2 , 0),
                new KalahaPit(3 , 7),
                new KalahaPit(4 , 7),
                new KalahaPit(5 , 7),
                new KalahaPit(6 , 7),
                new KalahaPit(7 , 1),
                new KalahaPit(8 , 7),
                new KalahaPit(9 , 6),
                new KalahaPit(10 , 6),
                new KalahaPit(11 , 6),
                new KalahaPit(12 , 6),
                new KalahaPit(13 , 6),
                new KalahaPit(14 , 0));

        BDDAssertions.then(kalahaGameAfterSowingPit2.getPlayerTurn()).isEqualTo("PlayerB");
        BDDAssertions.then(kalahaGameAfterSowingPit2.getPits()).isEqualTo(newKalahaPits);
        BDDAssertions.then(kalahaGameAfterSowingPit2.getLeftHouseStones()).isEqualTo(0);
        BDDAssertions.then(kalahaGameAfterSowingPit2.getRightHouseStones()).isEqualTo(1);
    }

Вышеупомянутые два теста были разработаны только для целей этой статьи, но вы можете добавить несколько тестов, чтобы убедиться, что микросервис клиента всегда будет получать правильный ответ от сервера, если реализация API не изменяется со временем. Тем не менее, мы все знаем, что эти изменения в реализации API в конце концов неизбежны, и в результате мы получим неожиданное поведение от клиентов только в рабочее время, что в конечном итоге приведет к неудовлетворенности клиентов и плохому пользовательскому опыту для наших пользователей Игры. Вот причина, почему это происходит:

Микросервисmancala-api ‘ разрабатывается и поддерживается отдельной командой, а не микросервисом mancala-web ‘ в соответствии с философией микросервиса, поэтому совершенно очевидно, что команда, ответственная за микросервис ‘mancala-api’, внесет много изменений в API внедрение без уведомления команд заказов, использующих этот микросервис, и поэтому, если мы не опубликуем клиентов в производстве, мы не заметим эти изменения.

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

Испытания по контракту с потребителем (CDC)

Когда мы разрабатываем сервисы, основанные на микросервисном подходе, нам необходимо убедиться, что клиентские сервисы (инициатор сервисов) всегда синхронизированы с сервисами провайдера. Это происходит через договор, заключенный между поставщиком услуг и потребителями этой услуги.

Контракт, который обычно разрабатывается с использованием скриптов groovy, определяет, что клиенты могут ожидать при вызове определенного API с заданными параметрами, и эти ответы гарантируются этим поставщиком услуг. Spring Cloud Contract предоставляет API для разработки тестов на основе CDC для наших микросервисов и поддерживает DSL для следующих языков:

  • Groovy.
  • YAML.
  • Джава.
  • Котлин.

Чтобы узнать подробности о том, как писать свои контракты DSL, используя ваш любимый язык, вы можете обратиться к загрузочной документации Spring здесь . Я использовал groovy для создания моих контрактных DSL.

Первый шаг

Во-первых, нам нужно создать наши скрипты groovy и поместить их в папку ресурсов / контрактов  проекта ‘mancala-api’. Вот два примера нашей реализации mancala-api:

Пример первый

Groovy-скрипт для операции создания MancalaGame с именем shouldReturnNewlyCreatedMancalaGame.groovy

import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.HttpMethods
import org.springframework.http.MediaType

Contract.make {

    description "should return a newly created Mancala Game "
    request {
        url("/games")
        method(HttpMethods.HttpMethod.POST)
    }
    response {
        status(200)
        body(
                ["id"        : "5d414dcd24e4990006e7c900",
                 "pits"      : [
                         [
                                 "id"    : 1,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 2,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 3,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 4,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 5,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 6,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 7,
                                 "stones": 0
                         ],
                         [
                                 "id"    : 8,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 9,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 10,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 11,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 12,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 13,
                                 "stones": 6
                         ],
                         [
                                 "id"    : 14,
                                 "stones": 0
                         ]
                 ]
                 ,
                 "playerTurn": null
                ]

        )
        headers {
            contentType(MediaType.APPLICATION_JSON_VALUE)
        }
    }
}

Пример второй

Groovy-скрипт для посева игры Mancala для пит-индекса 2, например, с названием shouldReturnMancalaGameSowingPit.groovy.

import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.HttpMethods
import org.springframework.http.MediaType

Contract.make {

    description "should return a Mancala Game after Sowing of index 2"
    request {
        url("/games/5d414dcd24e4990006e7c900/pits/2")
        method(HttpMethods.HttpMethod.PUT)
    }
    response {
        status(200)
        body(
                "id": "5d414dcd24e4990006e7c900" ,
                "pits": [
                        [
                                "id": 1,
                                "stones": 6
                        ],
                        [
                                "id": 2,
                                "stones": 0
                        ],
                        [
                                "id": 3,
                                "stones": 7
                        ],
                        [
                                "id": 4,
                                "stones": 7
                        ],
                        [
                                "id": 5,
                                "stones": 7
                        ],
                        [
                                "id": 6,
                                "stones": 7
                        ],
                        [
                                "id": 7,
                                "stones": 1
                        ],
                        [
                                "id": 8,
                                "stones": 7
                        ],
                        [
                                "id": 9,
                                "stones": 6
                        ],
                        [
                                "id": 10,
                                "stones": 6
                        ],
                        [
                                "id": 11,
                                "stones": 6
                        ],
                        [
                                "id": 12,
                                "stones": 6
                        ],
                        [
                                "id": 13,
                                "stones": 6
                        ],
                        [
                                "id": 14,
                                "stones": 0
                        ]
                    ]
                ,
                "playerTurn": "PlayerB"
        )
        headers {
            contentType(MediaType.APPLICATION_JSON_VALUE)
        }
    }
}

Шаг второй

Затем нам нужно добавить плагин ниже, предоставляемый Spring boot, в наш микросервис API-провайдера ‘mancala-api’:

<!-- Spring Cloud Contract Plugin-->
<plugin>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-contract-maven-plugin</artifactId>
  <extensions>true</extensions>
  <version>2.1.2.RELEASE</version>
  <configuration>
    <baseClassForTests>
      com.dzone.mancala.game.cdc.BaseClass
    </baseClassForTests>
    <testMode>EXPLICIT</testMode>
  </configuration>
</plugin>

Вышеприведенный плагин запускает наши DSL-контракты, размещенные в папке / resources / contract, и автоматически генерирует для них необходимые тестовые классы интеграции и будет выполнять их в соответствии с фактической реализацией этих API каждый раз, когда микросервис mancala-api упакован, и в результате, он генерирует заглушки для тех договоров , которые будут использоваться клиентами этого microservice ( «Манкала-веб») , а затем упаковать и разместить его в локальном хранилище Maven по умолчанию под идентификатор группы  и артефакта ID , как определено в pom.xml из ‘ Манкала-апи проект.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.dzone.mancalagame</groupId>
<artifactId>mancala-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mancala-api</name>
<description>Spring project for building Mancala Game Api</description>

Он выполняет эту работу с помощью библиотеки RestAssured  . Поэтому следующим шагом является настройка RestAssured для нашего класса MancalaController по заданному пути, определенному в конфигурации плагина: com.dzone.mancala.game.cdc.BaseClass

Шаг третий

Чтобы использовать библиотеку Spring Cloud Contract, нам нужно добавить нижеприведенную зависимость к нашему файлу pom.xml проекта ‘mancala-api’:


<!-- Spring Cloud Contract-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-contract-verifier</artifactId>
  <scope>test</scope>
</dependency>

Чтобы настроить RestAssured с использованием динамического порта, нам нужно использовать аннотацию @LocalServerPort,  чтобы получить работающий порт и назначить его библиотеке RestAssure, указав полный URL-адрес или назначив данный порт непосредственно в нашем методе установки, аннотированном @Before :

package com.dzone.mancala.game.cdc;

import com.dzone.mancala.game.controller.MancalaController;
import com.dzone.mancala.game.model.KalahaGame;
import com.dzone.mancala.game.model.KalahaPit;
import com.dzone.mancala.game.model.PlayerTurns;
import com.dzone.mancala.game.service.KalahaGameService;
import com.dzone.mancala.game.service.MancalaSowingService;
import io.restassured.RestAssured;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.List;


/*
    This is used for Spring Contact Testing.  It prepares the Stub dependencies
 */

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "server.port=0")
@RunWith(SpringRunner.class)
@Import({MancalaController.class, KalahaGameService.class})
@DirtiesContext
public class BaseClass {

    @LocalServerPort
    private String port;

    @MockBean
    private KalahaGameService mancalaGameService;

    @MockBean
    private MancalaSowingService mancalaSowingService;

    @Autowired
    private MancalaController mancalaController;

    @Before
    public void setupGame() throws Exception {
        RestAssured.baseURI = "http://localhost:" + this.port;

        KalahaGame expectedGame = new KalahaGame("5d414dcd24e4990006e7c900", 6);

        Mockito.when(this.mancalaGameService.createGame(6))
                .thenReturn(expectedGame);

        Mockito.when(mancalaGameService.loadGame("5d414dcd24e4990006e7c900"))
                .thenReturn(expectedGame);

        List<KalahaPit> pitsAfterSowingIndex2 = Arrays.asList(
                new KalahaPit(1, 6),
                new KalahaPit(2, 0),
                new KalahaPit(3, 7),
                new KalahaPit(4, 7),
                new KalahaPit(5, 7),
                new KalahaPit(6, 7),
                new KalahaPit(7, 1),
                new KalahaPit(8, 7),
                new KalahaPit(9, 6),
                new KalahaPit(10, 6),
                new KalahaPit(11, 6),
                new KalahaPit(12, 6),
                new KalahaPit(13, 6),
                new KalahaPit(14, 0));

        KalahaGame gameAfterSowingIndex2 = new KalahaGame("5d414dcd24e4990006e7c900", 6);
        gameAfterSowingIndex2.setPits(pitsAfterSowingIndex2);
        gameAfterSowingIndex2.setPlayerTurn(PlayerTurns.PlayerB);

        Mockito.when(this.mancalaSowingService.sow(expectedGame, 2))
                .thenReturn(gameAfterSowingIndex2);

        RestAssuredMockMvc.standaloneSetup(mancalaController);
    }
}

Как вы можете видеть в приведенном выше коде, чтобы Мок весь MancalaController класс , используя RestAssuredMockMvc.standaloneSetup ()  метод, мы должны использовать @MockBean для MancalaGameService, а также классы MancalaSowingService используются с MancalaController и использовать Mockito.when () метод для предоставить ожидаемый ответ при вызове соответствующих методов в этих классах.

Шаг четвертый

Теперь мы запускаем команду mvc clean install ‘, чтобы установить плагин и запустить скрипты groovy:

[.\mancala-game\mancala-microservice] ./mvnw clean install

Это сгенерирует классы-заглушки и поместит их в ваш локальный репозиторий maven:

[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ mancala-microservice ---
[INFO] Installing D:\mancala-game\mancala-microservice\pom.xml to C:\Users\[Your-User]\.m2\repository\com\dzone\mancalagame\mancala-microservice\0.0.1-SNAPSHOT\mancala-microservice-0.0.1-SNAPSHOT.pom

Затем нам нужно использовать сгенерированную заглушку в нашем клиентском микросервисе «mancala-web» и проверить связь между вызовами REST между этими двумя микросервисами с помощью библиотеки Spring Runner  .

Шаг пятый

Завершающим этапом нашего теста Consumer Driver Contract (CDC) является создание класса CDCApplicationTest в нашем микросервисе клиента ‘mancala-web’ и настройка библиотеки Spring Cloud Contract Stub Runner с помощью аннотации @AutoConfigureStubRunner  и использование ранее созданной заглушки для сервис ‘mancala-api’ (который по умолчанию хранится в вашем локальном репозитории maven) для его проверки. Чтобы использовать эту библиотеку, нам нужно добавить следующую зависимость в наш файл pom.xml:


<!-- Spring Stub Runner for CDC tests -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
  <scope>test</scope>
</dependency>

Чтобы определить местонахождение пакета-заглушки, нам нужно указать идентификатор группы и идентификатор артефакта, ранее использовавшиеся для его создания, которые в нашем проекте будут такими, как показано ниже:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.dzone.mancalagame</groupId>
<artifactId>mancala-api</artifactId>

Поэтому окончательная конфигурация для аннотации @AutoConfigureStubRunner будет такой, как показано  ниже:

@AutoConfigureStubRunner (
        ids = "com.dzone.mancalagame:mancala-api:+:8080",
        stubsMode = StubRunnerProperties.StubsMode.LOCAL)

Это очень важно указать правильный идентификатор группы  и артефакта идентификатор иначе библиотека Заглушка Runner не будет найден окурок , и вы получите ошибку во время выполнения CDCTest.

Вот окончательная реализация класса CDCApplicationTests для нашего сервиса Mancala  -api:


@AutoConfigureStubRunner (
        ids = "com.dzone.mancalagame:mancala-api:+:8080",
        stubsMode = StubRunnerProperties.StubsMode.LOCAL)
@AutoConfigureJson
@SpringBootTest (classes = MancalaWebApplication.class)
@RunWith(SpringRunner.class)
@DirtiesContext
public class CDCApplicationTests {

    @Autowired
    private MancalaClient mancalaClient;

    @Test
    public void testManacalaCreation () throws Exception {

        KalahaGame kalahaGame = mancalaClient.startNewMancalaGame();

        List<KalahaPit> kalahaPits = Arrays.asList(
                new KalahaPit(1 , 6),
                new KalahaPit(2 , 6),
                new KalahaPit(3 , 6),
                new KalahaPit(4 , 6),
                new KalahaPit(5 , 6),
                new KalahaPit(6 , 6),
                new KalahaPit(7 , 0),
                new KalahaPit(8 , 6),
                new KalahaPit(9 , 6),
                new KalahaPit(10 , 6),
                new KalahaPit(11 , 6),
                new KalahaPit(12 , 6),
                new KalahaPit(13 , 6),
                new KalahaPit(14 , 0));

        BDDAssertions.then(kalahaGame.getPlayerTurn()).isNull();
        BDDAssertions.then(kalahaGame.getPits()).isEqualTo(kalahaPits);
        BDDAssertions.then(kalahaGame.getLeftHouseStones()).isEqualTo(0);
        BDDAssertions.then(kalahaGame.getRightHouseStones()).isEqualTo(0);
    }

    /*
      We first need to run the Mancala Game creation test and use the game id generated to sow the game
   */
    @Test
    public void testManacalaSowingPitIndex2 () throws Exception {

        KalahaGame kalahaGame = mancalaClient.startNewMancalaGame();

        List<KalahaPit> kalahaPits = Arrays.asList(
                new KalahaPit(1 , 6),
                new KalahaPit(2 , 6),
                new KalahaPit(3 , 6),
                new KalahaPit(4 , 6),
                new KalahaPit(5 , 6),
                new KalahaPit(6 , 6),
                new KalahaPit(7 , 0),
                new KalahaPit(8 , 6),
                new KalahaPit(9 , 6),
                new KalahaPit(10 , 6),
                new KalahaPit(11 , 6),
                new KalahaPit(12 , 6),
                new KalahaPit(13 , 6),
                new KalahaPit(14 , 0));

        BDDAssertions.then(kalahaGame.getPlayerTurn()).isNull();
        BDDAssertions.then(kalahaGame.getPits()).isEqualTo(kalahaPits);
        BDDAssertions.then(kalahaGame.getLeftHouseStones()).isEqualTo(0);
        BDDAssertions.then(kalahaGame.getRightHouseStones()).isEqualTo(0);

        // 2. Run the Mancala Sow test for pit 2

        KalahaGame kalahaGameAfterSowingPit2 = mancalaClient.sowMancalaGame(kalahaGame.getId(), 2);

        System.out.println(kalahaGameAfterSowingPit2);

        List<KalahaPit> newKalahaPits = Arrays.asList(
                new KalahaPit(1 , 6),
                new KalahaPit(2 , 0),
                new KalahaPit(3 , 7),
                new KalahaPit(4 , 7),
                new KalahaPit(5 , 7),
                new KalahaPit(6 , 7),
                new KalahaPit(7 , 1),
                new KalahaPit(8 , 7),
                new KalahaPit(9 , 6),
                new KalahaPit(10 , 6),
                new KalahaPit(11 , 6),
                new KalahaPit(12 , 6),
                new KalahaPit(13 , 6),
                new KalahaPit(14 , 0));

        BDDAssertions.then(kalahaGameAfterSowingPit2.getPlayerTurn()).isEqualTo("PlayerB");
        BDDAssertions.then(kalahaGameAfterSowingPit2.getPits()).isEqualTo(newKalahaPits);
        BDDAssertions.then(kalahaGameAfterSowingPit2.getLeftHouseStones()).isEqualTo(0);
        BDDAssertions.then(kalahaGameAfterSowingPit2.getRightHouseStones()).isEqualTo(1);
    }
}

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

Теперь мы можем запустить команду mvn clean install для проверки наших тестов CDC:


D:\mancala-game\mancala-microservice>mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] mancala-api                                                        [jar]
[INFO] mancala-web                                                        [jar]
[INFO] mancala-microservice                                               [pom]
[INFO]
[INFO] -----------------< com.dzone.mancalagame:mancala-api >------------------
[INFO] Building mancala-api 0.0.1-SNAPSHOT                                [1/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ mancala-api ---
[INFO] Deleting D:\mancala-game\mancala-microservice\mancala-api\target
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ mancala-api ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ mancala-api ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 17 source files to D:\mancala-game\mancala-microservice\mancala-api\target\classes
[INFO]
[INFO] --- spring-cloud-contract-maven-plugin:2.1.2.RELEASE:generateTests (default-generateTests) @ mancala-api ---
[INFO] Generating server tests source code for Spring Cloud Contract Verifier contract verification
[INFO] Will use contracts provided in the folder [D:\mancala-game\mancala-microservice\mancala-api\src\test\resources\contracts]
[INFO] Directory with contract is present at [D:\mancala-game\mancala-microservice\mancala-api\src\test\resources\contracts]
[INFO] Test Source directory: D:\mancala-game\mancala-microservice\mancala-api\target\generated-test-sources\contracts added.
[INFO] Using [com.dzone.mancala.game.cdc.BaseClass] as base class for test classes, [null] as base package for tests, [null] as package with base classes, base class mappings []
[INFO] Creating new class file [D:\mancala-game\mancala-microservice\mancala-api\target\generated-test-sources\contracts\com\dzone\mancala\game\cdc\ContractVerifierTest.java]
[INFO] Generated 1 test classes.
[INFO]
[INFO] --- spring-cloud-contract-maven-plugin:2.1.2.RELEASE:convert (default-convert) @ mancala-api ---
[INFO] Will use contracts provided in the folder [D:\mancala-game\mancala-microservice\mancala-api\src\test\resources\contracts]
[INFO] Copying Spring Cloud Contract Verifier contracts to [D:\mancala-game\mancala-microservice\mancala-api\target\stubs\META-INF\com.dzone.mancalagame\mancala-api\0.0.1-SNAPSHOT\contracts]. Only files matching [.*] pattern will end up in the final JAR with stubs.
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] Converting from Spring Cloud Contract Verifier contracts to WireMock stubs mappings
[INFO]      Spring Cloud Contract Verifier contracts directory: D:\mancala-game\mancala-microservice\mancala-api\src\test\resources\contracts
[INFO] Stub Server stubs mappings directory: D:\mancala-game\mancala-microservice\mancala-api\target\stubs\META-INF\com.dzone.mancalagame\mancala-api\0.0.1-SNAPSHOT\mappings
[INFO] Creating new stub [D:\mancala-game\mancala-microservice\mancala-api\target\stubs\META-INF\com.dzone.mancalagame\mancala-api\0.0.1-SNAPSHOT\mappings\shouldReturnMancalaGameSowingPit.json]
[INFO] Creating new stub [D:\mancala-game\mancala-microservice\mancala-api\target\stubs\META-INF\com.dzone.mancalagame\mancala-api\0.0.1-SNAPSHOT\mappings\shouldReturnNewlyCreatedMancalaGame.json]
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ mancala-api ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 6 resources
[INFO] skip non existing resourceDirectory D:\mancala-game\mancala-microservice\mancala-api\target\generated-test-resources\contracts
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ mancala-api ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 7 source files to D:\mancala-game\mancala-microservice\mancala-api\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ mancala-api ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.dzone.mancala.game.cdc.ContractVerifierTest
...
...
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 30.001 s - in com.dzone.mancala.game.cdc.ContractVerifierTest
[INFO] Running com.dzone.mancala.game.controller.KalahaGameControllerTests
...
...
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 19.346 s - in com.dzone.mancala.game.controller.KalahaGameControllerTests
[INFO] Running com.dzone.mancala.game.repository.KalahaGameRepositoryTests
...
...
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.849 s - in com.dzone.mancala.game.repository.KalahaGameRepositoryTests
[INFO] Running com.dzone.mancala.game.service.KalahaGameServiceTests
....
....
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 16.977 s - in com.dzone.mancala.game.service.KalahaGameServiceTests
[INFO] Running com.dzone.mancala.game.service.MancalaSowingServiceTests
[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s - in com.dzone.mancala.game.service.MancalaSowingServiceTests
2019-10-12 13:19:12.378  INFO [mancala-api,,,] 12776 --- [      Thread-21] c.n.l.PollingServerListUpdater           : Shutting down the Executor Pool for PollingServerListUpdater
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 19, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- spring-cloud-contract-maven-plugin:2.1.2.RELEASE:generateStubs (default-generateStubs) @ mancala-api ---
[INFO] Files matching this pattern will be excluded from stubs generation []
[INFO] Building jar: D:\mancala-game\mancala-microservice\mancala-api\target\mancala-api-0.0.1-SNAPSHOT-stubs.jar
[INFO]
[INFO] --- maven-jar-plugin:3.1.2:jar (default-jar) @ mancala-api ---
[INFO] Building jar: D:\mancala-game\mancala-microservice\mancala-api\target\mancala-api-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.8.RELEASE:repackage (repackage) @ mancala-api ---
[INFO] Replacing main artifact with repackaged archive
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ mancala-api ---
[INFO] Installing D:\mancala-game\mancala-microservice\mancala-api\target\mancala-api-0.0.1-SNAPSHOT.jar to C:\Users\Talebi\.m2\repository\com\dzone\mancalagame\mancala-api\0.0.1-SNAPSHOT\mancala-api-0.0.1-SNAPSHOT.jar
[INFO] Installing D:\mancala-game\mancala-microservice\mancala-api\pom.xml to C:\Users\Talebi\.m2\repository\com\dzone\mancalagame\mancala-api\0.0.1-SNAPSHOT\mancala-api-0.0.1-SNAPSHOT.pom
[INFO] Installing D:\mancala-game\mancala-microservice\mancala-api\target\mancala-api-0.0.1-SNAPSHOT-stubs.jar to C:\Users\Talebi\.m2\repository\com\dzone\mancalagame\mancala-api\0.0.1-SNAPSHOT\mancala-api-0.0.1-SNAPSHOT-stubs.jar
[INFO]
[INFO] -----------------< com.dzone.mancalagame:mancala-web >------------------
[INFO] Building mancala-web 0.1.0                                         [2/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ mancala-web ---
[INFO] Deleting D:\mancala-game\mancala-microservice\mancala-web\target
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ mancala-web ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ mancala-web ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 14 source files to D:\mancala-game\mancala-microservice\mancala-web\target\classes
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ mancala-web ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 3 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ mancala-web ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to D:\mancala-game\mancala-microservice\mancala-web\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ mancala-web ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.dzone.mancala.web.client.CDCApplicationTests
13:19:36.022 [main] DEBUG org.springframework.test.context.junit4.SpringJUnit4ClassRunner - SpringJUnit4ClassRunner constructor called with [class com.dzone.mancala.web.client.CDCApplicationTests]
...
...
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 38.665 s - in com.dzone.mancala.web.client.CDCApplicationTests
[INFO] Running com.dzone.mancala.web.client.MancalaIntegrationTests
...
...
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 9.815 s - in com.dzone.mancala.web.client.MancalaIntegrationTests
2019-10-12 13:20:24.280  INFO [mancala-web,,,] 4484 --- [      Thread-21] c.n.l.PollingServerListUpdater           : Shutting down the Executor Pool for PollingServerListUpdater
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:3.1.2:jar (default-jar) @ mancala-web ---
[INFO] Building jar: D:\mancala-game\mancala-microservice\mancala-web\target\mancala-web-0.1.0.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.8.RELEASE:repackage (repackage) @ mancala-web ---
[INFO] Replacing main artifact with repackaged archive
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ mancala-web ---
[INFO] Installing D:\mancala-game\mancala-microservice\mancala-web\target\mancala-web-0.1.0.jar to C:\Users\Talebi\.m2\repository\com\dzone\mancalagame\mancala-web\0.1.0\mancala-web-0.1.0.jar
[INFO] Installing D:\mancala-game\mancala-microservice\mancala-web\pom.xml to C:\Users\Your-OS-User\.m2\repository\com\dzone\mancalagame\mancala-web\0.1.0\mancala-web-0.1.0.pom
[INFO]
[INFO] -------------< com.dzone.mancalagame:mancala-microservice >-------------
[INFO] Building mancala-microservice 0.0.1-SNAPSHOT                       [3/3]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.0.0:clean (default-clean) @ mancala-microservice ---
[INFO]
[INFO] --- spring-boot-maven-plugin:2.0.6.RELEASE:repackage (default) @ mancala-microservice ---
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ mancala-microservice ---
[INFO] Installing D:\mancala-game\mancala-microservice\pom.xml to C:\Users\[Your-OS-User]\.m2\repository\com\dzone\mancalagame\mancala-microservice\0.0.1-SNAPSHOT\mancala-microservice-0.0.1-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] mancala-api 0.0.1-SNAPSHOT ......................... SUCCESS [01:52 min]
[INFO] mancala-web 0.1.0 .................................. SUCCESS [01:05 min]
[INFO] mancala-microservice 0.0.1-SNAPSHOT ................ SUCCESS [  6.679 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:16 min
[INFO] Finished at: 2019-10-12T13:20:33+03:30
[INFO] ------------------------------------------------------------------------

Обратите внимание, что мы выполнили эту команду в родительском проекте под названием mancala-microservice . Каждый раз, когда мы пытаемся упаковать проект mancala-microservice ( корневой проект двух микросервисов ) и использовать артефакты для запуска в производство, новая версия заглушки будет сгенерирована проектом ‘mancala-api’ и будет помещена в ваш локальный репозиторий maven, который снова будет использоваться проектом ‘mancala-web’ для тестирования реализации клиента с провайдером API.

Поэтому, если будут какие-либо изменения в сигнатуре API, она будет обнаружена именно здесь. Это обеспечивает постоянную синхронизацию как производителя, так и потребителей API, и в вашей производственной среде не будет никаких неожиданных исключений.

Полный исходный код этой статьи доступен в моем GitHub

Чтобы создать и запустить это приложение, пожалуйста, следуйте инструкциям, которые я предоставил здесь .  

Ваши ценные отзывы и комментарии высоко ценятся!

Дальнейшее чтение

Избегание подхода Super Mario Bros.

Как технологии улучшают игровой опыт