2020. 12. 25.

6주차 과제: 상속

https://github.com/whiteship/live-study/issues/6

목표

자바의 상속에 대해 학습하세요.

학습할 것 (필수)

  • 자바 상속의 특징
  • super 키워드
  • 메소드 오버라이딩
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
  • 추상 클래스
  • final 키워드
  • Object 클래스


자바 상속의 특징

public class Vehicle {
    protected String brand = "Ford";

    public void honk() {
        System.out.println("Tuut, tuut!");
    }
}
// Vehicle 클래스를 상속받은 Car 클래스
class Car extends Vehicle {
    private String modelName = "Mustang";

    public static void main(String[] args) {
        Car myCar = new Car();

        // 부모 클래스의 메소드 사용
        myCar.honk();

        // 부모 클래스의 brand 변수 사용
        System.out.println(myCar.brand + " " + myCar.modelName);
    }
}
Tuut, tuut!
Ford Mustang

Process finished with exit code 0

코드 출처: https://www.w3schools.com/java/java_inheritance.asp

다른 클래스에서 파생된 클래스 → 하위 클래스(파생 클래스, 확장 클래스, 자식 클래스)

하위 클래스가 파생된 클래스 → 수퍼 클래스(기본 클래스, 부모 클래스)

수퍼 클래스가 없는 Object를 제외하고 모든 클래스에는 하나의 수퍼 클래스만 존재(단일상속)

수퍼 클래스가 명시돼 있지 않은 경우 모든 클래스는 암묵적으로 Object의 하위 클래스

사진, 내용 출처: https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html


public class Vehicle {
    public void honk() {}
}
class Car extends Vehicle {
    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.honk();
    }
}
javap -c Vihicle.class

public com.testcompany.Vehicle();
  Code:
     0: aload_0
     1: invokespecial #1    // Method java/lang/Object."<init>":()V
     4: return

invokespecial - 인스턴스 메소드를 호출;
superclass, private, 인스턴스 초기화 메서드 호출을 특별히 다루기 위한.

내용 출처: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial

'invokespecial'은 'invokevirtual'과 다르게 객체의 클래스가 아닌 참조형 타입에 따라 메소드를 선택

다시 말해서 동적 바인딩 대신 정적 바인딩을 수행

'invokespecial'이 사용되는 세 가지 상황에서 동적 바인딩은 원하는 결과를 얻지 못합니다.

  1. 인스턴스 초기화(<init>) 메소드 호출
  2. private 메소드 호출
  3. super 카워드를 사용한 메소드 호출

<init> 메소드는 오직 새로운 인스턴스가 생성될 때 호출

새로 생성된 객체의 상속경로를 따라 각 클래스마다 하나 이상의 <init> 메소드가 호출됨

왜 'invokespecial'이 <init> 메소드 호출을 사용할까? subclass <init> 메소드는 superclass <init> 메소드를 호출할 수 있어야 하기 때문. 이것이 인스턴스화 된 객체가 많은 <init> 메소드를 호출하는 방법. 가상 머신은 object's 클래스에 선언된 <init> 메소드를 호출. <init> 메소드는 먼저 같은 클래스의 다른 <init> 메소드를 호출 or superclass의 <init> 메소드를 호출. 이 프로세스는 Object까지 계속 올라감.

class Superclass {
    private void interestingMethod() {
        System.out.println("Superclass's interesting method.");
    }

    void exampleMethod() {
        interestingMethod();
    }
}

class Subclass extends Superclass {
    void interestingMethod() {
        System.out.println("Subclass's interesting method.");
    }

    public static void main(String[] args) {
        Subclass me = new Subclass();
        me.exampleMethod();
        // 출력: Superclass's interesting method.
    }
}

Subclass에서 main()을 호출하면 "Superclass의 흥미로운 메소드"를 출력해야 함. 'invokevirtual'을 사용하면 "Subclass의 흥미로운 메소드"를 출력. WHY? 가상 머신은 객체의 실제 클래스인 Subclass 기반으로 interestingMethod()를 선택하기 때문.

반면 'invokespecial'을 사용하면 가상 머신이 참조 유형에 따라 메소드를 선택하므로 Superclass interestingMethod()를 호출.

