https://github.com/MyoungSoo7/tddtest

 

GitHub - MyoungSoo7/tddtest: tddtest

tddtest. Contribute to MyoungSoo7/tddtest development by creating an account on GitHub.

github.com

 

자 유스케이스를 그려보고...

TDD는 늘 소스는 내것이 아니라고 가르치니~

모두를 위한 소스를 짜는 것을 고민하며..

자 업무 시나리오와 개발가능 범위를 자판기편에서 했는데,

https://iamipro.tistory.com/533

 

백엔드와 테스트 주도 개발 자판기편

1. TDD 이모저모TDD는 기능중심의 단위 테스트를 해볼수 있고, 이를 연계해 통합테스트까지 가능하다. 사실상 QA 비용이 절감되는 반면... 개발시간이 늘어나는 부분이 있다. AC2 애자일 코치 김창준

iamipro.tistory.com

좀더 디테일하게, 업무시나리오와 함께 업무규칙을 고려하고 개발가능 범위와 함께 요구사항 분석을 합니다.

 

자 그리고 소유기반, 행위기반으로 클래스를 만들어서..

이렇게 비디오 대여관련 패키지를 만들어 보았고.. 

테스트를 하면서 개발합니다.

일단 대여기능에 있어서, 비디오 정보를 가져오는 것 부터... 

테스트, 

잘되는군요~!

 

그러다가... 이거 그냥 세팅할까 하는 생각에..

세팅해버립니다.

대여 표현을 어찌할 까 한참 시행착오 끝에

user만 생성하는 생성자와

user, video, lend 다 생성하는 메서드 두개를 만들었더니,

뒤가 술술 풀렸습니다. 

자 가보죠ㅎㅎㅎ

자 세팅 했으니 고객이 비디오를 대여한 것이 제대로 되었나 확인,

포인트와, 빌릴일수 할인율은 일단 첫째날은 0으로~

 

자 여기서 난제는 할인입니다. 포인트 적립도 그렇지만~!

일단 가격과, 할인 비율과 기간을 가지고

 

비디오 타입별로 가격의 합을 구하는 메소드를  만들었습니다.

 

총대여가격은 lend한 video의 가격 다구하면 되고~

 

포인트는 생성시.. 영화,다큐,스포츠에 따라 미리 적재..

 

일단 DB를 배재하니.. 대여정보를 toString으로 보여주는...

 

 

헤고... 소유기반으로 클래스와 변수를 만들어서,

행위기반으로 클래스와 메소드를 연계시키면서 만들고

TDD방식으로 계속해서 만들어나가면서 만들어서 완성시켰습니다.

 

저번 자판기보다 난이도가 있네요~!

LIST

1. TDD 이모저모

https://github.com/MyoungSoo7/tddtest

 

GitHub - MyoungSoo7/tddtest: tddtest

tddtest. Contribute to MyoungSoo7/tddtest development by creating an account on GitHub.

github.com

 

 

TDD는 기능중심의 단위 테스트를 해볼수 있고, 이를 연계해 통합테스트까지 가능하다. 사실상 QA 비용이 절감되는 반면... 개발시간이 늘어나는 부분이 있다. AC2 애자일 코치 김창준님은 TDD로 개발시간은 15%증가하지만 결함은 60%감소한다고 한다. 결국, 

애자일 개발방법론에 있어서 TDD를 하면 결함률을 낮추어 유지보수성이 증가한다는 장점이 있고 Matchers, Hamcrest 등 일관된 테스트 실행환경에서 테스트를 실행하며 테스트케이스의 의미를 명확하게 보고자한다.

뭐 일부러 에러도 내고 성능테스트도 하고, 경계조건과 결과를 통한 역관계를 확인하기도 하고 좋은 부분이 많다. 그러나 역시 TDD의 한계는 동시성 문제나, 접근제한자(private)문제나 GUI문제나 의존적인 모듈테스트에 난감하고 어렵다는 것이다.

2. TDD와 업무 시나리오, 개발 가능범위의 기능

TDD를 하기전에 업무시나리오를 그려보면, 여기서 사람과 하드웨어와 소프트웨어의 역할 별로 파악하면서 개발 가능 범위를 알 수 있다.

업무시나리오와 개발가능범위 검토후, 3개의 기능을 개발하기로 하고, 클래스를 3개 만든다.

