업무하다가 CucumberTest가 동작하는 원리에 대해 새롭게 알게 된 게 있어서 정리한다.
주로 컨트롤러 테스트를 위해 이용한다.
feature 에서 시나리오를 생성해, given, when, then 에 해당하는 입력값 혹은 결괏값을 정의한다.
Feature: Get person by ID
Scenario: Retrieve person data by ID
Given a person with ID 1 exists
When I send a GET request to "/person/1"
Then the response status should be 200
And the response should contain the person's name "John Doe"
이렇게 Feature 파일을 생성하고 테스트를 원하는 시나리오를 작성한다.
Given, When, Then 옆에는 추후 작성할 Step Definition에서 사용할 함수 이름을 작성한다. 함수 이름으로 Feature와 테스트 함수가 매칭된다.
@RestController
@RequestMapping("/person")
class PersonController {
@GetMapping("/{id}")
fun getPerson(@PathVariable id: Long): ResponseEntity<PersonResponse> {
// 예시: 실제로는 서비스나 DB에서 가져오겠지만 하드코딩
if (id == 1L) {
val person = PersonResponse(id = 1, name = "John Doe", age = 30)
return ResponseEntity.ok(person)
}
return ResponseEntity.notFound().build()
}
}
data class PersonResponse(
val id: Long,
val name: String,
val age: Int
)
이런 컨트롤러 메서드가 있다고 할 때, 시나리오에서는 이 컨트롤러 함수를 호출하게 된다.
@ContextConfiguration(classes = [PersonController::class])
@WebMvcTest
class PersonStepDefs {
@Autowired
private lateinit var mockMvc: MockMvc
private lateinit var result: MvcResult
@Given("a person with ID {long} exists")
fun a_person_with_id_exists(id: Long) {
val person = Person(id = id, name = "John Doe", age = 30)
val entityManager = (personRepository as JpaRepository<Person, Long>).entityManager()
entityManager.persist(person)
entityManager.flush()
}
@When("I send a GET request to {string}")
fun i_send_a_get_request_to(path: String) {
result = mockMvc.perform(MockMvcRequestBuilders.get(path))
.andReturn()
}
@Then("the response status should be {int}")
fun the_response_status_should_be(status: Int) {
Assertions.assertEquals(status, result.response.status)
}
@Then("the response should contain the person's name {string}")
fun the_response_should_contain_name(expectedName: String) {
val json = result.response.contentAsString
val jsonObj = JSONObject(json)
Assertions.assertEquals(expectedName, jsonObj.getString("name"))
}
}
이렇게 StepDefinition을 만들어 놓으면, 시나리오에 따라 차례로 Given, When, Then 함수가 실행되어 원하는 시나리오로 CucumberTest가 진행된다.
테스트를 위해 실제로 데이터베이스에 값을 저장하고 싶을 때는 Given에 원하는 데이터를 저장하는 코드를 작성하면 된다.
데이터 모델링을 위해 이런 방법을 사용할 수도 있다.
Feature: Get person by ID
Scenario: Retrieve person data by ID
Given the following persons exist:
| id | name | age |
| 1 | John Doe | 30 |
| 2 | Jane Smith| 25 |
When I send a GET request to "/person/1"
Then the response status should be 200
And the response should contain the person's name "John Doe"
테이블 형태로 Feature에 값을 데이터 모델링 형태로 작성하면
@Given("the following persons exist:")
fun the_following_persons_exist(dataTable: DataTable) {
val persons = dataTable.asMaps().map { row ->
Person(
id = row["id"]?.toLong(),
name = row["name"] ?: error("Name is required"),
age = row["age"]?.toInt() ?: error("Age is required")
)
}
// ID가 지정되어 있기 때문에 merge 또는 persist 방식 필요
val entityManager = (personRepository as JpaRepository<Person, Long>).entityManager()
persons.forEach {
entityManager.persist(it)
}
entityManager.flush()
}
StepDefinition에서 row["id"]와 같이 편리한 방식으로 값을 가져와서 데이터를 생성 및 저장할 수 있다.
wow!
업무하다가 CucumberTest가 동작하는 원리에 대해 새롭게 알게 된 게 있어서 정리한다.
주로 컨트롤러 테스트를 위해 이용한다.
feature 에서 시나리오를 생성해, given, when, then 에 해당하는 입력값 혹은 결괏값을 정의한다.
Feature: Get person by ID
Scenario: Retrieve person data by ID
Given a person with ID 1 exists
When I send a GET request to "/person/1"
Then the response status should be 200
And the response should contain the person's name "John Doe"
이렇게 Feature 파일을 생성하고 테스트를 원하는 시나리오를 작성한다.
Given, When, Then 옆에는 추후 작성할 Step Definition에서 사용할 함수 이름을 작성한다. 함수 이름으로 Feature와 테스트 함수가 매칭된다.
@RestController
@RequestMapping("/person")
class PersonController {
@GetMapping("/{id}")
fun getPerson(@PathVariable id: Long): ResponseEntity<PersonResponse> {
// 예시: 실제로는 서비스나 DB에서 가져오겠지만 하드코딩
if (id == 1L) {
val person = PersonResponse(id = 1, name = "John Doe", age = 30)
return ResponseEntity.ok(person)
}
return ResponseEntity.notFound().build()
}
}
data class PersonResponse(
val id: Long,
val name: String,
val age: Int
)
이런 컨트롤러 메서드가 있다고 할 때, 시나리오에서는 이 컨트롤러 함수를 호출하게 된다.
@ContextConfiguration(classes = [PersonController::class])
@WebMvcTest
class PersonStepDefs {
@Autowired
private lateinit var mockMvc: MockMvc
private lateinit var result: MvcResult
@Given("a person with ID {long} exists")
fun a_person_with_id_exists(id: Long) {
val person = Person(id = id, name = "John Doe", age = 30)
val entityManager = (personRepository as JpaRepository<Person, Long>).entityManager()
entityManager.persist(person)
entityManager.flush()
}
@When("I send a GET request to {string}")
fun i_send_a_get_request_to(path: String) {
result = mockMvc.perform(MockMvcRequestBuilders.get(path))
.andReturn()
}
@Then("the response status should be {int}")
fun the_response_status_should_be(status: Int) {
Assertions.assertEquals(status, result.response.status)
}
@Then("the response should contain the person's name {string}")
fun the_response_should_contain_name(expectedName: String) {
val json = result.response.contentAsString
val jsonObj = JSONObject(json)
Assertions.assertEquals(expectedName, jsonObj.getString("name"))
}
}
이렇게 StepDefinition을 만들어 놓으면, 시나리오에 따라 차례로 Given, When, Then 함수가 실행되어 원하는 시나리오로 CucumberTest가 진행된다.
테스트를 위해 실제로 데이터베이스에 값을 저장하고 싶을 때는 Given에 원하는 데이터를 저장하는 코드를 작성하면 된다.
데이터 모델링을 위해 이런 방법을 사용할 수도 있다.
Feature: Get person by ID
Scenario: Retrieve person data by ID
Given the following persons exist:
| id | name | age |
| 1 | John Doe | 30 |
| 2 | Jane Smith| 25 |
When I send a GET request to "/person/1"
Then the response status should be 200
And the response should contain the person's name "John Doe"
테이블 형태로 Feature에 값을 데이터 모델링 형태로 작성하면
@Given("the following persons exist:")
fun the_following_persons_exist(dataTable: DataTable) {
val persons = dataTable.asMaps().map { row ->
Person(
id = row["id"]?.toLong(),
name = row["name"] ?: error("Name is required"),
age = row["age"]?.toInt() ?: error("Age is required")
)
}
// ID가 지정되어 있기 때문에 merge 또는 persist 방식 필요
val entityManager = (personRepository as JpaRepository<Person, Long>).entityManager()
persons.forEach {
entityManager.persist(it)
}
entityManager.flush()
}
StepDefinition에서 row["id"]와 같이 편리한 방식으로 값을 가져와서 데이터를 생성 및 저장할 수 있다.
wow!