'invokeinterface' opcode는 'invokevirtual'과 똑같이 작동. 단지 다른점이라면 'invokeinterface'는 인터페이스 타입일 때 사용.

호출 명령어와 속도

상상할 수 있듯이 인터페이스 참조가 메소드를 호출하는 것이 클래스 참조가 메소드를 호출하는 것보다 느릴 수 있다. 자바 가상 머신이 'invokevirtual' 명령어를 발견하고 인스턴스 메소드에 대한 직접 참조(direct reference)에 대한 기호 참조를 분석할 때 'direct reference'는 메소드 테이블에 대한 오프셋(offset;상대 주소)이 될 수 있다. 그 시점부터 동일한 오프셋을 사용할 수 있다. 그러나 'invokeinterface' 명령어의 경우 가상 머신은 오프셋이 이전 호출과 동일하다고 가정할 수 없기 때문에 명령어가 발생할 때마다 메소드 테이블을 검색해야 한다.

가장 빠른 명령어는 'invokespecial' 및 'invokestatic'일 가능성이 크다. 이러한 명령어에 의해 호출된 메소드는 정적으로 바인딩 되기 때문이다. JVM이 기호 참조를 해석하고 'direct reference'로 교체하면 실제 바이트 코드에 대한 포인터를 포함할 것이다.

구현 의존성

이러한 모든 속도 예측은 추측에 불과합니다. 자바 가상 머신 디자이너들이 기술을 사용해 속도를 높일 수 있기 때문입니다. 기호 참조 및 호출 메소드를 해결하기 위한 데이터 구조 및 알고리즘은 JVM 사양이 아닙니다. 이러한 결정은 JVM 설계자에게 맡겨집니다.

결론

  1. 인스턴스 메소드는 <init>메소드, private 메소드 및 super 키워드로 호출된 메소드를 제외하고 동적으로 바인딩 됩니다. 이 세 가지 특수한 경우 인스턴스 메소드는 정적으로 바인딩 됩니다.
  2. 클래스 메소드는 항상 정적으로 바인딩됩니다.
  3. 인터페이스 참조로 호출된 인스턴스 메소드는 객체 참조로 호출된 동일한 메소드보다 느릴 수 있습니다.

1997년 6월 JavaWorld에 실린 기사 입니다.

코드, 내용 출처: https://www.artima.com/underthehood/invocation.html


super 키워드

public class Superclass {

    public void printMethod() {
        System.out.println("Printed in Superclass.");
    }
}
public class Subclass extends Superclass {

    // overrides printMethod in Superclass
    public void printMethod() {
        super.printMethod();
        System.out.println("Printed in Subclass");
    }
    public static void main(String[] args) {
        Subclass s = new Subclass();
        s.printMethod();
        // 출력
        // Printed in Superclass.
        // Printed in Subclass
    }
}

메소드가 수퍼 클래스의 메소드 중 하나를 재정의하는 경우 super 키워드를 사용하여 재정의된 메소드를 호출할 수 있음

출처: https://docs.oracle.com/javase/tutorial/java/IandI/super.html


메소드 오버라이딩

인스턴스 메소드

동일한 시그니쳐(이름, 매개 변수의 개수 및 타입)와 리턴 타입을 가진 서브 클래스의 인스턴스 메소드는 슈퍼 클래스의 메소드를 재정의

메소드를 재정의할 때 컴파일러에 superclass의 메소드를 재정의할 것을 지시하는 @Override 주석(annotation)을 사용. 어떤 이유로 컴파일러가 superclass 메소드 중 하나가 존재하지 않음을 감지하면 오류를 생성.

Static 메소드

만약 subclass가 static 메소드이며 superclass의 static 메소드와 동일한 시그니처를 사용하면 subclass의 메소드는 superclass의 메소드를 숨김.

정적 메소드를 숨기는 것과 인스턴스 메소드를 오버라이딩(재정의)하는 것의 차이점은 다음과 같은 중요한 의미가 있다.

  • 호출하는 오버라이딩 인스턴스 메소드는 subclass 버전이다.
  • 호출하는 숨겨진 static 메소드의 버전은 superclass에서 호출되는지 subclass에서 호출되는지에 따라 다르다.