첫번째로는 음료수의 이름과 가격을 가진 Drink 클래스,

두번째로는 코인을 넣었을때 잔액확인을 할 수 있는 VendingMachine클래스

세번째로는 잔돈 코인을 반환하기 위해 사용하는 CoinSet

기능별로 클래스를 만들었다. 자판기에서 구현하며 위 3가지 기능을 테스트해보았다.

잔액확인, 잔액 거스름돈 반환 그리고 추가적으로

잔돈거스름돈에만 집중할수 있는 잔돈거스름돈 모듈... 

테스트는 성공

뭐 이런 단위테스트는 기본적인 것이다. 기능별로 만든 하위 폴더를 보면 결국 리팩토링을 거쳐 4개 클래스를 가지고 단위 테스트를 한다.

기능은 2개

 

LIST

1편에서

https://iamipro.tistory.com/502

 

스프링부트3 백엔드 개발자 되기(신선영 지음) - 1. 스프링부트 입문기초

신선영 리멤버 백엔드 개발자님의 책이다.shinsunyoung.tistory.com 운영중이고 깃헙도 그렇다.https://shinsunyoung.tistory.com/ 해어린 블로그공블로그shinsunyoung.tistory.comhttps://github.com/shinsunyoung shinsunyoung - O

iamipro.tistory.com

 

사실 Test 인 Junit 빼고는 나머지는 익히 다 안것이었다.

개발을 TDD로 해본적이 없어서 저번 항해플러스에서 많이 애먹었는데

백엔드 입문 기본에 TDD식 기본이 나온다.

@AutoConfigureMockMvc
@SpringBootTest
class BlogApiControllerTest {

MockMVC만드는 거랑 설정

@Autowired
protected MockMvc mockMvc;

@Autowired
private ObjectMapper objectMapper;

@Autowired
private WebApplicationContext webApplicationContext;

@Autowired
BlogRepository blogRepository;

@BeforeEach
public void mockMvcSetUp() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    blogRepository.deleteAll();
}

MockMvcBuilders가 WebApplicationContext 가지고 셋업까지 한다.

이런식으로 Mock을 써본 것도 처음인것 같다.

라이브러리도 Hamcrest라고 표현식을 보다 쉽게 만드는데 사용되는 Matcher 라이브러리를 새로 봤고, 그외 JUnit, SpringBootTest, AssertJ, Mockito, JSONassert, JsonPath 이런 것들은 다 한번씩 본것 같다.

 

이런 형태로 객체를 String으로 직렬화 하는 것과, 직렬화의 개념을 좀더 명확히 알게 된 것이 좋았다.

final AddArticleRequest addArticleRequest = new AddArticleRequest(title, content);

// 직렬화
final String requestBody = objectMapper.writeValueAsString(addArticleRequest); // 객체를 json 문자열로 직렬화

 

post, get으로 통신하는 것...

// 설정한 내용을 바탕으로 요청 전송
ResultActions resultActions = mockMvc.perform(post(url)
        .contentType(MediaType.APPLICATION_JSON)
        .content(requestBody));
// when
final ResultActions resultActions = mockMvc.perform(get(url, article.getId()));

 

jsonPath로 확인하는 것

resultActions.andExpect(status().isOk())
        .andExpect(jsonPath("title").value(title))
        .andExpect(jsonPath("content").value(content));

assertThat을 쓰기 까지 과정

// given
final String url = "/api/article/{id}";
final String title = "제목";
final String content = "내용";
Article article = blogRepository.save(Article.builder().title(title).content(content).build());

// when
mockMvc.perform(delete(url, article.getId())).andExpect(status().isOk());

// then
List<Article> articles = blogRepository.findAll();
assertThat(articles).isEmpty();

 

CRUD는 그냥 기본적으로 탑재해서 만들어 봤는데

이렇게 TDD형태로 테스트 하는 건 해봤지만 익숙하지 않다.

그냥 작동하는 거 보고 되면 넘어갔는데...

 

후반에 가면 좀더 고차원적인 테스트가 나타날려나...

지금 보이는건 그저 http통신이 되고 인자만 맞다면 직관적으로..

제대로 작동할 것 같은 테스트다...

 

h2 db 에서 data.sql 을 넣기 위해서 아래와 같이

