본문 바로가기

STUDY/국비과정

[JAVA 웹 개발 공부] 국비지원 67일차 - Maven 외부 라이브러리 추가, JUnit 어노테이션, JUnit DB Test

Maven 외부 라이브러리 추가하기

 

외부 라이브러리를 사용하기 위해서 pom.xml에 <dependencies> 태그 안에 dependency를 추가한다.

<dependencies>는 프로젝트가 의존하는 라이브러리들의 정보를 담는 태그이다.

 

*자주 사용하는 라이브러리

라이브러리 기능 dependency
Apache Commons DBCP 
» 2.9.0
DataSource 구현체 <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.9.0</version>
</dependency>
MySQL Connector Java 
» 8.0.32
jdbc mysql 드라이버 <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.32</version>
</dependency>
SLF4J API Module 
» 
2.0.6
logger 인터페이스 <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.6</version>
</dependency>
Logback Classic Module
» 1.3.5
logger slf4j 구현체 <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.4.5</version>
</dependency>
Jackson Databind 
» 2.14.2
json parser <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.2</version>
 </dependency>
JUnit 
» 4.13.2
테스트 자동화 <!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

 

 

JUnit

 

1. JUnit

*Java에서 독립된 단위테스트(Unit Test)를 지원해주는 프레임워크

*테스트 결과는 Test클래스로 개발자에게 테스트 방법 및 클래스의 History를 공유 가능
*단정(assert) 메서드로 테스트 케이스의 수행 결과를 판별
*어노테이션으로 간결하게 지원(JUnit4부터)

 

 

2. JUnit 어노테이션

 

어노테이션 설명
@BeforeClass @BeforeClass 어노테이션은 @Test 메소드보다 먼저 한번만 수행되어야 할 경우에 사용하면 된다.
@Before @Before가 선언된 메서드는 @Test 메서드가 실행되기 전에 반드시 실행되어진다.
@Test메서드에서 공통으로 사용하는 코드를 @Before 메서드에 선언하여 사용하면 된다.
@Test @Test가 선언된 메서드는 테스트를 수행하는 메소드가 된다.
JUnit은 각각의 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 원칙으로 @Test마다 객체를 생성한다.
@After @After가 선언된 메서드는 @Test 메소드가 실행된 후 실행된다.
@AfterClass @AfterClass 어노테이션은 @Test 메소드 보다 나중에 한번만 수행되어야 할 경우에 사용하면 된다.
@Ignore @Ignore가 선언된 메서드는 테스트를 실행하지 않게 한다.

 

3. JUnit 메소드

메소드 설명
assertEquals(a,b);   객체 a,b의 값이 일치함을 확인한다.
 assertArrayEquals(a,b);  배열 a,b의 값이 일치함을 확인한다.
 assertSame(a,b);  객체 a,b가 같은 객체임을 확인한다.
 두 객체의 레퍼런스가 동일한가를 확인한다.
 assertTrue(a);  조건 a가 참인가 확인한다.
 assertNotNull(a);  객체 a가 null이 아님을 확인한다.

 

4. 예제

package bookapp;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

public class TestPractice {
	
	@Test
	public void testSum() {
		int a = 10;
		int b = 20;
		
		int sum = a + b;
		
		assertEquals(30, sum); // '값'이 같으면 통과
	}

	@Test
	public void testNotEqual() {
		int a = 10;
		int b = 20;
		
		int sum = a + b;
		
		assertNotEquals(30, sum); // '값'이 다르면 통과
	}
	
	@Test
	public void testSame() {
		Object a = new Object();
		Object b = new Object();
		
		assertSame(a, b); // '같은참조'면 통과
	}
	
	@Test
	public void testNotSame() {
		Object a = new Object();
		Object b = new Object();
		
		assertNotSame(a, b); // '같은참조'가 아니면 통과
	}
	
	@Test
	public void testBoolean() {
		boolean result = true;
		assertTrue(result); // boolean 자료 값이 'true'면 통과
	}
}

 

 

JUnit DB Test

 

*Book.java

package bookapp;

public class Book {
	private int no;
	private String title;
	private int price;
	
	public Book() {}

	public Book(int no, String title, int price) {
		super();
		this.no = no;
		this.title = title;
		this.price = price;
	}

	public int getNo() {
		return no;
	}

	public void setNo(int no) {
		this.no = no;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}