public class Animal {
    public static void testClassMethod() {
        System.out.println("The static method in Animal");
    }
    public void testInstanceMethod() {
        System.out.println("The instance method in Animal");
    }
}
public class Cat extends Animal {
    public static void testClassMethod() {
        System.out.println("The static method in Cat");
    }
    public void testInstanceMethod() {
        System.out.println("The instance method in Cat");
    }

    public static void main(String[] args) {
        Cat myCat = new Cat();
        Animal myAnimal = myCat;
        Animal.testClassMethod();
        myAnimal.testInstanceMethod();
        // 출력
        // The static method in Animal
        // The instance method in Cat
    }
}

Cat 클래스는 Animal 인스턴스 메소드를 재정의하고 Animal의 static 메소드를 숨긴다.

호출된 static 메소드는 superclass에 있는 버전이고 오버라이드된 인스턴스 메소드는 subclass 버전이다.

출처: https://docs.oracle.com/javase/tutorial/java/IandI/override.html


다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

동적 메소드 디스패치는 컴파일 타임이 아닌 런타임에 오버라이드된 메소드에 대한 호출이 해결되는 메커니즘

출처: https://www.geeksforgeeks.org/dynamic-method-dispatch-runtime-polymorphism-java/

오버라이드된 메소드가 superclass 참조를 통해 호출되면 Java는 호출이 발생할 때 참조되는 객체의 유형에 따라 해당 메소드의 버전(수퍼 클래스 / 서브 클래스)이 실행될 것인지 결정

런타임시 실행될 재정의 된 메소드 버전을 결정하는 것은 참조되는 개체의 유형 (참조 변수 유형이 아님)에 따라 다름

superclass 참조 변수는 subclass 객체를 참조 할 수 있다. 이를 업 캐스팅이라고도 한다. Java는이 사실을 사용하여 런타임에 재정의 된 메서드에 대한 호출을 해결한다.


추상 클래스

추상(abstract) 클래스는 추상적으로 선언된 클래스로 추상 메소드를 포함하거나 포함하지 않을 수 있다.

추상 클래스는 인스턴스화할 수 없지만 subclass화할 수 있다.

abstract void moveTo(double deltaX, double deltaY);
// 클래스에 추상 메소드가 포함된 경우 클래스는 다음과 같이 선언
public abstract class GraphicObject {
   // declare fields
   // declare nonabstract methods
   abstract void draw();
}

추상 클래스가 subclass화될 때 일반적으로 부모 클래스의 추상 메소드에 대한 구현을 제공. 하지만 그렇지 않은 경우에는 subclass도 반드시 추상화 선언을 해야 함.

default or static으로 선언되지 않은 인터페이스의 메소드는 암시적으로 추상이므로 추상 수정자는 인터페이스 메소드와 함께 사용되지 않음. (사용할 수 있지만 불필요)

출처: https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html


final 키워드

class ChessAlgorithm {
    enum ChessPlayer {WHITE, BLACK}
    ...
    final ChessPlayer getFirstPlayer () {
        return ChessPlayer.WHITE;
    }
    ...
}

메소드 선언에서 final 키워드를 사용해 subclass로 재정의할 수 없음을 나타냄. 'Object' 클래스가 이 작업을 수행하며 많은 메소드가 final이다.

변경되면 안되고 객체의 일관성이 매우 중요한 경우에 사용. ex) ChessAlgorithm 클래스의 getFirstPlayer 메소드를 final로 만들 수 있다.

생성자에서 호출된 메소드는 일반적으로 final로 선언되어야 함. 생성자가 non-final 메소드를 호출하면 subclass가 해당 매소드를 바람직하지 않은 결과로 재정의할 수 있다.

전체 클래스를 final로 선언할 수도 있다. final로 선언된 클래스는 subclass화할 수 없다. 예를 들어 'String' 클래스와 같은 불변의 클래스를 만들 때 특히 유용함.

출처: https://docs.oracle.com/javase/tutorial/java/IandI/final.html


Object 클래스

Object 클래스를 제외한 클래스에는 정확히 하나의 direct superclass가 있다. 클래스는 직접이든 간접적이든 상관없이 모든 superclass의 필드와 메소드를 상속. subclass는 상속하는 메소드를 재정의하거나 상속하는 필드 or 메소드를 숨길 수 있다. (필드를 숨기는 것은 일반적으로 나쁜 프로그래밍 관행입니다.)

