과제
과제0.JUnit5 학습하세요
- 인텔리J, 이클립스, VScode에서 Junit5로 테스트 코드 작성하는 방법에 익숙해질 것
- 이미 JUnit 알고 계신분들은 다른 것 아무거나!
- 더 자바, 테스트 강의도 있으니 참고하세요~
JUnit5
- JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform = 테스트를 실행해 주는 런처 제공, TestEngine API 정의
- JUnit Jupiter = TestEngine API 구현체로 JUnit5 제공, 테스트를 작성하고 확장을 하기 위한 새로운 프로그래밍 모델과 확장 모델의 조합
- JUnit Vintage = 하위 호환성을 위해 JUnit4와 JUnit3을 지원하는 TestEngine 구현체
- Java8부터 지원하며 이전 버전으로 작성된 코드여도 컴파일이 정상적으로 지원됨
JUnit Jupiter
애노테이션 |
설명 |
@TestFactory |
동적 테스트 지원 |
@DisplayName |
테스트 클래스, 테스트 메서드에 대해 사용자 정의하는 이름 노출, default는 메서드 이름임 |
@DisplayNameGeneration |
클래스에 해당 애노테이션을 붙이면 @Test 메소드 이름에 언더바(_)로 표시한 모든 부분은 공백으로 처리됨 |
@Nested |
클래스가 중첩된 비정적 테스트 클래스임을 나타냄, 클래스안에 Nested 테스트 클래스를 작성할 때 사용되며, static이 아닌 중첩클래스, 즉 Inner 클래스여야함 |
@Tag |
테스트를 필터링하기 위한 태그 선언, 클래스 또는 메서드 레벨에서 사용함 |
@ExtendWith |
custom extensions 등록, 상속 가능 |
@BeforeEach |
각각의 테스트 메서드 실행 이전에 매번 실행 (JUnit4: @Before) |
@AfterEach |
각각 테스트 메소드 실행 이후에 실행 (JUnit4: @After) |
@BeforeAll |
현재 클래스의 모든 테스트 메서드 실행 이전에 한 번만 실행 (JUnit4: @BeforeClass) |
@AfterAll |
현재 클래스의 모든 테스트 메소드 실행 이후에 한 번만 실행 ( (JUnit4: @AfterClass) |
@Disable |
테스트 클래스, 테스트 메서드 비활성화 (JUnit4: @Ignore) |
@Timeout |
주어진 시간안에 테스트가 끝나지 않으면 실패 |
@RegisterExtension |
필드를 통해 extension을 등록함, 필드는 private이 아니라면 상속됨 |
@TempDir |
필드 주입이나 파라미터 주입을 통해 임시적인 디렉토리를 제공할 때 사용 |
Assertions
- JUnit Jupiter는 JUnit4에서 제공하는 Assertions 기능과 더불어 Java8의 lambda를 지원하는 Assertions가 추가됨
- JUnit Jupiter assertions은 org.junit.jupiter.api.- Assertions의 static으로 이동됨
- assertAll()을 사용하여 Assertions를 그룹화하여 실행 가능
Assumptions
- Assumptions는 특정 조건이 충족될 때만 테스트를 실행함
- 특정 조건을 만족하지 않으면 테스트를 실행하지 않음
- 일반적으로 테스트가 제대로 실행하기 위한 필요한 외부조건에 사용됨(dev profile에서만 테스트)
- JUnit Jupiter assumptions은 org.junit.jupiter.api.Assumptions의 static으로 이동됨
- assumeTrue(), assumeFalse(), assumingThat()
Exception Testing
- assertThrows()
- assertEquals()
Test Suites
- 여러 테스트 클래스를 모아서 테스트 가능
- @SelectPackages, @SelectClasses
Dynamic Tests
- 런타임에 생성된 테스트 케이스를 선언하고 실행할 수 있는 JUnit 5의 동적 테스트 기능
- 런타임에서 테스트 케이스를 동적으로 정의 가능
Parameter를 사용한 반복 테스트
- @ParameterizedTest로 파라미터에 따라 반복적인 테스트 수행
- @ValueSource로 테스트에 사용할 변수 지정
중첩된 계층구조 테스트
- 중첩된 계층 구조를 가진 테스트 메서드 작성 가능
- @Nested
백기선님 팁
- JUnit4와 JUnit5가 섞여서 사용되지 않도록 조심해야 함
- JUnit5에서 public 접근 제어자는 없어도 됨
- @DisplayName 에는 메서드 이름 대신 다른 이름을 넣는 것이 좋음
- @TestInstance(TestInstance.Lifecycle.PER_CLASS)를 사용하여 테스트를 stateful하게 가능
- @Test는 우리가 정의한 순서대로 실행되지 않으므로 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)과 @Order를 사용하여 순서를 줄 수 있음
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class StatefulTest {
private int number;
@Test @Order(1)
void test1(){
System.out.println(number++);//0
}
@Test @Order(2)
void test2(){
System.out.println(number++);//1
}
@Test @Order(3)
void test3(){
System.out.println(number);//2
}
}
과제1.live-study 대시보드를 만드는 코드를 작성하세요
- 깃헙 이슈 1번부터 18번까지 댓글을 순회하며 댓글을 남긴 사용자를 체크할 것
- 참여율을 계산하세요. 총 18회 중에 몇 %를 참여했는지 소수점 두 자리까지 보여줄 것
- Github 자바 라이브러리를 사용하면 편리합니다
- 깃헙 API를 익명으로 호출하는데 제한이 있기 때문에 본인의 깃헙 프로젝트에 이슈를 만들고 테스트를 하시면 더 자주 테스트할 수 있습니다.
public class DashBoard {
private Map<String, Integer> result = new HashMap<>();
private GitHub gitHub;
public DashBoard(GitHub gitHub) {
this.gitHub = gitHub;
}
public static void main(String[] args) throws IOException {
DashBoard dashBoard = new DashBoard(new GitHubBuilder().withPassword(GithubAuth.USER_ID, GithubAuth.PASSWORD).build());
Map<String, Integer> result = dashBoard.getResult("limyohwan/studyhalle");
for (String userId : result.keySet()) {
System.out.println(userId + " : " + result.get(userId) + "회");
}
}
public Map<String, Integer> getResult(String repositoryName) throws IOException {
GHRepository studyhalle = getRepository(repositoryName);
List<GHIssue> issues = studyhalle.getIssues(GHIssueState.ALL);
calculateDashboardResult(issues);
return this.result;
}
private GHRepository getRepository(String repositoryName) throws IOException {
return this.gitHub.getRepository(repositoryName);//"limyohwan/studyhalle"
}
private void calculateDashboardResult(List<GHIssue> issues) throws IOException {
for (GHIssue issue : issues) {
calculateDashboardResultPerIssue(issue);
}
}
private void calculateDashboardResultPerIssue(GHIssue issue) throws IOException {
addCountPerParticipant(getParticipants(issue.getComments()));
}
private Set<String> getParticipants(List<GHIssueComment> comments) throws IOException {
Set<String> participants = new HashSet<>();
for (GHIssueComment comment : comments) {
GHUser user = comment.getUser();
participants.add(user.getName());
}
return participants;
}
private void addCountPerParticipant(Set<String> participants) {
for (String participant : participants) {
this.result.merge(participant, 1, Integer::sum);
}
}
}
과제2.LinkedList를 구현하세요
- LinkedList에 대해 공부하세요
- 정수를 저장하는 ListNode 클래스를 구현하세요
- ListNode add(ListNode head, ListNode nodeToAdd, int position)를 구현하세요
- ListNode remove(ListNode head, int positionToRemove)를 구현하세요
- boolean contains(ListNode head, ListNode nodeToCheck)를 구현하세요
LinkedList
- ArrayList 클래스가 배열을 이용하여 요소를 저장함으로써 발생하는 단점을 극복하기 위해 고안됨
- JDK 1.2부터 제공된 LinkedList 클래스는 내부적으로 연결 리스트를 이용하여 요소를 저장
- 저장된 요소가 비순차적으로 분포되며, 이러한 요소들 사이를 링크(link)로 연결하여 구성
- ArrayList와 LinkedList의 차이는 사용 방법이 아닌, 내부적으로 요소를 저장하는 방법임
단일 연결 리스트(singly linked list)
- 요소를 가리키는 참조만을 가지는 연결 리스트
- 요소의 저장과 삭제 작업이 다음 요소를 가리키는 참조만 변경하면 되므로 빠르게 처리됨
- 현재 요소에서 이전 요소로 접근하기가 매우 어려움
이중 연결 리스트(doubly linked list)
- LinkedList 클래스도 위와 같은 이중 연결 리스트를 내부적으로 구현한 것
- 이전 요소를 가리키는 참조도 가짐
ListNode
public class ListNode {
public int val;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
LinkedList
public class LinkedList {
private int size = 0;
public int getSize() {
return size;
}
ListNode add(ListNode head, ListNode nodeToAdd, int position) {
ListNode node = head;
if(size < position){
return null;
}
if (head == null) {
node = nodeToAdd;
head = node;
} else {
for (int i = 1; i < position; i++) {
node = node.next;
}
nodeToAdd.next = node.next;
node.next = nodeToAdd;
}
size++;
return head;
}
ListNode remove(ListNode head, int positionToRemove){
ListNode node = head;
if(size <= positionToRemove || head == null){
return null;
}
if(positionToRemove == 0){
node = node.next;
head = node;
} else{
for(int i = 1; i < positionToRemove; i++){
node = node.next;
}
node.next = node.next.next;
}
size--;
return head;
}
boolean contains(ListNode head, ListNode nodeToCheck){
ListNode node = head;
boolean check = false;
if(head == null){
return false;
}
do{
if(node.val == nodeToCheck.val && node.next == nodeToCheck.next){
check = true;
break;
}
node = node.next;
}while(node != null);
return check;
}
}
과제3.Stack을 구현하세요
- int 배열을 사용해서 정수를 저장하는 Stack을 구현하세요
- void push(int data)를 구현하세요
- int pop()을 구현하세요
public class Stack {
private Integer[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
this.elements = new Integer[DEFAULT_INITIAL_CAPACITY];
}
public void push(int element) {
if(elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
elements[size++] = element;
}
public int pop() {
if(size == 0) {
throw new EmptyStackException();
}
int result = elements[--size];
elements[size] = null;
return result;
}
public int getSize(){
return this.size;
}
}
과제4.앞서 만든 ListNode를 사용해서 Stack을 구현하세요
- ListNode head를 가지고 있는 ListNodeStack 클래스를 구현하세요
- void push(int data)를 구현하세요
- int pop()을 구현하세요
public class ListNodeStack {
private ListNode head;
private int size = 0;
public void push(int element) {
ListNode nodeToAdd = new ListNode(element);
if(head == null) {
head = nodeToAdd;
size = 1;
return;
}
ListNode temp = head;
for(int i = 1; i < size; i++){
temp = temp.next;
}
temp.next = nodeToAdd;
size++;
}
public int pop() {
if(size == 0) {
throw new EmptyStackException();
}
ListNode temp = head;
for(int i = 0; i < size-1; i++){
temp = temp.next;
}
int result = temp.val;
temp = null;
size--;
return result;
}
public int getSize(){
return this.size;
}
}
(optional)과제5.Queue를 구현하세요
- 배열을 사용해서 한번
- ListNode를 사용해서 한번
public class Queue {
private Integer[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Queue() {
this.elements = new Integer[DEFAULT_INITIAL_CAPACITY];
}
public void add(int element) {
if(elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
elements[size++] = element;
}
public int remove() {
if(size == 0) {
throw new EmptyStackException();
}
int result = elements[0];
for(int i = 1; i < size; i++){
elements[i-1] = elements[i];
}
elements[--size] = null;
return result;
}
public int element() {
if(size == 0) {
throw new EmptyStackException();
}
return elements[0];
}
public int getSize(){
return this.size;
}
}
public class ListNodeQueue {
private ListNode head;
private int size = 0;
public void add(int element) {
ListNode nodeToAdd = new ListNode(element);
if(head == null) {
head = nodeToAdd;
size = 1;
return;
}
ListNode temp = head;
for(int i = 1; i < size; i++){
temp = temp.next;
}
temp.next = nodeToAdd;
size++;
}
public int remove() {
if(size == 0) {
throw new EmptyStackException();
}
ListNode temp = head;
int result = temp.val;
if(temp.next != null){
head = temp.next;
}else{
head = null;
}
size--;
return result;
}
public int element() {
if(size == 0) {
throw new EmptyStackException();
}
return head.val;
}
public int getSize(){
return this.size;
}
}
public class QueueSample {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.add(1); // 예외 던짐
queue.poll(); // null
queue.remove(); //예외 던짐
queue.peek(); // null
queue.element(); //예외 던짐
}
}