-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
3주차 과제 - Spring 테스트 작성하기 #83
Changes from 11 commits
d3341a1
77d34df
9fb83c8
b357afb
30e07c6
45263d4
4caf49c
5c8484e
8aebc01
3503251
3089b94
9815368
7d25cc2
7fd1735
8e03669
23c6e66
d7cdfba
9c7d9e6
4a9d718
b86f1b5
8138d60
f4ae790
067bf9b
a8f92e7
c12316e
ad0b279
28f14e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,3 +24,4 @@ Thumbs.db | |
Thumbs.db:encryptable | ||
ehthumbs.db | ||
ehthumbs_vista.db | ||
/app/out/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,20 @@ | ||
package com.codesoom.assignment.models; | ||
|
||
import java.util.Objects; | ||
|
||
public class Task { | ||
private Long id; | ||
|
||
private String title; | ||
|
||
public Task() { | ||
} | ||
|
||
public Task(Long id, String title) { | ||
this.id = id; | ||
this.title = title; | ||
} | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
@@ -20,4 +30,28 @@ public String getTitle() { | |
public void setTitle(String title) { | ||
this.title = title; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) | ||
return true; | ||
if (o == null || this.getClass() != o.getClass()) | ||
return false; | ||
Task task = (Task) o; | ||
return Objects.equals(this.id, task.getId()) && this.title.equals(task.getTitle()); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(id, title); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
final StringBuilder sb = new StringBuilder("Task{"); | ||
sb.append("id=").append(id); | ||
sb.append(", title='").append(title).append('\''); | ||
sb.append('}'); | ||
return sb.toString(); | ||
Comment on lines
+51
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. String.format을 사용해서 표현하는 건 어떨까요? 다음과 같이 표현할 수 있을거에요 return String.format("{ id = %s, title = %s }", id, title); 더 좋은건 Template literal이지만 자바에서는 지원하질 않네요 ㅠ 만약 이런게 지원하는 코틀린을 사용했다면 다음과 같이 사용할 수 있을거에요 return "{ id = $id, title = $title }" |
||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,302 @@ | ||||||
package com.codesoom.assignment.controllers; | ||||||
|
||||||
import com.codesoom.assignment.TaskNotFoundException; | ||||||
import com.codesoom.assignment.application.TaskService; | ||||||
import com.codesoom.assignment.models.Task; | ||||||
import org.junit.jupiter.api.BeforeEach; | ||||||
import org.junit.jupiter.api.DisplayName; | ||||||
import org.junit.jupiter.api.Nested; | ||||||
import org.junit.jupiter.api.Test; | ||||||
|
||||||
import static org.assertj.core.api.Assertions.assertThat; | ||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||||||
|
||||||
class TaskControllerNestedTest { | ||||||
|
||||||
private final String DEFAULT_TITLE = "TEST"; | ||||||
private final long DEFAULT_SIZE = 3; | ||||||
private TaskController controller; | ||||||
|
||||||
@BeforeEach | ||||||
void setUp() { | ||||||
// subject | ||||||
controller = new TaskController(new TaskService()); | ||||||
|
||||||
// fixtures | ||||||
} | ||||||
|
||||||
void setDefaultSizeTasks(){ | ||||||
for(long i = 1 ; i <= DEFAULT_SIZE ; i++){ | ||||||
Task newTask = new Task(); | ||||||
newTask.setTitle(DEFAULT_TITLE + i); | ||||||
controller.create(newTask); | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("list 메소드는") | ||||||
class Describe_List{ | ||||||
|
||||||
@Nested | ||||||
@DisplayName("Task가 존재한다면") | ||||||
class Context_ExistsTask{ | ||||||
|
||||||
@BeforeEach | ||||||
void setUp(){ | ||||||
setDefaultSizeTasks(); | ||||||
} | ||||||
|
||||||
@Test | ||||||
@DisplayName("모든 Task를 반환한다") | ||||||
void It_ReturnAllTask(){ | ||||||
assertThat(controller.list()).hasSize((int) DEFAULT_SIZE); | ||||||
for(long i = 1 ; i <= DEFAULT_SIZE ; i++){ | ||||||
assertThat(controller.detail(i)).isEqualTo(new Task(i , DEFAULT_TITLE + i)); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("Task가 존재하지 않는다면") | ||||||
class Context_NotExistsTask{ | ||||||
private final int EMPTY = 0; | ||||||
|
||||||
@Test | ||||||
@DisplayName("비어있는 List를 반환한다") | ||||||
void It_ReturnEmptyList(){ | ||||||
assertThat(controller.list()).hasSize(EMPTY); | ||||||
} | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 우리는 지금 스코프가 있는 테스트 코드 스타일을 선택해서 작업을 하고 있어요. 이렇게 테스트별로 스코프가 있는 방식으로 작업할 때 열심히 고민해야 하는 주제는 바로 테스트 코드 스코프에서 논리적인 인과를 잘 마련해주는 것입니다. 여기에서 정말 논리적으로 튼튼한 테스트코드를 작성하려면 몇 가지 조치가 필요해요. 가장 쉬운 방법은 이 두 가지입니다.
위의 두 방법이 각자 어떤 장점을 갖고 있는지에 대해 생각해 보세요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
스코프가 있는 테스트 코드 스타일의 논리적인 인과 기억하겠습니다 ㅎㅎ
글로 명시하는게 읽는 사람에겐 더 정확하게 전달될 것 같습니다. 하지만
Context단계에서 테스트에 대한 준비를 더 철저하게 해주는게 더 좋을 것 같습니다 전체적으로 다시 확인 해보겠습니다! |
||||||
|
||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("detail 메소드는") | ||||||
class Describe_Detail{ | ||||||
|
||||||
@Nested | ||||||
@DisplayName("id가 null이라면") | ||||||
class Context_NullPathVariable{ | ||||||
private final Long givenId = null; | ||||||
|
||||||
@Test | ||||||
@DisplayName("TaskNotFoundException를 던진다") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 잘 수정하셨습니다. 그런데 여기에서 한 가지 더 생각해볼만한 문제가 있습니다. 바로 아마 그 분은 이 테스트의
Suggested change
하지만 위와 같이 설명을 작성해 두면 클래스 이름 변경에 취약했던 설명이 이름 변경에도 유연하게 대응하게 됩니다. 즉 클래스 이름에 테스트 설명이 강하게 의존하고 있었던 문제를 약하게 의존하게 만들어 해소한거죠. 잘 생각해보면 객체지향 원칙이 주석이나 설명에도 작동한다는 사실을 알 수 있습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아 중요한 걸 놓치고 있었네요.. |
||||||
void It_ThrowException(){ | ||||||
assertThatThrownBy(() -> controller.detail(givenId)) | ||||||
.isInstanceOf(TaskNotFoundException.class); | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("존재하지 않는 id로 조회한다면") | ||||||
class Context_SearchInvalidId{ | ||||||
private final Long invalidId = 100L; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 아이디가 정말로 존재하지 않는다는 논리적인 근거를 이 스코프에서 마련해 보세요. |
||||||
|
||||||
@Test | ||||||
@DisplayName("TaskNotFoundException을 던진다") | ||||||
void It_ThrowException(){ | ||||||
assertThatThrownBy(() -> controller.detail(invalidId)) | ||||||
.isInstanceOf(TaskNotFoundException.class); | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("존재하는 id로 조회한다면") | ||||||
class Context_SearchValidId{ | ||||||
|
||||||
@BeforeEach | ||||||
void setUp(){ | ||||||
setDefaultSizeTasks(); | ||||||
} | ||||||
|
||||||
@Test | ||||||
@DisplayName("Task를 반환한다") | ||||||
void It_ReturnTask(){ | ||||||
assertThat(controller.detail(DEFAULT_SIZE)) | ||||||
.isEqualTo(new Task(DEFAULT_SIZE , DEFAULT_TITLE + DEFAULT_SIZE)); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("create 메소드는") | ||||||
class Describe_Create{ | ||||||
|
||||||
@Nested | ||||||
@DisplayName("id가 null이 아닌 Task가 주어져도") | ||||||
class Context_NotNullIdTask{ | ||||||
private final long beforeId = DEFAULT_SIZE + 1; | ||||||
private final String title = DEFAULT_TITLE + beforeId; | ||||||
private final Task beforeTask = new Task(beforeId , title); | ||||||
|
||||||
@Test | ||||||
@DisplayName("generate된 id가 세팅된다") | ||||||
void It_GenerateId(){ | ||||||
Task afterTask = controller.create(beforeTask); | ||||||
assertThat(beforeTask.getId()).isNotEqualTo(afterTask.getId()); | ||||||
assertThat(afterTask.getTitle()).isEqualTo(title); | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("title이 null이면") | ||||||
class Context_NullTitle{ | ||||||
private final Task titleNullTask = new Task(); | ||||||
private final String title = null; | ||||||
|
||||||
@Test | ||||||
@DisplayName("Task의 제목은 null로 저장된다") | ||||||
void It_SaveTitleNull(){ | ||||||
Task saveTask = controller.create(titleNullTask); | ||||||
assertThat(saveTask.getTitle()).isEqualTo(title); | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("title이 존재하는 Task를 전달하면") | ||||||
class Context_ExistsTitleTask{ | ||||||
private final String title = DEFAULT_TITLE + DEFAULT_SIZE; | ||||||
private final Task beforeTask = new Task(null , title); | ||||||
|
||||||
@Test | ||||||
@DisplayName("저장된다") | ||||||
void It_SaveTask(){ | ||||||
int oldSize = controller.list().size(); | ||||||
Task afterTask = controller.create(beforeTask); | ||||||
assertThat(beforeTask.getTitle()).isEqualTo(afterTask.getTitle()); | ||||||
assertThat(controller.list()).hasSize(oldSize + 1); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("update 메소드는") | ||||||
class Describe_Update{ | ||||||
|
||||||
@Nested | ||||||
@DisplayName("id가 null이거나 id에 해당하는 Task가 없다면") | ||||||
class Context_IdIsNullAndInvalidId{ | ||||||
private final Long nullId = null; | ||||||
private final Long invalidId = 100L; | ||||||
private final Task updateTask = new Task(); | ||||||
|
||||||
@Test | ||||||
@DisplayName("TaskNotFouneException이 발생한다") | ||||||
void It_ThrowException(){ | ||||||
assertThatThrownBy(() -> controller.update(nullId , updateTask)) | ||||||
.isInstanceOf(TaskNotFoundException.class); | ||||||
assertThatThrownBy(() -> controller.update(invalidId , updateTask)) | ||||||
.isInstanceOf(TaskNotFoundException.class); | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("id에 해당하는 Task가 있으면") | ||||||
class Context_ValidId{ | ||||||
|
||||||
private final String title = DEFAULT_TITLE + (DEFAULT_SIZE + 1); | ||||||
private final Task beforeTask = new Task(null , title); | ||||||
|
||||||
@BeforeEach | ||||||
void setUp(){ | ||||||
setDefaultSizeTasks(); | ||||||
} | ||||||
|
||||||
@Test | ||||||
@DisplayName("Task의 title을 업데이트한다") | ||||||
void It_UpdateTitle(){ | ||||||
int oldSize = controller.list().size(); | ||||||
Task afterTask = controller.update(DEFAULT_SIZE , beforeTask); | ||||||
assertThat(beforeTask.getTitle()).isEqualTo(afterTask.getTitle()); | ||||||
assertThat(controller.list()).hasSize(oldSize); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("patch 메소드는") | ||||||
class Describe_Patch{ | ||||||
|
||||||
@Nested | ||||||
@DisplayName("id가 null이거나 id에 해당하는 Task가 없다면") | ||||||
class Context_IdIsNullAndInvalidId{ | ||||||
|
||||||
private final Long nullId = null; | ||||||
private final Long invalidId = 100L; | ||||||
private final Task updateTask = new Task(); | ||||||
|
||||||
@Test | ||||||
@DisplayName("TaskNotFouneException이 발생한다") | ||||||
void It_ThrowException(){ | ||||||
assertThatThrownBy(() -> controller.patch(nullId , updateTask)) | ||||||
.isInstanceOf(TaskNotFoundException.class); | ||||||
assertThatThrownBy(() -> controller.patch(invalidId , updateTask)) | ||||||
.isInstanceOf(TaskNotFoundException.class); | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("id에 해당하는 Task가 있으면") | ||||||
class Context_ValidId{ | ||||||
|
||||||
private final String title = DEFAULT_TITLE + (DEFAULT_SIZE + 1); | ||||||
private final Task beforeTask = new Task(null , title); | ||||||
|
||||||
@BeforeEach | ||||||
void setUp(){ | ||||||
setDefaultSizeTasks(); | ||||||
} | ||||||
|
||||||
@Test | ||||||
@DisplayName("Task의 title을 업데이트한다") | ||||||
void It_UpdateTitle(){ | ||||||
int oldSize = controller.list().size(); | ||||||
Task afterTask = controller.patch(DEFAULT_SIZE , beforeTask); | ||||||
assertThat(beforeTask.getTitle()).isEqualTo(afterTask.getTitle()); | ||||||
assertThat(controller.list()).hasSize(oldSize); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("delete 메소드는") | ||||||
class Describe_Delete{ | ||||||
|
||||||
@Nested | ||||||
@DisplayName("id가 null이거나 id에 해당하는 Task가 없다면") | ||||||
class Context_IdIsNullAndInvalidId{ | ||||||
|
||||||
private final Long nullId = null; | ||||||
private final Long invalidId = 100L; | ||||||
|
||||||
@Test | ||||||
@DisplayName("TaskNotFouneException이 발생한다") | ||||||
void It_ThrowException(){ | ||||||
assertThatThrownBy(() -> controller.delete(nullId)) | ||||||
.isInstanceOf(TaskNotFoundException.class); | ||||||
assertThatThrownBy(() -> controller.delete(invalidId)) | ||||||
.isInstanceOf(TaskNotFoundException.class); | ||||||
} | ||||||
} | ||||||
|
||||||
@Nested | ||||||
@DisplayName("id에 해당하는 Task가 있다면") | ||||||
class Context_ValidId{ | ||||||
|
||||||
private final Long validId = DEFAULT_SIZE; | ||||||
|
||||||
@BeforeEach | ||||||
void setUp(){ | ||||||
setDefaultSizeTasks(); | ||||||
} | ||||||
|
||||||
@Test | ||||||
@DisplayName("해당 Task를 삭제한다") | ||||||
void It_DeleteTask(){ | ||||||
int beforeSize = controller.list().size(); | ||||||
controller.delete(validId); | ||||||
assertThat(controller.list()).hasSize(beforeSize - 1); | ||||||
} | ||||||
} | ||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋습니다! 다만
if
문은 한 줄짜리라도 꼭 중괄호를 생략하지 않는 습관을 들이는 것이 좋습니다. 비싸지 않으면서도 치명적인 실수를 방어해주는 좋은 습관이죠.이와 관련된 유명하고 재미있는 버그 이야기가 있습니다. Apple의 SSL/TLS 프로토콜에서 발견된 끔찍한 실수입니다.
if
중괄호가 습관화되어 있었다면 하지 않았을 실수죠.https://embeddedgurus.com/barr-code/2014/03/apples-gotofail-ssl-security-bug-was-easily-preventable/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런 일이 있었군요 ㅎㅎ 감사합니다 !!
수정하겠습니다