Object 클래스는 클래스 계층 구조의 맨 위에 있다. 모든 클래스는 이 클래스의 자손이며 메소드를 상속한다. Object에서 상속받은 유용한 메소드에는 toString(), equals(), clone() 및 getClass()가 있다.

출처: https://docs.oracle.com/javase/tutorial/java/IandI/summaryinherit.html

더 읽어보기: https://opentutorials.org/course/1223/6241

2020. 12. 18.

5주차 과제: 클래스

https://github.com/whiteship/live-study/issues/5

목표

자바의 Class에 대해 학습하세요.

학습할 것 (필수)

  • 클래스 정의하는 방법
  • 객체 만드는 방법 (new 키워드 이해하기)
  • 메소드 정의하는 방법
  • 생성자 정의하는 방법
  • this 키워드 이해하기

과제 (Optional)

  • int 값을 가지고 있는 이진 트리를 나타내는 Node 라는 클래스를 정의하세요.
  • int value, Node left, right를 가지고 있어야 합니다.
  • BinrayTree라는 클래스를 정의하고 주어진 노드를 기준으로 출력하는 bfs(Node node)와 dfs(Node node) 메소드를 구현하세요.
  • DFS는 왼쪽, 루트, 오른쪽 순으로 순회하세요.


클래스 정의하는 방법

class MyClass {
    // UpperCarmelCase를 사용
    // 클래스 이름은 일반적으로 명사 또는 명사구
    // 때로는 형용사 형용사구 예) Readable
    // 테스트 클래스의 경우 마지막에 Test로 끝나도록
    // 예) HashTest
}
class MyClass extends MySuperClass implements YourInterface {
    // MySuperclass의 하위 클래스이고 
    // YourInterface 인터페이스의 구현을 표시
}
private class MyClass {
    // 접근 제어자
    // private -> default -> protected -> public
    // 순으로 보다 많은 접근을 허용
}

참고: https://google.github.io/styleguide/javaguide.html#s5.2.2-class-names


객체 만드는 방법 (new 키워드 이해하기)

// MyClass.java file
public class MyClass {
}
// Main.java file
public class Main {
    public static void main(String[] args) {
        MyClass mc = new MyClass();
    }
}
javap -c Main.class

public static void main(java.lang.String[]);
Code:
 0: new           #7  // class com/company/MyClass
 3: dup
 4: invokespecial #9  // Method com/company/MyClass.<init>:()V
 7: astore_1
 8: return

#0 new - 상수 풀(constant pool) 인덱스에서 클래스 참조(reference)로 식별되는 새로운 객체를 생성

#3 dup - 스택의 가장 위의 값을 복제

인스턴스화

new 키워드는 객체를 생성하는 Java 연산자입니다.

초기화

new 연산자 다음에 생성자 호출이 이어지며 이는 새 객체를 초기화합니다.

출처: https://docs.oracle.com/javase/tutorial/java/javaOO/objectcreation.html


메소드 정의하는 방법

public double calculateAnswer (double wingSpan,
    int numberOfEngines, double length, double grossTons) {
    // 여기서 계산
}

메서드 선언에는 순서대로 6개의 구성 요소가 있습니다.

1. 수정자
예) public, private

2. 반환 유형
메소드에서 반환한 값의 데이터 유형 또는 void(메소드가 값을 반환하지 않는 경우).

3. 메소드 이름
lowerCamelCase로 작성
일반적으로 동사, 동사구 예) sendMessage, stop
JUnit 테스트 메소드 이름에 밑줄이 표시되어 논리적 구성 요소를 구분할 수 있으며 각 구성 요소는 lowerCamelCase로 작성
전형적인 패턴 예시 <methodUnderTest>_<state>, pop_emptyStack
테스트 이름을 정하는 올바른 방법은 없습니다.
출처: https://google.github.io/styleguide/javaguide.html#s5.2.3-method-names

4. 괄호 안의 매개 변수 목록
쉼표로 구분된 입력 매개 변수 목록으로 데이터 유형이 앞에 있고 괄호()로 묶여 있습니다.
매개 변수가 없으면 빈 괄호를 사용.

