2022.05.29
ํ๊ต ํ๋ก์ ํธ ๋ง๊ฐ์ผ์ด ์ผ๋ง ๋จ์ง ์์์ ๊ฐ์ธ ๊ณต๋ถํ ์๊ฐ์ด ์ ์ ์์ด์ก๋ค... ๊ทธ๋๋ 1์๊ฐ์ฉ์ ๊ผญ ๊ณต๋ถํ๊ณ ์ ๋ฆฌํ๋ ค๊ณ ํ๋ค!
Spring ์ํ๊ณ๋ฅผ ์์๋ณด๊ธฐ ์ํ ๊ฒ ๋ชฉ์ ์ด๋ฏ๋ก ์ ๋ง ๋จ์ํ ์ฐ์ต ์์ ์!
1. ๋น์ง๋์ค ์๊ตฌ์ฌํญ ์ ๋ฆฌ
- ๋ฐ์ดํฐ: ํ์ID, ์ด๋ฆ
- ๊ธฐ๋ฅ: ํ์ ๋ฑ๋ก, ์กฐํ
- ๊ฐ์ : DB์ ์ฅ์๊ฐ ์์ง ์์ ํด์ง
- ์ปจํธ๋กค๋ฌ: MVC์ Controller ์ญํ
- ์๋น์ค: ํต์ฌ ๋น์ง๋์ค ๋ก์ง ๊ตฌํ ex) ํ์ ์ค๋ณต ๊ฐ์ ์ฒ๋ฆฌ
- ๋ฆฌํฌ์งํ ๋ฆฌ: DB์ ์ ๊ทผ, ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ DB์ ์ ์ฅํ๊ณ ๊ด๋ฆฌ
- ๋๋ฉ์ธ: ๋น์ง๋์ค ๋๋ฉ์ธ ๊ฐ์ฒด ex) ํ์, ์ฃผ๋ฌธ ๋ฑ๋ฑ ์ฃผ๋ก DB์ ์ ์ฅํ๊ณ ๊ด๋ฆฌ๋๋ ๋น์ฆ๋์ค ๊ฐ์ฒด
- ํด๋์ค ์์กด๊ด๊ณ
- ์์ง DB๊ฐ ์ ํด์ง์ง ์์๋ค๋ ๊ฐ์ ์ด๋ฏ๋ก ์ธํฐํ์ด์ค๋ก ๊ตฌํ ํด๋์ค๋ฅผ ๋ณ๊ฒฝํ ์ ์๋๋ก ์ค๊ณํจ
2. ์ฝ๋
public class Member {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member>
}
Null์ด ๋ฐํ๋ ๊ฐ๋ฅ์ฑ์ด ์์ ๋ Optional์ ์ฌ์ฉํจ!
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
}
3. ํ ์คํธ ์ฝ๋ ์์ฑ
์ง๊ธ๊น์ง ๋งค๋ฒ ํ ์คํธ ํ ๋ main์ ํตํด์ ์คํํ์๋๋ฐ JUnit ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ๋ฅผ ์คํํ๋ฉด ๋น ๋ฅด๊ณ ๋ฐ๋ณต ๊ฐ๋ฅํ๊ฒ ํ ์คํธ ๊ฐ๋ฅํ๋ค๊ณ ํ๋ค!
- test ํด๋์ Testํด๋์ค ์์ฑ(๊ด๋ก์ ํ ์คํธํ๋ ค๋ ํด๋์ค์ ์ด๋ฆ ๋ง์ง๋ง์ Test๋ฅผ ๋ถ์)
class MemoryMemberRepositoryTest {
MemberRepository repository = new MemoryMemberRepository();
@Test
public void save(){
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
Assertions.assertEquals(member, result); //member: ๊ธฐ๋๊ฐ, result: ๊ฒฐ๊ณผ
}
}
๋งค๋ฒ System.out.print()๋ก ์ฐ์ด๋ด๊ธฐ์๋ ํ๊ณ๊ฐ ์์ผ๋ jupiter or assertj ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ Assertions๋ฅผ ์ด์ฉํจ!
์คํ ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ์ฑ๊ณต์์๋ ์ฐฝ์ ๋ฐ๋ก ์ฐํ๋ ๊ฑฐ ์์ด ๋น๋ ์ฑ๊ณต์ผ๋ก ๋์จ๋ค!
์คํจ ์์๋ test Failed๊ฐ ์ถ๋ ฅ๋จ!
์์ฆ์ assertj ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ Assertions๋ฅผ ๋ง์ด ์ฌ์ฉํจ
Assertions.assertThat(member).isEqualTo(result);
assertThat๋ฉ์๋๋ฅผ static import ํ๋ฉด ์์ Assertions๋ฅผ ๋ถ์ด์ง ์๊ณ ์ฌ์ฉ ๊ฐ๋ฅํจ
๊ฟํ! ๋ณ์๋ฅผ ๋ณต์ฌํด์์ ์ด๋ฆ์ด ๊ฒน์น ๋๋ shift+F6์ ๋๋ฌ์ ๋ณ๊ฒฝํ๋ฉด ๊ทธ ๋ณ์๊ฐ ์ฌ์ฉ๋ ๊ณณ์ ํจ๊ป ๊ณ ์ณ์ค
- ํ ์คํธ๋ ๋ฉ์๋, ํด๋์ค, ํจํค์ง ๋จ์๋ก ํ ์คํธ ๊ฐ๋ฅํ๋ค.
- ๊ฐ ๋ฉ์๋๋ง๋ค ํ ์คํธ๊ฐ ๋๋๋ฉด ๋ฐ์ดํฐ๋ฅผ clear๋ฅผ ํด์ค์ผ ๋๋ค.
- ์๋ํ๋ฉด ํด๋์ค ๋จ์๋ก ํ ์คํธํ๋ค๊ณ ํ๋ฉด ๊ฐ ๋ฉ์๋์ ํ ์คํธ ์คํ ์์๊ฐ ๋ณด์ฅ์ด ๋์ง ์์์ ์ถฉ๋์ด ๋ฐ์ํ ์๋ ์๋ค.
- ๊ทธ๋์ ๊ฐ ๋ฉ์๋๋ง๋ค ๋ฐ์ดํฐ๋ฅผ clear ํด์ค์ผ ํ๋ค.
ํ ์คํธ๋ ์คํ ์์์ ์๊ด์ด ์์ด์ผ ํ๋ค
@AfterEach
public void afterEach(){
repository.clearStore();
}
์์ ์ฝ๋๋ฅผ ํ ์คํธ ํด๋์ค ์์ ์ถ๊ฐํ๋ค.
@AfterEach: ํญ์ ๋ค๋ฅธ ๋ฉ์๋์ ์คํ์ด ๋๋๊ณ ๋๋ฉด ์๋ ์คํ๋๋๋ก ํจ.
๊ทธ๋ฆฌ๊ณ repository์๋ ์ ์ฅ์๋ฅผ clear ํ๋ ๋ฉ์๋๋ฅผ ๊ตฌํํ๋ฉด ๋จ
4. TDD(Test-Driven Development)
- ํ
์คํธ ์ฝ๋๋ฅผ ๋จผ์ ์์ฑํ๊ณ ๊ตฌํ ํด๋์ค๋ฅผ ๋ง๋ค์ด์ ๊ฐ๋ฐํ๋ ๊ฒ
(๋์ค์ ๋ ๊ณต๋ถํด๋ณด๊ณ ์ถ์!!)
# ํ์ ์๋น์ค ๊ฐ๋ฐ
- Repository์ Role์ ๋จ์ํ ์ ์ฅ์์์ ํธ๋์ญ์
- Service์ Role์ ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๋ ๊ฒ
- Repository ํด๋์ค์์๋ ๋ฉ์๋ ์ด๋ฆ์ด findById์ ๊ฐ์ด ๋จ์ํ ์ ์ฅ์์์ ํธ๋์ญ์ ์ ๋ํ๋ด๋ ๋ค์ด๋ฐ์ ์ด๋ค.
- Service ํด๋์ค์์๋ join, findMembers ๋ฑ ๋น์ง๋์ค ๋ก์ง์ ๊ฐ๊น์ด ๋ค์ด๋ฐ์ ์ฌ์ฉํ๋ค.
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
/**
* ํ์๊ฐ์
*/
public Long join(Member member){
//๊ฐ์ ์ด๋ฆ์ด ์๋ ์ค๋ณต ํ์X ๋ผ๊ณ ๊ฐ์
Optional<Member> result = memberRepository.findByName(member.getName());
result.ifPresent(m -> {
throw new IllegalStateException("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
});
memberRepository.save(member);
return member.getId();
}
}
๊ฐ์ด null์ผ ๊ฒฝ์ฐ๊ฐ ์๋ค๋ฉด Optional๋ก ๊ฐ์ธ๋ ๊ฒ ํจ์จ์ ์(Optional์์ ์ ๊ณตํ๋ ์ฌ๋ฌ ํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
});
Optionalํ์ ์ ๋ณ์๋ฅผ ๋ฐ๋ก ๋ง๋ค์ง ์๊ณ ๋ฉ์๋ ์ฒด์ด๋์ ์ด์ฉํ์ฌ ํ ์๋ ์์
/**
* ํ์๊ฐ์
*/
public Long join(Member member){
//๊ฐ์ ์ด๋ฆ์ด ์๋ ์ค๋ณต ํ์X ๋ผ๊ณ ๊ฐ์
validateDuplicateMember(member); //์ค๋ณต ํ์ ๊ฒ์ฆ
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
});
}
์ด๋ฐ ์์ผ๋ก ๋ฉ์๋๋ฅผ ๊ฒ์ฆ ๋ก์ง์ ๋ถ๋ฆฌํ๋ ๊ฒ ๊น๋ํจ
# ํ์ ์๋น์ค ํ ์คํธ
ํ ์คํธ ์ฝ๋์ ๋ฉ์๋ ๋ช ์ ํ๊ธ๋ก ์ฐ๋ฉด ์ง๊ด์ ์ด์ด์ ๋ง์ด ์ฌ์ฉํ๋ค!
- given: ์ด๋ ํ ์ํฉ์ด ์ฃผ์ด์ ธ์
- when: ์ด๊ฑฐ๋ฅผ ์คํํ์ ๋
- then: ๊ฒฐ๊ณผ๊ฐ ์ด๊ฒ ๋์์ผ ํ๋ค
์ด ํจํด์ ์ฌ์ฉํ๋ฉด์ ๋์ค์ ์์ ์ ๊ฒ์ผ๋ก ๋ณํํ๋ ๊ฒ ์ข๋ค.
@Test
void ํ์๊ฐ์
() {
//given
Member member = new Member();
member.setName("hello");
//when
Long savedId = memberService.join(member);
//then
Member findMember = memberService.findOne(savedId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
public void ์ค๋ณต_ํ์_์์ธ(){
//given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member1.setName("spring");
//when
memberService.join(member1);
try{
memberService.join(member2);
fail(); //์ฌ๊ธฐ๋ก ์๋ฒ๋ฆฐ๋ค๋๊ฑด ์์ธ๊ฐ ๋ฐ์ํด์ผ ํ๋๋ฐ ๋ฐ์ํ์ง ์์๋ค๋ ๊ฒ์ด๋ฏ๋ก
}catch(IllegalStateException e){
assertThat(e.getMessage()).isEqualTo("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
}
//then
}
try-catch๋ฌธ์ ์ฌ์ฉํ๋ ๊ฒ ์ ๋งคํ ๋ถ๋ถ์ด ๋ง์์ ๋์ ์ Assertions.assertThrows() ์ฌ์ฉ
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
assertThrows์ ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ํ ์์ธ ํด๋์ค ํ์ ์ ๋ฃ๊ณ , ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ์์ธ๊ฐ ๋ฐ์ํด์ผ ํ ์์ ์ ๋๋ค์์ผ๋ก ์์ฑ.
- import static org.assertj.core.api.Assertions.;
- import static org.junit.jupiter.api.Assertions.;
static์ผ๋ก import ํ๋ฉด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ๋ ์์ Assertions๋ฅผ ๋ถ์ด์ง ์์๋ ๋จ
MemberRepository๊ฐ ์ฌ๋ฌ ๊ฐ ์์ฑ๋๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด์ MemberServiceํด๋์ค์์ MemberRepository๋ฅผ ์ฃผ์ ๋ฐ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
MemberServiceTestํด๋์ค์์๋ MemberService์ ๋๊ฐ์ db๋ฅผ ๊ฐ์ง MemoryMemberRepository๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด @BeforeEach ์ด๋ ธํ ์ด์ ์ ์ด์ฉํ์ฌ ๊ฐ ๋ฉ์๋์ ์คํ ์ ์ MemberSerivce์ memberRepository์ ๊ตฌํ์ฒด์ธ MemoryMemberRepository๋ฅผ ์ฃผ์ ์์ผ์ค๋ค. => DI(Dependency Injection)
class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach(){
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
@AfterEach //๋ค๋ฅธ ๋ฉ์๋์ ๋์์ด ๋๋๊ณ ์คํ๋จ
public void afterEach(){
memberRepository.clearStore();
}
.....
}
public class MemberForm {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@GetMapping("/members/new")
public String createForm(){
return "members/createMemberForm";
}
@PostMapping("/members/new")
public String create(MemberForm form){
Member member = new Member();
member.setName(form.getName());
memberService.join(member);
return "redirect:/"; //ํ์๊ฐ์
์ด ๋๋๋๊น redirect๋ก ํํ๋ฉด์ผ๋ก ๋ณด๋
}
@GetMapping("/members")
public String list(Model model){
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
}
ํ์ ๋ฑ๋ก
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<form action="/members/new" method="post">
<div class="form-group">
<label for="name">์ด๋ฆ</label>
<input type="text" id="name" name="name" placeholder="์ด๋ฆ์
์
๋ ฅํ์ธ์">
</div>
<button type="submit">๋ฑ๋ก</button>
</form>
</div> <!-- /container -->
</body>
</html>
์ด๋ ๊ฒ input์ name๊ฐ์ MemberForm ํด๋์ค์ set ๋ค์ ๋ถ๋ ์ด๋ฆ๊ณผ ๋์ผํ๊ฒ ํ๋ฉด MemberController์ post method create์ ํ๋ผ๋ฏธํฐ์ธ MemberForm์ ์์ฑ name์ Spring์ด ์๋์ผ๋ก public์ธ set๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ input๊ฐ์ด ๋ค์ด๊ฐ๊ฒ ๋จ.
ex) input์ ์์ฑ name="ace"์ด๋ฉด MemberForm์ setter์ name์ setAce์ฌ์ผ ๋์ํจ
ํ์ ์กฐํ
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<div>
<table>
<thead> <tr>
<th>#</th>
<th>์ด๋ฆ</th>
</tr>
</thead>
<tbody>
<tr th:each="member : ${members}">
<td th:text="${member.id}"></td>
<td th:text="${member.name}"></td>
</tr>
</tbody>
</table>
</div>
</div> <!-- /container -->
</body>
</html>
th:each="member : ${members}" => thymeleaf์ Loop ๋ฌธ๋ฒ
th:text="${member.id}" ์ฌ๊ธฐ์๋ Memberํด๋์ค์ ํ๋กํผํฐ id์ ์ ๊ทผํ ์ ์๋ get๋ฉ์๋๋ฅผ ์ด์ฉํ์ฌ ๊ฐ์ ๊ฐ์ ธ์ด
'๐ Backend > ๐ Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
7. Spring DB ์ ๊ทผ ๊ธฐ์ - H2 (0) | 2022.07.02 |
---|---|
6. Spring Bean ๋ฑ๋ก (0) | 2022.07.02 |
4. Spring Web ๊ฐ๋ฐ ๊ธฐ์ด ๊ฐ๋ (0) | 2022.07.01 |
3. Spring Library๐์ ํ๋ก์ ํธ ์ค์ (0) | 2022.07.01 |
2. Spring ํ๋ก์ ํธ ์์ฑํ๋๋ฒ (0) | 2022.07.01 |