마이크로서비스를 구축할 때 단위 테스트, 통합 테스트, 컴포넌트 테스트, 계약 테스트, 엔드투엔드 테스트까지 모두 사용해야 함
기능은 여러 개의 .feature 파일로 구성됨
각 기능 상단에 기능에 대한 설명이 있고, 이 설명은 엔진에 의해 무시되는 부분
기능은 테스트 케이스를 정의하는 여러 시나리오로 구성됨
각 시나리오는 BDD 키워드(Given, When, Then, And, But)를 이용한 여러 스텝으로 정의
같은 시나리오의 스텝은 서로 상태를 공유할 수 있음
[작성할 기능]
1. 답안을 통한 상호 작용을 테스트
2. 리더보드의 기능을 테스트
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>microservices.book</groupId>
<artifactId>tests-e2e-v9</artifactId>
<packaging>jar</packaging>
<version>0.9.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<name>tests-e2e-v9</name>
<description>소셜 곱셈 애플리케이션 - 엔드투엔드 테스트 (도서 스프링 부트로 배우는 마이크로서비스)</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<jackson-2-version>2.8.9</jackson-2-version>
<cucumber-version>1.2.5</cucumber-version>
</properties>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>${cucumber-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>${cucumber-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
<version>4.5.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
<!-- 스트리밍 API를 포함한 코어는 저수준 추상화를 공유함 (하지만 데이터 바인딩은 하지 않음) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-2-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson-2-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-2-version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
새로운 API 인터페이스를 추가하고, 테스트 프로파일을 적용하고, 테스트 데이터를 분리하여 테스트할 수 있는 시스템으로 수정함
package microservices.book.gamification.controller;
import microservices.book.gamification.domain.ScoreCard;
import microservices.book.gamification.service.GameService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Gamification 사용자 통계 서비스의 REST API
*/
@RestController
@RequestMapping("/scores")
class ScoreController {
private final GameService gameService;
public ScoreController(final GameService gameService) {
this.gameService = gameService;
}
@GetMapping("/{attemptId}")
public ScoreCard getScoreForAttempt(
@PathVariable("attemptId") final Long attemptId) {
return gameService.getScoreForAttempt(attemptId);
}
}
server:
port: 8000
zuul:
ignoredServices: '*'
prefix: /api
routes:
multiplications:
path: /multiplications/**
serviceId: multiplication
strip-prefix: false
results:
path: /results/**
serviceId: multiplication
strip-prefix: false
users:
path: /users/**
serviceId: multiplication
strip-prefix: false
leaders:
path: /leaders/**
serviceId: gamification
strip-prefix: false
scores:
path: /scores/**
serviceId: gamification
strip-prefix: false
stats:
path: /stats/**
serviceId: gamification
strip-prefix: false
endpoints:
routes:
sensitive: false
trace:
sensitive: false
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka/
package microservices.book.multiplication.controller;
import microservices.book.multiplication.domain.User;
import microservices.book.multiplication.repository.UserRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping("/{userId}")
public Optional<User> getUserById(@PathVariable("userId") final Long userId) {
return userRepository.findById(userId);
}
}
[테스트 프로파일 도입 목적]
spring.datasource.url=jdbc:h2:mem:gamification-test;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL;AUTO_SERVER=TRUE
package microservices.book.gamification.controller;
import microservices.book.gamification.service.AdminService;
import org.springframework.context.annotation.Profile;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Profile("test")
@RestController
@RequestMapping("/gamification/admin")
class AdminController {
private final AdminService adminService;
public AdminController(final AdminService adminService) {
this.adminService = adminService;
}
@PostMapping("/delete-db")
public ResponseEntity deleteDatabase() {
adminService.deleteDatabaseContents();
return ResponseEntity.ok().build();
}
}
package microservices.book.multiplication.controller;
import microservices.book.multiplication.service.AdminService;
import org.springframework.context.annotation.Profile;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Profile("test")
@RestController
@RequestMapping("/multiplication/admin")
public class AdminController {
private final AdminService adminService;
public AdminController(AdminService adminService) {
this.adminService = adminService;
}
@PostMapping("/delete-db")
public ResponseEntity deleteDatabase() {
adminService.deleteDatabaseContents();
return ResponseEntity.ok().build();
}
}
package microservices.book.gamification.service;
/**
* 애플리케이션 관리자 전용 오퍼레이션을 위한 서비스
* 운영 환경에서는 절대 사용하지 말고 테스트 용도로만 사용할 것
*/
public interface AdminService {
/**
* 데이터베이스 내용을 모두 삭제
*/
void deleteDatabaseContents();
}
package microservices.book.multiplication.service;
/**
* 애플리케이션 관리자 전용 오퍼레이션을 위한 서비스
* 운영 환경에서는 절대 사용하지 말고 테스트 용도로만 사용할 것
*/
public interface AdminService {
/**
* 데이터베이스 내용을 모두 삭제
*/
void deleteDatabaseContents();
}
package microservices.book.gamification.controller;
import microservices.book.gamification.service.AdminService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@RunWith(SpringRunner.class)
@ActiveProfiles(profiles = "test")
@WebMvcTest(AdminController.class)
public class AdminControllerEnabledTest {
@MockBean
private AdminService adminService;
@Autowired
private MockMvc mvc;
/**
* 테스트 프로파일이 설정된 경우 컨트롤러가 예상한대로 동작하는지 확인하는 테스트
* (클래스 어노테이션 참고)
*
* @throws Exception 에러가 발생한 경우
*/
@Test
public void deleteDatabaseTest() throws Exception {
// when
MockHttpServletResponse response = mvc.perform(
post("/gamification/admin/delete-db")
.accept(MediaType.APPLICATION_JSON))
.andReturn().getResponse();
// then
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
verify(adminService).deleteDatabaseContents();
}
}
package microservices.book.gamification.controller;
import microservices.book.gamification.service.AdminService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@RunWith(SpringRunner.class)
@WebMvcTest(AdminController.class)
public class AdminControllerDisabledTest {
@MockBean
private AdminService adminService;
@Autowired
private MockMvc mvc;
/**
* 프로파일이 테스트로 설정되지 않은 경우 컨트롤러가 NOT ACCESSIBLE 인지 확인하는 테스트
*
* @throws Exception 에러가 발생한 경우
*/
@Test
public void deleteDatabaseTest() throws Exception {
// when
MockHttpServletResponse response = mvc.perform(
post("/gamification/admin/delete-db")
.accept(MediaType.APPLICATION_JSON))
.andReturn().getResponse();
// then
assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
verifyNoInteractions(adminService);
}
}
server:
port: 8000
zuul:
ignoredServices: '*'
prefix: /api
routes:
multiplications:
path: /multiplications/**
serviceId: multiplication
strip-prefix: false
results:
path: /results/**
serviceId: multiplication
strip-prefix: false
users:
path: /users/**
serviceId: multiplication
strip-prefix: false
leaders:
path: /leaders/**
serviceId: gamification
strip-prefix: false
scores:
path: /scores/**
serviceId: gamification
strip-prefix: false
stats:
path: /stats/**
serviceId: gamification
strip-prefix: false
endpoints:
routes:
sensitive: false
trace:
sensitive: false
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka/
---
# 테스트 목적의 관리자 경로 추가
spring:
profiles: test
zuul:
routes:
gamification-admin:
path: /gamification/admin/**
serviceId: gamification
strip-prefix: false
multiplication-admin:
path: /multiplication/admin/**
serviceId: multiplication
strip-prefix: false
# language: ko
기능: 사용자는 정답일 수도 있고 오답일 수도 있는 곱셈 답안을 제출할 수 있다.
사용자가 정답을 제출하면 결과가 정답임을 나타내는 응답을 받는다.
또한 점수를 받고 몇 가지 배지도 받을 수 있다.
따라서 재접속하고 계속 플레이할 수 있도록 동기부여가 된다.
배지는 첫 번째 정답을 맞출 때, 사용자가 100점, 500점, 999점을 획득할 때 각각 획득할 수 있다.
사용자가 오답을 제출하면 점수와 배지 모두 얻을 수 없다.
시나리오: 사용자가 첫 번째 정답 답안을 제출하고 배지를 얻는다
만약 사용자 철수가 1개의 정답 답안을 제출한다
그러면 사용자는 답안이 정답이라는 응답을 받는다
그리고 사용자는 10점을 얻는다
그리고 사용자는 FIRST_WON 배지를 얻는다
시나리오: 사용자는 두 번째 정답 답안을 제출하고 포인트만 얻는다
먼저 사용자 철수가 1개의 정답 답안을 제출한다
그리고 사용자는 FIRST_WON 배지를 얻는다
만약 사용자 철수가 1개의 정답 답안을 제출한다
그러면 사용자는 답안이 정답이라는 응답을 받는다
그리고 사용자는 10점을 얻는다
그리고 사용자는 배지를 얻지 못한다
시나리오: 사용자는 오답 답안을 제출하고 아무것도 얻지 못한다
만약 사용자 철수가 1개의 오답 답안을 제출한다
그러면 사용자는 답안이 오답이라는 응답을 받는다
그리고 사용자는 0점을 얻는다
그리고 사용자는 배지를 얻지 못한다
# 브론즈, 실버, 골드 배지 확인
시나리오 개요: <이전_답안> 정답을 제출한 이후에 사용자가 1개의 정답을 제출하고 <배지_이름> 배지를 얻는다.
먼저 사용자 철수가 <이전_답안>개의 정답 답안을 제출한다
만약 사용자 철수가 1개의 정답 답안을 제출한다
그러면 사용자는 답안이 정답이라는 응답을 받는다
그리고 사용자는 10점을 얻는다
그리고 사용자는 <배지_이름> 배지를 얻는다
예:
| 이전_답안 | 배지_이름 |
| 9 | BRONZE_MULTIPLICATOR |
| 49 | SILVER_MULTIPLICATOR |
| 99 | GOLD_MULTIPLICATOR |
package microservices.book;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"pretty", "html:target/cucumber", "junit:target/junit-report.xml"},
features = "src/test/resources/multiplication.feature")
public class MultiplicationFeatureTest {
}
package microservices.book;
import cucumber.api.java.Before;
import cucumber.api.java.ko.그러면;
import cucumber.api.java.ko.먼저;
import microservices.book.testutils.MultiplicationApplication;
import microservices.book.testutils.beans.AttemptResponse;
import microservices.book.testutils.beans.Stats;
import java.util.List;
import java.util.stream.IntStream;
import static org.assertj.core.api.Assertions.assertThat;
public class MultiplicationFeatureSteps {
private MultiplicationApplication app;
private AttemptResponse lastAttemptResponse;
private Stats lastStatsResponse;
public MultiplicationFeatureSteps() {
this.app = new MultiplicationApplication();
}
@Before
public void cleanUp() {
app.deleteData();
}
@먼저("^사용자 ([^\\s]+)가 (\\d+)개의 ([^\\s]+) 답안을 제출한다$")
public void 사용자가_정답을_제출한다(final String userAlias,
final int attempts,
final String rightOrWrong) throws Throwable {
int attemptsSent = IntStream.range(0, attempts)
.mapToObj(i -> app.sendAttempt(userAlias, 10, 10,
"정답".equals(rightOrWrong) ? 100 : 258))
// 마지막 답안을 나중에 사용하도록 저장
.peek(response -> lastAttemptResponse = response)
.mapToInt(response -> response.isCorrect() ? 1 : 0)
.sum();
assertThat(attemptsSent).isEqualTo("정답".equals(rightOrWrong) ? attempts : 0)
.withFailMessage("답안을 애플리케이션으로 전송 시 에러");
}
@그러면("^사용자는 답안이 ([^\\s]+)이라는 응답을 받는다$")
public void 사용자는_응답을_받는다(final String rightOrWrong) throws Throwable {
assertThat(lastAttemptResponse.isCorrect())
.isEqualTo("정답".equals(rightOrWrong))
.withFailMessage("기대한 응답 "
+ rightOrWrong + " 답안");
}
@그러면("^사용자는 (\\d+)점을 얻는다$")
public void 사용자는_점수를_얻는다(final int points) throws Throwable {
long attemptId = lastAttemptResponse.getId();
Thread.currentThread().sleep(2000);
int score = app.getScoreForAttempt(attemptId).getScore();
assertThat(score).isEqualTo(points);
}
@그러면("^사용자는 ([^\\s]+) 배지를 얻는다$")
public void 사용자는_배지를_얻는다(final String badgeType) throws Throwable {
long userId = lastAttemptResponse.getUser().getId();
Thread.currentThread().sleep(200);
lastStatsResponse = app.getStatsForUser(userId);
List<String> userBadges = lastStatsResponse.getBadges();
assertThat(userBadges).contains(badgeType);
}
@그러면("^사용자는 배지를 얻지 못한다$")
public void 사용자는_배지를_얻지_못한다() throws Throwable {
long userId = lastAttemptResponse.getUser().getId();
Stats stats = app.getStatsForUser(userId);
List<String> userBadges = stats.getBadges();
if (stats.getScore() == 0) {
assertThat(stats.getBadges()).isNullOrEmpty();
} else {
assertThat(userBadges).isEqualTo(lastStatsResponse.getBadges());
}
}
@먼저("^사용자는 (\\d+)점을 가지고 있다$")
public void 사용자는_점수를_가지고_있다(final int points) throws Throwable {
long userId = lastAttemptResponse.getUser().getId();
int statPoints = app.getStatsForUser(userId).getScore();
assertThat(points).isEqualTo(statPoints);
}
public AttemptResponse getLastAttemptResponse() {
return lastAttemptResponse;
}
public Stats getLastStatsResponse() {
return lastStatsResponse;
}
public MultiplicationApplication getApp() {
return app;
}
}
package microservices.book.testutils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import microservices.book.testutils.beans.AttemptResponse;
import microservices.book.testutils.beans.User;
import microservices.book.testutils.beans.LeaderBoardPosition;
import microservices.book.testutils.beans.Stats;
import microservices.book.testutils.beans.ScoreResponse;
import microservices.book.testutils.http.ApplicationHttpUtils;
import java.io.IOException;
import java.util.List;
public class MultiplicationApplication {
private static final String APPLICATION_BASE_URL = "http://localhost:8000/api";
private static final String CONTEXT_ATTEMPTS = "/results";
private static final String CONTEXT_SCORE = "/scores/";
private static final String CONTEXT_STATS = "/stats";
private static final String CONTEXT_USERS = "/users/";
private static final String CONTEXT_LEADERBOARD = "/leaders";
private static final String CONTEXT_DELETE_DATA_GAM = "/gamification/admin/delete-db";
private static final String CONTEXT_DELETE_DATA_MULT = "/multiplication/admin/delete-db";
private ApplicationHttpUtils httpUtils;
public MultiplicationApplication() {
this.httpUtils = new ApplicationHttpUtils(APPLICATION_BASE_URL);
}
public AttemptResponse sendAttempt(String userAlias, int factorA, int factorB, int result) {
String attemptJson = "{\"user\":{\"alias\":\"" + userAlias + "\"}," +
"\"multiplication\":{\"factorA\":\"" + factorA + "\",\"factorB\":\"" + factorB + "\"}," +
"\"resultAttempt\":\"" + result + "\"}";
String response = httpUtils.post(CONTEXT_ATTEMPTS, attemptJson);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
return objectMapper.readValue(response, AttemptResponse.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public ScoreResponse getScoreForAttempt(long attemptId) {
String response = httpUtils.get(CONTEXT_SCORE + attemptId);
if (response.isEmpty()) {
return new ScoreResponse(0);
} else {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
return objectMapper.readValue(response, ScoreResponse.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public Stats getStatsForUser(long userId) {
String response = httpUtils.get(CONTEXT_STATS + "?userId=" + userId);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
return objectMapper.readValue(response, Stats.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public User getUser(long userId) {
String response = httpUtils.get(CONTEXT_USERS + userId);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
return objectMapper.readValue(response, User.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public List<LeaderBoardPosition> getLeaderboard() {
String response = httpUtils.get(CONTEXT_LEADERBOARD);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
JavaType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, LeaderBoardPosition.class);
return objectMapper.readValue(response, javaType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void deleteData() {
httpUtils.post(CONTEXT_DELETE_DATA_GAM, "");
httpUtils.post(CONTEXT_DELETE_DATA_MULT, "");
}
}
-> REST API로 JSON을 받고 Jackson을 이용해 일반 객체로 매핑
package microservices.book.testutils.http;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.apache.http.entity.ContentType;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
public class ApplicationHttpUtils {
private final String baseUrl;
public ApplicationHttpUtils(final String baseUrl) {
this.baseUrl = baseUrl;
}
public String post(final String context, final String body) {
try {
HttpResponse response = Request.Post(baseUrl + context)
.bodyString(body, ContentType.APPLICATION_JSON)
.execute().returnResponse();
assertIs200(response);
return EntityUtils.toString(response.getEntity());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String get(final String context) {
try {
HttpResponse response = Request.Get(baseUrl + context)
.execute().returnResponse();
assertIs200(response);
return EntityUtils.toString(response.getEntity());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void assertIs200(final HttpResponse httpResponse) {
assertThat(httpResponse.getStatusLine().getStatusCode()).isEqualTo(200);
}
}
[테스트 할 기본 시나리오]
# language: ko
기능: 사용자는 점수가 높은 순부터 낮은 순으로 목록에 올라 있다.
점수를 얻으면 순위가 올라갈 수 있다.
시나리오: 사용자가 정답 답안을 더 많이 제출하고 1등이 된다.
만약 사용자 철수가 2개의 정답 답안을 제출한다
그리고 사용자 영희가 1개의 정답 답안을 제출한다
그러면 사용자 철수가 리더보드에서 1등이 된다
그리고 사용자 영희가 리더보드에서 2등이 된다
시나리오: 사용자는 더 높은 점수를 얻으면 다른 사용자를 등수를 앞지른다
먼저 사용자 철수가 3개의 정답 답안을 제출한다
그리고 사용자 영희가 2개의 정답 답안을 제출한다
그리고 사용자 철수가 리더보드에서 1등이 된다
만약 사용자 영희가 2개의 정답 답안을 제출한다
그러면 사용자 영희가 리더보드에서 1등이 된다
그리고 사용자 철수가 리더보드에서 2등이 된다
package microservices.book;
import cucumber.api.java.ko.그러면;
import microservices.book.testutils.beans.LeaderBoardPosition;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class LeaderboardFeatureSteps {
private MultiplicationFeatureSteps mSteps;
public LeaderboardFeatureSteps(final MultiplicationFeatureSteps mSteps) {
this.mSteps = mSteps;
}
@그러면("^사용자 ([^\\s]+)가 리더보드에서 (\\d+)등이 된다$")
public void 사용자가_리더보드에서_등수에_오른다(final String user, final int position) throws Throwable {
Thread.currentThread().sleep(500);
List<LeaderBoardPosition> leaderBoard = mSteps.getApp().getLeaderboard();
assertThat(leaderBoard).isNotEmpty();
long userId = leaderBoard.get(position - 1).getUserId();
String userAlias = mSteps.getApp().getUser(userId).getAlias();
assertThat(userAlias).isEqualTo(user);
}
}
package microservices.book;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"pretty", "html:target/cucumber", "junit:target/junit-report.xml"},
features = "src/test/resources/leaderboard.feature")
public class LeaderboardFeatureTest {
}
[실행 순서]
1. RabbitMQ 서버를 실행
2. 서비스 레지스트리 마이크로 서비스를 실행 (프로파일 없음)
3. 게이트웨이 마이크로서비스를 실행(테스트 프로파일)
4. 곱셈 마이크로서비스를 실행 (테스트 프로파일)
5. 게임화 마이크로서비스를 실행 (테스트 프로파일)
6. ui 루트 폴더에서 제티 웹 서버를 실행(UI는 테스트하지 않기 때문에 실행하지 않아도 됨)