defer-datasource-initialization: true 해줘야 하고

spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
    defer-datasource-initialization: true
  datasource:
    url: jdbc:h2:mem:blog
  h2:
    console:
      enabled: true

 

그외 dto를 빌더패턴으로 하던가..

 

참으로 restful하게 자원을 명사적으로 잘 알아보게 만들어놨다.

article을 복수로 하는게 맞긴 하는데 그냥 이대로도 나쁘지 않은 것 같아서..

단수를 쓰긴 했는데 신경써야 하는 생각이 문득 들기는 하는데...

궂이 구분을 해보면 한군데 복수로 써야 하는 곳이 있기는 하다.

그러나 그냥 코드 보기 편하게... pass

@PostMapping("/api/article")
public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request) {
    Article savedArticle = blogService.save(request); 
    return ResponseEntity.status(HttpStatus.CREATED).body(savedArticle);
}

@GetMapping("/api/article")
public ResponseEntity<List<ArticleResponse>> findAllArticles() {
    List<ArticleResponse> articles = blogService.findAll()
            .stream()
            .map(ArticleResponse::new)
            .toList();
    return ResponseEntity.ok().body(articles);
}

@GetMapping("/api/article/{id}")
public ResponseEntity<ArticleResponse> findArticleById(@PathVariable Long id) {
    Article article = blogService.findById(id);
    return ResponseEntity.ok().body(new ArticleResponse(article));
}

@DeleteMapping("/api/article/{id}")
public ResponseEntity<Void> deleteArticleById(@PathVariable Long id) {
    blogService.deleteById(id);
    return ResponseEntity.ok().build();
}

@PutMapping("/api/article/{id}")
public ResponseEntity<Article> updateArticle(@PathVariable Long id, @RequestBody UpdateArticleRequest request) {
    Article updatedArticle = blogService.update(id, request);
    return ResponseEntity.ok().body(updatedArticle);
}
 

Restful하게 하는것도 좀더 연습해야 겠다.

화면까지 가서 대충 타임리프 하나 만들어 놓긴 했는데..

 

 

뭐mvc야 그렇다 치고

@Getter
@Setter
class Person {
    private Long id;
    private String name;
    private int age;
    private List<String> hobbies;
}

요 dto를 아래와 같이 today 날짜랑 찍어주는 간단한 예제다.

 

 타임리프도 오랜만에 보니 새록새록 재밌다. JSTL이 생각난다. 방식이 다르지만

LIST

주문한 책이 안온다...

tdd를 여기저기 봐도...

이 상황에 적합한 최적화를 모르겠다.

a to z를 다 하기 보다

 

그냥 맨땅에 해딩일단 조금 해보려고 한다.

https://iamipro.tistory.com/461

 

데이터 모델링( 비즈니스 로직 tdd로 가기전)

설계라는것은 엄청난 것이라는 생각이 든다. 왜냐하면 창조적인 본능과 연계된 것으로 지구에서 눈에 보이는 것중에 인간만이 할 수 있기 때문이다. 여태까지는 도구를 쓴 부분만 생각했는데

iamipro.tistory.com

 

앞서 한 데이터 모델링을 기반으로

수강기능을 tdd로 해보려고 한다.

 

리포지토리 테스트는 끝났다~!

JPA참 편하다ㅎㅎ~

 

일단 tdd에서는 mock을 쓴다.

mock은 tdd의 단위테스트의 독립성을 보장해주기 위해서 한다고 한다.

 

https://cornswrold.tistory.com/369

 

Mockito 어노테이션(@Mock, @InjectMocks)