5. 예외 리스트

6. 중괄호로 묶인 메서드 본문
지역 변수 선언을 포함하여 메소드의 코드가 여기에 표시됩니다.

출처: https://docs.oracle.com/javase/tutorial/java/javaOO/methods.html


생성자 정의하는 방법

public class Main {
    int x;

    // 클래스 생성자 생성
    public Main() {
        x = 5;
    }

    public static void main(String[] args) {
        Main myObj = new Main();
        // 메인 클래스 객체를 선언하면서 생성자를 호출
    }
}

생성자 생성 규칙

1. 클래스명과 메소드명이 동일하다.

2. 리턴타입을 정의하지 않는다.


this 키워드 이해하기

public class Point {
    public int x = 0;
    public int y = 0;
        
    // 생성자
    public Point(int a, int b) {
        x = a;
        y = b;
    }
}
public class Point {
    public int x = 0;
    public int y = 0;

    // 생성자
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

인스턴스 메소드 or 생성자 내에서 현재 사용하고 있는 객체에 대한 참조

this 키워드를 사용하는 가장 일반적인 이유는 필드(Field: 클래스 영역 안의 변수)가 메소드 or 생성자 매개 변수로 음영 처리되기 때문

this 키워드를 사용하여 동일한 클래스의 다른 생성자를 호출할 수 있습니다. 이를 명시적 생성자 호출(explicit constructor invocation)이라고 합니다.

public class Rectangle {
    private int x, y;
    private int width, height;

    public Rectangle() {
        this(0, 0, 1, 1);
    }
    public Rectangle(int width, int height) {
        this(0, 0, width, height);
    }
    public Rectangle(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
    ...
}

출처: https://docs.oracle.com/javase/tutorial/java/javaOO/thiskey.html


과제

아직 작성 중입니다.

public class Node {

    // int 값을 가지고 있는 이진 트리를 나타내는
    // Node 라는 클래스를 정의하세요.

    // int value, Node left, right 를 가지고 있어야 합니다.
    private int value;
    private Node left;
    private Node right;

    public Node(int value) {
        this.value = value;
        this.left = null;
        this.right = null;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }
}
public class BinaryTree {
    // BinrayTree라는 클래스를 정의하고 주어진 노드를
    // 기준으로 출력하는 bfs(Node node)와 dfs(Node node)
    // 메소드를 구현하세요.
    // DFS는 왼쪽, 루트, 오른쪽 순으로 순회하세요.

    // Breadth First Search (또는 Level Order Traversal)
    // : 너비우선탐색
    // Depth First Search : 깊이우선탐색

    // 트리의 루트
    Node root;

    // 기본 생성자에서 루트 값 초기화
    BinaryTree() {
        root = null;
    }

    void insertNode(int value) {
        root = insertRec(root, value);
    }

    Node insertRec(Node root, int value) {

        if (root == null) {
            root = new Node(value);
            return root;
        }

        if (value < root.getValue()) {
            root.setLeft(insertRec(root.getLeft(), value));
        } else if (value > root.getValue()) {
            root.setRight(insertRec(root.getRight(), value));
        }

        return root;
    }

}




2020. 12. 4.

4주차 과제: 제어문

https://github.com/whiteship/live-study/issues/4

목표

자바가 제공하는 제어문을 학습하세요.

학습할 것

선택문

반복문

과제

과제 0. JUnit 5 학습하세요.

  • 인텔리J, 이클립스, VS Code에서 JUnit 5로 테스트 코드 작성하는 방법에 익숙해 질 것.

과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.

  • 깃헙 이슈 1번부터 18번까지 댓글을 순회하며 댓글을 남긴 사용자를 체크 할 것.
  • 참여율을 계산하세요. 총 18회에 중에 몇 %를 참여했는지 소숫점 두자리가지 보여줄 것.
  • Github 자바 라이브러리를 사용하면 편리합니다.
  • 깃헙 API를 익명으로 호출하는데 제한이 있기 때문에 본인의 깃헙 프로젝트에 이슈를 만들고 테스트를 하시면 더 자주 테스트할 수 있습니다.

과제 2. LinkedList를 구현하세요.