	@Override
	public String toString() {
		return "Book [no=" + no + ", title=" + title + ", price=" + price + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + no;
		result = prime * result + price;
		result = prime * result + ((title == null) ? 0 : title.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Book other = (Book) obj;
		if (no != other.no)
			return false;
		if (price != other.price)
			return false;
		if (title == null) {
			if (other.title != null)
				return false;
		} else if (!title.equals(other.title))
			return false;
		return true;
	}
}

 

*BookService.java

package bookapp;

import java.util.List;

public interface BookService {
	Book create(Book book);
	List<Book> read();
	Book update(Book book);
	Book delete(int no);
}

 

*BookServiceImpl.java

package bookapp;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import bookapp.dbutil.ConnectionProvider;

public class BookServiceImpl implements BookService {
	private BookDAO dao;

	public BookServiceImpl(BookDAO dao) {
		super();
		this.dao = dao;
	}

	@Override
	public Book create(Book book) {
		Connection conn = null;
		try {
			conn = ConnectionProvider.getConnection();
			conn.setAutoCommit(false);
			int key = dao.insert(conn, book);
			conn.commit();
			
			book.setNo(key);
			return book;
		} catch (RuntimeException | SQLException e) {
			if (conn != null) {
				try {
					conn.rollback();
				} catch (SQLException e1) {
					e1.printStackTrace();
				}
			}
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	@Override
	public List<Book> read() {
		Connection conn = null;
		try {
			conn = ConnectionProvider.getConnection();
			return dao.select(conn);
		} catch (RuntimeException | SQLException e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	@Override
	public Book update(Book book) {
		Connection conn = null;
		try {
			conn = ConnectionProvider.getConnection();
			conn.setAutoCommit(false);
			dao.update(conn, book);
			conn.commit();
			
			return book;
		} catch (RuntimeException | SQLException e) {
			if (conn != null) {
				try {
					conn.rollback();
				} catch (SQLException e1) {
					e1.printStackTrace();
				}
			}
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	@Override
	public Book delete(int no) {
		Connection conn = null;
		try {
			conn = ConnectionProvider.getConnection();
			conn.setAutoCommit(false);
			Book book = dao.selectByNo(conn, no);
			dao.delete(conn, no);
			conn.commit();
			
			return book;
		} catch (RuntimeException | SQLException e) {
			if (conn != null) {
				try {
					conn.rollback();
				} catch (SQLException e1) {
					e1.printStackTrace();
				}
			}
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}
}

 

*BookDAO.java

package bookapp;

import java.sql.Connection;
import java.util.List;

public interface BookDAO {
	int insert(Connection conn, Book book);
	List<Book> select(Connection conn);
	Book selectByNo(Connection conn, int no);
	int update(Connection conn, Book book);
	int delete(Connection conn, int no);
}

 

*BookDAOImpl.java

package bookapp;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import bookapp.dbutil.ConnectionProvider;

public class BookDAOImpl implements BookDAO {
	@Override
	public int insert(Connection conn, Book book) {
		String sql = "INSERT INTO book (title, price) VALUES (?, ?)";
		try (PreparedStatement stmt = conn.prepareStatement
				(sql, Statement.RETURN_GENERATED_KEYS )) {
			
			stmt.setString(1, book.getTitle());
			stmt.setInt(2, book.getPrice());
			int result = stmt.executeUpdate();
			if (result == 1) {
				try (ResultSet rs = stmt.getGeneratedKeys()) {
					rs.next();
					return rs.getInt(1);
				} 
			} else {
				throw new RuntimeException("행이 생성되지 않았습니다. ");
			}
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException("추가 작업 중 예외 발생", e);
		}
	}
    
	@Override
	public List<Book> select(Connection conn) {
		String sql = "SELECT * FROM book";
		try (PreparedStatement stmt = conn.prepareStatement(sql);
				ResultSet rs = stmt.executeQuery()) {
			List<Book> list = new ArrayList<>();
			while (rs.next()) {
				list.add(resultMapping(rs));
			}
			return list;
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException("조회 작업 중 예외 발생", e);
		}
	}

	private Book resultMapping(ResultSet rs) throws SQLException {
		Book book = new Book();
		book.setNo(rs.getInt("no"));
		book.setTitle(rs.getString("title"));
		book.setPrice(rs.getInt("price"));
		return book;
	}

	@Override
	public Book selectByNo(Connection conn, int no) {
		String sql = "SELECT * FROM book WHERE no = ?";
		try (PreparedStatement stmt = conn.prepareStatement(sql)) {
			stmt.setInt(1, no);
			
			try (ResultSet rs = stmt.executeQuery()) {
				if (rs.next()) {
					return resultMapping(rs);
				}
			}
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException("조회 작업 중 예외 발생", e);
		}
		return null;
	}

	@Override
	public int update(Connection conn, Book book) {
		String sql = "UPDATE book SET title = ?, price = ? WHERE no = ?";
		try (PreparedStatement stmt = conn.prepareStatement(sql)) {
			stmt.setString(1, book.getTitle());
			stmt.setInt(2, book.getPrice());
			stmt.setInt(3, book.getNo());
			
			return stmt.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException("수정 작업 중 예외 발생", e);
		}
	}

	@Override
	public int delete(Connection conn, int no) {
		String sql = "DELETE FROM book WHERE no = ?";
		try (PreparedStatement stmt = conn.prepareStatement(sql)) {
			stmt.setInt(1, no);
			
			return stmt.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException("삭제 작업 중 예외 발생", e);
		}
	}
}

 

*pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>bookapp</groupId>
	<artifactId>bookapp</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<dependencies>
		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.32</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
			<version>2.9.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>2.0.6</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.4.5</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.14.2</version>
		</dependency>
	</dependencies>

	<build>
		<sourceDirectory>src</sourceDirectory>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>3.2.3</version>
				<configuration>
					<warSourceDirectory>webapp</warSourceDirectory>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

 

*ConnectionProvider.java

package bookapp.dbutil;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;

public class ConnectionProvider {
	private static DataSource dataSource;
	static {
		BasicDataSource ds = new BasicDataSource();
		ds.setUrl("jdbc:mysql://localhost:3306/my_db");
		ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
		ds.setUsername("root");
		ds.setPassword("root");
		dataSource = ds;
	}
	public static Connection getConnection() throws SQLException {
		return dataSource.getConnection();
	}
}

 


*데이터베이스 작업 구현 클래스 테스트하기(테스트 목적의 폴더 구성, 배포할때 제외하기)

(1) 프로젝트 우클릭 → Properties → java Build Path → Source 탭 → Add Folder... → create New Folder - Test 로 이름 짓고 완료

(2) Contains test sources 더블 클릭 → Yes로 변경

(3) Output folder 클릭 → target 폴더 안 test폴더(test-classes)로 변경

 

*TestBookDAO.java

package bookapp;

import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import bookapp.dbutil.ConnectionProvider;

public class TestBookDAO {
	private static BookDAO dao;
	private Connection conn;
	
	@BeforeClass
	public static void setup() {
		dao = new BookDAOImpl();
	}
	
	@Before
	public void makeConn() throws SQLException {
		conn = ConnectionProvider.getConnection();
		conn.setAutoCommit(false);
	}
	
	@Test
	public void daoInit() {
		// DAO 객체 생성 테스트
		BookDAO dao = new BookDAOImpl();
		System.out.println("생성 결과" + (dao != null));
	}

	@Test
	public void daoInsert() {
		// insert 메소드 테스트...
		Book book = new Book();
		book.setTitle("테스트용책이름");
		book.setPrice(50000);

		BookDAO dao = new BookDAOImpl();
		int result = dao.insert(conn, book);
		assertNotEquals(0, result);
	}
	
	@Test
	public void daoSelect() {
		List<Book> list = dao.select(conn);
		
		assertNotNull(list);
	}
	
	@Test
	public void daoUpdate() {
		int result = dao.update(conn, new Book(2, "이번책 바꾸기", 3000));
		
		assertNotEquals(0, result);
	}
	
	@Test
	public void daoDelete() {
		int result = dao.delete(conn, 2);
		
		assertNotEquals(0, result);
	}
	
	@After
	public void rollbackAndClose() throws SQLException {
		conn.rollback();
		conn.close();
	}
}

 

*TestBookService.java

package bookapp;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;

public class TestBookService {
	@Test
	public void create() {
		BookService service = new BookServiceImpl(new BookDAOImpl());
		
		Book book = new Book();
		book.setTitle("서비스 객체 테스트");
		book.setPrice(30000);
		
		Book result = service.create(book);
		assertNotNull(result);
	}
}