Mockito 관련 어노테이션 @RunWith(MockitoJunitRunner.class) Mockito에서 제공하는 목객체를 사용하기 하기위해 위와같은 어노테이션을 테스트클래스에 달아준다. @RunWith(MockitoJunitRunner.class) public class Test(){

cornswrold.tistory.com

@InjectMocks 는 @Mock 이붙은 객체를 @InjectMocks이 붙은 객체에 주입할수 있다고 한다.

 

즉 아래코드로 보면 EnrollmentService 에 CourseService를 주입한 것이다.

수강등록( EnrollmentService  )이라는 서비스에 앞서 수강서비스(CourseService)를 주입해서 사용하는 것이다.

 

 


import me.lms.tddstart.model.Course;
import me.lms.tddstart.model.dto.CourseDto;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class EnrollmentServiceTest {

    @Mock
    private CourseService courseService;

    @InjectMocks
    private EnrollmentService enrollmentService;

    @Test
    @DisplayName("수강신청 테스트")
    void enroll() {

        when(courseService.getCourseByCode(1)).thenReturn(new Course(1, "강의1" ,1));
        CourseDto result = enrollmentService.enroll(1, 1);

        assertNotNull(result);
        assertEquals(1, result.getCourseCode());
        assertEquals("강의1", result.getCourseName());

    }

}

그리고 등록테스트는 간단하게  

CourseService 에서 과목 하나를 가져오면서 과목객체를 생성한다.

CourseDto객체를 만들어서 수강신청 서비스에 등록한다

 

이로서 간단한 수강신청 등록 tdd 완성이다.

 

좀더 공부할 거리를 생각해보자~!

 

아래 보니 모키토를 좀더 상세히 설명해주즌 사이트가 있다.

https://doyoung.tistory.com/12

 

Junit5 에서 Mockito 사용하기

아래의 글을 작성하면서 Mockito 에 대해서 궁금해져서 공부를 해보았다. Junit5 시작 ​ What is JUnit 5? JUnit 5 Société Générale Use, Contribute and Attract: learn about Société Générale's open source strategy. junit.org j

doyoung.tistory.com

 

tdd는 처음에 실패하는 케이스를 생각해보고...

실패하지 않게끔 하는게 리팩토링하는게 궁극의 목적이라고 알고 있다...

 

일단 그렇다면 기본적인 등록까지는 

데이터 모델링을 제대로 했기에 제대로 되는거고...

 

그 다음은... dto 객체를 생성하면서

좀더 복잡한 tdd... 동시성 테스트... 같은것을 해보자~!

LIST

[10분 테코톡] 🌊 바다의 JUnit5 사용법 (youtube.com)

 

//@BeforeAll  모든 테스트 메서드 실행전에 딱한번
//@AfterALL  모든테스트 메서드 실행후에 딱한 번
//@BeforeEach  각각의 테스트 메서드 실행전에
//@AfterEach 각각의 테스트 메서드 실행후에
//@Disabled 해당 테스트 메서드를 실행하지 않음
//@RepeatedTest(10) 해당 테스트 메서드를 10번 반복 실행
//@parameterizedTest  테스트 메서드를 반복 실행하면서 다른 값을 넣어줄 수 있음
//@Nested 테스트 클래스 안에 테스트 클래스를 만들어서 테스트를 그룹화 할 수 있음

//Assertions  결과
// assertAll 모든 테스트를 실행하고 결과를 한번에 확인할 수 있음
// assertArrayEquals 배열이 같은지 확인
// assertDoesNotThrow 예외가 발생하지 않는지 확인
// assertEquals 값이 같은지 확인
// assertIterableEquals Iterable 객체가 같은지 확인
// assertNotEquals 값이 다른지 확인
// assertNotSame 객체가 다른지 확인
// assertNull 객체가 null인지 확인
// assertSame 객체가 같은지 확인
// assertThat 조건에 맞는지 확인
// assertTrue 조건이 참인지 확인
// assertFalse 조건이 거짓인지 확인
// assertThrows 예외가 발생하는지 확인
// assertTimeout 시간안에 실행이 완료되는지 확인
// assertTimeoutPreemptively 시간안에 실행이 완료되는지 확인하고 완료되지 않으면 중단

//Assumption 전제문 가정
//assumeFalse 조건이 거짓이면 테스트를 중단
//assumeTrue 조건이 참이면 테스트를 중단
//assumingThat 조건이 참이면 테스트를 실행하고 거짓이면 테스트를 중단

테스트 코드 적용하기 (JUnit, TDD) [ 스프링 부트 (Spring Boot) ] (youtube.com)

 

단위테스트는 코드가 의도적으로 동작하는지 고려 하여 코드의 안정성을 높일수 있다.

JUnit5 는 Jupiter , Platform 로 구성된다.

//@SpringBootTest : 모든 Bean을 로드하여 테스트
//@ExtendWith(SpringExtension.class) : JUnit5에서 Spring을 사용할 수 있도록 지원
//MockBean : 테스트용 Bean을 등록
//@WebMvcTest : {Class명.class}에 작성된 클래스만 실제로 로드하여 테스트 (컨트롤러와 연관된 Bean이 모두 로드됨)
//@AutoConfigureMockMvc : MockMvc를 자동으로 설정해줌

 

 

 

 

TDD를 사용하는 것이 좋을까? (velog.io)

 

TDD를 사용하는 것이 좋을까?

Test-Driven Development : 테스트가 코드 작성을 주도하는 개발방식TDD란 소프트웨어 개발 방법론 중 하나로, 테스트가 코드 작성을 주도하는 개발방식 입니다. 개발 → 테스트 방식이 아닌, 테스트 →

velog.io

[10분 테코톡] 더즈, 티키의 Classic TDD VS Mockist TDD (youtube.com)

 

 

 

뱅크샐러드의 특별한 스펙, '테크 스펙' | 뱅크샐러드 (banksalad.com)

 

뱅크샐러드의 특별한 스펙, '테크 스펙' | 뱅크샐러드

blog.banksalad.com

 

고객의 불편함이 제품으로 되기까지 - Airbridge API 팀의 개발 프로세스 (ab180.co)

 

고객의 불편함이 제품으로 되기까지 - Airbridge API 팀의 개발 프로세스

AB180에서 API 개발팀이 프로덕트를 만드는 방법을 공유합니다.

engineering.ab180.co

 

LIST

(1) [10분 테코톡] 말랑의 스프링 이벤트 - YouTube

 

이벤트를 통해 의존성 개선하기~!

인터페이스에서 이벤트 사용시~!

 

이벤트는 반환 타입이 필요한 경우 사용이 불가능하다.

로직의 흐름이 명확하지 않다.(상황에 따라 장/단이 나타난다.)

 

 

[10분 테코톡] 후니의 스프링 트랜잭션 - YouTube

 

JDBC api는 의존성, 복잡성등의 문제로 스프링 트랜잭션을 이용한다.

동기화, 추상화(Connection, EntityManager, Session )

, 선언적인 기능(@Transactional)으로 나타낸다.(트랜잭션 속성- 하위)

 

isolation~!

(1) [10분 테코톡] 🎃 손너잘의 테스트 코드 최적화 여행기 - YouTube

 

 

 

 

 

read DB와 update , delete DB 따로 구성

 

 

 

 

(1) [10분 테코톡] 🌊 바다의 JUnit5 사용법 - YouTube

 

 

@Disabled - 테스트를 하고 싶지 않음

@DisplaqyName - 어떤 테스트인지 표현

@RepeatedTest - 반복테스트

@PrameterizedTest - 매개변수를 대입해가며 반복 ㅇ실행

@Nested  - 내부클래스 정의,

예외발생을 확인하는 테스트(Assertions)

 

 

(1) [10분 테코톡] 😼 피카의 TDD와 단위테스트 - YouTube

프로그램을 작성하기 전에 테스트~!

TDD(Test-Driven Development) ~!

 

설계 - 테스트코드 - 개발

 

변화에 대한 두려움을 줄여준다, 디버깅 시간을 줄여주고, 동작하는 문서역할

 

TDD를 하면 요구사항에 맞춰 개발하여 오버엔지니어링을 막아준다.

TDD는 설계에 대한 피드백이 빠르다.

좋은단위 테스트 FIRST~!

 

(1) [10분 테코톡] 더즈, 티키의 Classic TDD VS Mockist TDD - YouTube

 

Classic TDD(Fixture :재사용가능) VS Mockist TDD(MOCK :1회로 코드 간결)

 

 

 

 

 

 

 

 

 

 

(1) [10분 테코톡] 제이의 단위 테스트 - YouTube

단위테스트 ~!

given (준비, 값) - when (기능 실행) - then( 결과 검증)

@ParameterizedTest ~! ( n개의 테스트 메서드)

LIST

Test Driven Devolpment(테스트 하면서 개발한다.)

단위테스트 할때 Junit은 유용한 방법이 된다.

모키토 프레임워크로 가짜 객체 mock을 만들어서 테스트 하기도 좋다

 

단위 테스트가 잘되면 통합테스트를 하고 거기서도 통화하면 제품을 내놓게 된다.

LIST

+ Recent posts