[TDD/방법론] TDD 예제(1) - 도서관리 시스템 Junit5로 테스트해보기

nana·2024년 9월 17일

방법론

목록 보기
7/9
post-thumbnail

주저리주저리

사전 스터디를 하면서 가장 시간을 많이 빼앗은 부분은 세팅이었다.. JUnit이라는 것도 처음들어보고 Java도 Intellij도 처음이라 어디서 어떻게 시작해야하는지 몰라서 정말 많이 헤맨듯 하다 ㅠㅠ
community version이라 예제 소스를 보아도 나는 설정해야하는 것들이 한가득.. 이지만!
어쩌겠어, 돈 쓰던가 악으로 깡으로 버텨서 해내던가 해야지.
그리고 결국 해내고야 만... 세팅 ㅠㅠ

  • java버전 세팅 오류(이걸로 3일 날린듯)
  • Assertion import 문제
  • 파일 구조 문제

예제 코드

예제코드는 chatGPT에게 java스펙과 초보자임을 설명한 후 테스트 코드와 테스트 해야하는 부분들을 알려달라고 했다.

⭐ 주요 기능

  • 책 등록: 도서관에 새로운 책을 등록.
  • 책 대출: 도서관에서 특정 책을 대출. 대출 시 책의 재고가 줄어듦.
  • 책 반납: 책을 반납하면 재고가 늘어남.
  • 책 재고 확인: 책의 현재 재고를 확인.
  • 책 검색: 책 제목으로 검색.

Book.java


public class Book {

    private String title;
    private String author;
    private int stock;  // 재고

    public Book(String title, String author, int stock) {
        this.title = title;
        this.author = author;
        this.stock = stock;
    }

    // Getter, Setter
    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public int getStock() {
        return stock;
    }

    public void setStock(int stock) {
        this.stock = stock;
    }

    // 책을 대출할 때 재고 감소
    public void borrow() {
        if (stock <= 0) {
            throw new IllegalStateException("No stock available");
        }
        stock--;
    }

    // 책을 반납할 때 재고 증가
    public void returnBook() {
        stock++;
    }
}

Library.java



import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class Library {

    private List<Book> books;

    public Library() {
        this.books = new ArrayList<>();
    }

    // 책을 도서관에 등록
    public void addBook(Book book) {
        books.add(book);
    }

    // 제목으로 책을 검색
    public Optional<Book> findBookByTitle(String title) {
        return books.stream()
                .filter(book -> book.getTitle().equalsIgnoreCase(title))
                .findFirst();
    }

    // 책을 대출 (제목으로)
    public void borrowBook(String title) {
        Book book = findBookByTitle(title)
                .orElseThrow(() -> new IllegalArgumentException("Book not found"));
        book.borrow();
    }

    // 책을 반납 (제목으로)
    public void returnBook(String title) {
        Book book = findBookByTitle(title)
                .orElseThrow(() -> new IllegalArgumentException("Book not found"));
        book.returnBook();
    }

    // 책 재고 확인
    public int getStock(String title) {
        return findBookByTitle(title)
                .map(Book::getStock)
                .orElseThrow(() -> new IllegalArgumentException("Book not found"));
    }
}

테스트코드

BookTest.java


import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class BookTest {


    /***
     * 1. 책을 빌림 > 재고 감소
     * 2. 책을 빌리는데 재고가 없음 > IllegalStateException
     * 3. 책을 반납함 > 재고 증가
     */
    @Test
    void testBorrowBook(){
        Book book = new Book("Effective Java", "Joshua Block",5);
        book.borrow();
        Assertions.assertEquals(4, book.getStock());
    }

    @Test
    void testBorrowBookNoStock(){
        Book book = new Book("Junit 5","today",0);
        book.borrow();
        Assertions.assertThrows(IllegalStateException.class,book::borrow);
    }

    @Test
    void testReturnBook(){
        Book book = new Book("TestBook","nana",6);
        book.returnBook();
        Assertions.assertEquals(6, book.getStock());
    }

import static org.junit.jupiter.api.Assertions.;

assertTrueassertEquals와 같은 AssertionAPI는 org.junit.jupiter.api.Assertions에 정의되어있는
static메서드*이므로 선언시에 꼭 import static로 정의해줘야한다.

testBorrowBook()

book 생성자로 (책 제목,저자, 재고)를 생성하여 borrow() 빌리면
Assertions.assertEquals 4개가 남을 것임을 의미.


stock을 4로 설정했더니 기댓값은 4이나 실제 3이 나왔다는 메시지가 뜬다.

testBorrowBookNoStock()



재고가 없을 때 정상적으로 IllegalStateException을 호출함을 알 수 있다.

testReturnBook()



6개에서 책을 한권 반납하면 7개가 되어야한다며 테스트 실패를 반환하고있다.

LibraryTest.java


import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class LibraryTest {

    private Library library;

    @BeforeEach
    void setUp() {
        library = new Library();
        Book book1 = new Book("Effective Java", "author1",7);
        Book book2 = new Book("Clean code", "author2",3);

        library.addBook(book1);
        library.addBook(book2);
    }

    @AfterEach
    void tearDown() {
    }

    @Test
    void testfindBookByTitle() {
        Book book = library.findBookByTitle("Effective java2")
                .orElseThrow(()-> new AssertionError("Book not found"));
        Assertions.assertEquals("Effective Java",book.getTitle());
    }

    @Test
    void borrowBook() {
    }

    @Test
    void returnBook() {
    }

    @Test
    void getStock() {
    }
}

setUp()

@BeforeEach어노테이션을 이용해 사전 설정값을 준다.

testfindBookByTitle()

testBorrowBook() & testReturnBook()

이번엔 Clean code라는 책을 빌리고, 반납해보았다.
빌렸으니 2개, 빌렸다 반납했으니 3개로 테스트가 통과하였다.

testGetStockForNonExistentBook() & testBorrowNonExistentBook() & testReturnNonExistentBook()


사전에 재고로 등록하지 않은 "Nonexistent Book"을 빌리고, 반납하고 재고를 확인하였다.



모두다 정상적인 예외코드를 반환함을 알 수 있다.

✔️JUnit 기본 assert 메서드

형태설명
void assertTrue(boolean condition)condition이 true이면 테스트에 성공한다.
void assertEquals(Object expected, Object actual)expected 와 actual 의 값이 같으면 테스트에 성공한다.
void assertSame(Object expected, Object actual)expected와 actual이 같은 레퍼런스이면 테스트에 성공한다.
void assertNull(Object actual)actual이 null이면 테스트에 성공한다.
void assertAll(Executable... executables)인자로 들어온 모든 executable들이 예외를 발생시키지 않으면 테스트에 성공한다.
void assertThrows(Class expectedType, Executable executable)인자로 들어온 executable이 expectedType 타입의 예외를 발생시키는지 확인한다.
void assertTimeout(Duration timeout, Executable executable)executable이 timeout 안에 수행을 완료하는지 확인한다.
void assertTimeoutPreemptively(Duration timeout, Executable executable)executable이 timeout 안에 수행을 완료하는지 확인한다.

출처 : JUnit 사용법

profile
BackEnd Developer, 기록의 힘을 믿습니다.

0개의 댓글