관리 메뉴

개발그래머

[자바스터디 4주차] 과제 본문

Java

[자바스터디 4주차] 과제

임요환 2023. 5. 27. 23:16

과제

과제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(); //예외 던짐
    }
}