  • LinkedList에 대해 공부하세요.
  • 정수를 저장하는 ListNode 클래스를 구현하세요.
  • ListNode add(ListNode head, ListNode nodeToAdd, int position)를 구현하세요.
  • ListNode remove(ListNode head, int positionToRemove)를 구현하세요.
  • boolean contains(ListNode head, ListNode nodeTocheck)를 구현하세요.

과제 3. Stack을 구현하세요.

  • int 배열을 사용해서 정수를 저장하는 Stack을 구현하세요.
  • void push(int data)를 구현하세요.
  • int pop()을 구현하세요.

과제 4. 앞서 만든 ListNode를 사용해서 Stack을 구현하세요.

  • ListNode head를 가지고 있는 ListNodeStack 클래스를 구현하세요.
  • void push(int data)를 구현하세요.
  • int pop()을 구현하세요.

(optional) 과제 5. Queue를 구현하세요.

  • 배열을 사용해서 한번
  • ListNode를 사용해서 한번.


선택문

if (조건){
  // 조건이 참(true)이면 블록 내 코드를 실행
}
if (조건){
  // 조건이 참이면 이 블록 내 코드를 실행
} else {
  // 조건이 거짓(false)이면 이 블록 내 코드를 실행
}
if (조건1){
  // 조건1이 참이면 블록내 코드를 실행
} else if (조건2) {
  // 조건1이 거짓이고 조건2가 참이면 이 블록 내 코드를 실행
} else {
  // 조건1 조건2가 모두 거짓일 경우 이 블록 내 코드를 실행
}
switch (조건 값) {
  case 값1:
    // 조건 값 = 값1일 때 실행하고자 하는 명령문
    break;
  case 값2:
    // 조건 값 = 값2일 때 실행하고자 하는 명령문
    break;
  default:
    // 조건이 어떠한 case 값에도 해당하지 않을 때
    // 실행하고자 하는 명령문
    break;
}


반복문

while (조건문) {
  // 조건이 참일 경우 반복하고자 하는 명령문
}
do {
  // do / while 문은 먼저 루프를 한 번 실행한 후 조건식을 확인
} while (조건문)
for (초기식; 조건식; 증감식) {
  // 조건식의 결과가 참인 동안 반복적으로 실행하고자 하는 명령문
}


과제 0. JUnit 5 학습하세요.

참고: JUnit5 사용자 가이드

https://junit.org/junit5/docs/current/user-guide/

JUnit 5란?

JUnit 5 = JUnit 플랫폼 + JUnit Jupiter + JUnit 빈티지

JUnit 플랫폼: JVM 위에서 프레임워크 테스트를 위한 기초역할. TestEngine Api를 제공.

JUnit 쥬피터: 테스트 작성 및 확장을 위한 새로운 프로그래밍 모델과 확장 모델의 조합. 쥬피터 베이스 테스트를 구동하기 위한 테스트엔진.

JUnit 빈티지: JUnit 3와 JUnit 4 기반 테스트 플랫폼 실행을 위한 테스트엔진.


JUnit 5은 Java 8 이상이 필요

IntelliJ 실행 → 'New Project' or 상단 메뉴에서 'File' → 'New Project'

JUnit 5 종속성(dependency) 추가를 위해 Maven이나 Gradle 선택

Gradle:
https://docs.gradle.org/current/userguide/java_testing.html#using_junit5

Maven:
https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html

test {	// JUnit 플랫폼 테스트를 위해 추가
    useJUnitPlatform()
}
dependencies {	// JUnit 쥬피터 종속성 추가
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}
종속성을 추가하면 인텔리j에서 친절하게 Gradle 변화를 감지하고 로드할 것인지 물어본다.


처음 시작을 위한 gradle 예시 코드
https://github.com/junit-team/junit5-samples/tree/r5.7.0/junit5-jupiter-starter-gradle

main/java → class 'Calculator' 작성

public class Calculator {
  public int add(int a, int b) {
    return a + b;
  }
}

test/java → 테스트 코드 작성. 단축키 클래스 내에서 'Ctrl+Shift+T' 참고 영상

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

class MyFirstJUnitJupiterTests {
  private final Calculator calculator = new Calculator();
  @Test
  void addition() {
    assertEquals(2, calculator.add(1, 1));
  }
}

주석 (Annotations)

@Test
// 테스트 메소드임을 나타냄

@ParameterizedTest
// 파라미터화 테스트 메소드(각각 다른 arguments를 테스트)

@RepeatedTest
// 반복 테스트 템플릿

@TestFactory
// 동적 테스트를 위한 테스트 팩토리 메소드

@TestTemplate
// 등록된 providers가 반환한 

@TestMethodOrder
// 주석이 달린 테스트 클래스의 테스트 메소드 실행 순서를
// 구성하기 위해 사용

@TestInstance
// 주석이 달린 테스트 클래스에서 테스트 인스턴스 수명주기를
// 구성하는데 사용

@DisplayName
// 테스트 클래스 or 테스트 메소드에 사용자 지정 이름을 선언

@DisplayNameGeneration
// 테스트 클래스에 대한 사용자 지정 이름 생성기를 선언

@BeforeEach
// @Text, @RepeatedTest, @ParameterizedTest, or @TestFactory
// 각각 메소드보다 먼저 실행되어야 함을 표시

@AfterEach
// @Text, @RepeatedTest, @ParameterizedTest, or @TestFactory
// 각각 메소드 이후에 실행되어야 함을 표시

@BeforeAll
// @Text, @RepeatedTest, @ParameterizedTest, or @TestFactory
// 모든 메소드보다 먼저 실행 되어야 함을 표시

@AfterAll
// @Text, @RepeatedTest, @ParameterizedTest, or @TestFactory
// 모든 메소드 이후에 실행 되어야 함을 표시

@Nested
// 주석이 달린 클래스가 정적이 아닌 중첩 클래스임을 나타냄

@Tag
// 메소드 or 클래스 레벨에서 테스트 필터링을 위한
// 태그 선언을 위해 사용

@Disabled
// 테스트 클래스 or 메소드를 비활성화하는 데 사용

@Timeout
// 테스트, 테스트 팩토리, 테스트 템플릿 or 
// 라이프싸이클 메소드의 실행 시간 초과 실패에 사용

@ExtendWith
// 확장 선언(클래스, 매소드)등을 등록하는데 사용

@RegisterExtension
// 프로그래밍 적으로 확장성을 등록하는데  사용

@TempDir
// 라이프사이클 메소드, 테스트 메소드의 매개 변수, 필드 주입을 위한
// 임시 폴더 사용을 위해 선언


과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.

https://github.com/Sungjun-HQ/DashboardReader/tree/master/src

class DashboardReaderTest {

    @Test
    void getGHRepositoryTest() throws IOException {

        DashboardReader dashboardReader = new DashboardReader();

        // 접속한 github에서 태스트용 저장소로 이동
        GHRepository repo = dashboardReader.connectGH()
        	.getRepository("whiteship/live-study/");

        // 저장소에서 이슈 상태가 OPEN (ALL, CLOSED)
        List list = repo.getIssues(GHIssueState.ALL);
//        List list = repo.getIssues(GHIssueState.OPEN);

        assertTrue(list.size() > 0);

        Map map1 = new HashMap<>();

        for (GHIssue ghIssue : list) {
            GHUser user;
            List commentsList = ghIssue.getComments();

            if (commentsList.size() > 0) {
                for (GHIssueComment comment : commentsList) {
                    user = comment.getUser();

                    // 유저의 로그인 id 가져오기
                    if (map1.get(user.getLogin()) == null) {
                        map1.put(user.getLogin(), 1);
                    } else {
                        map1.put(user.getLogin(), map1.get(user.getLogin()) + 1);
                    }
                }
            }
        }
        // map1 소수점 출력
        for (String i : map1.keySet()) {
            float progressFloat = map1.get(i);
            progressFloat = progressFloat / 18 * 100;
            System.out.print("name: " + i);
            System.out.println(", progress: " + String.format("%.2f", progressFloat));
        }
    }
}

출력

Starting Gradle Daemon...
Gradle Daemon started in 1 s 264 ms

> Task :compileJava UP-TO-DATE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
name: a, progress: 16.67
name: b, progress: 11.11
name: c, progress: 16.67
name: d, progress: 16.67


과제 2. LinkedList를 구현하세요.