on
4주차 과제: 제어문.
해당 글을 백기선 님의 자바 스터디 4주차 과제를 공부하고 공유하기 위해서 작성되었습니다.
목표
자바가 제공하는 제어문을 학습한다.
학습할 것
제어문
코드의 실행흐름은 보통 위에서 아래로 한 문장씩 순차적으로 진행되지만, 때로는 같은 문장을 반복해서 수행하거나 조건에 따라 문장을 건너뛰어야 할 때가 있다.
이러한 프로그램의 흐름(flow)을 바꾸는 역할을 하는 문장들을 제어문(control statement)이라고 한다.
제어문에는 선택문(selection statement)과 반복문(loop statement) 이 있는데, 선택문은 조건에 따라 선택적으로 문장이 수행되도록 하고 반복문은 특정 문장들을 반복적으로 수행한다.
선택문
선택문(조건문)은 조건식과 문장을 포함하는 블럭{}으로 구성되어 있으며, 조건식의 연산결과에 따라 실행할 문장이 달라져서 프로그램의 실행흐름을 변경할 수 있다.
if
문과 switch
문, 두 가지가 있으며 주로 if
문이 많이 사용된다. 처리할 경우의 수가 많은 경우 switch
문이 효율적이지만, if
문보다 제약이 많다.
if문
if
문은 가장 기본적인 조건문이며, ‘조건식’과 ‘괄호 { }’로 이루어져 있다. ‘if’의 뜻은 ‘만일 ~이라면 …‘이므로 만일(if) 조건식이 참(true)이면 괄호 { }안의 문장을 수행하라.라는 의미로 해석된다.
if (조건식) {
// 조건식이 참(true)일 때 수행될 문장들
}
조건식
if
문에 사용되는 조건식은 일반적으로 비교연산자와 논리연산자로 구성된다.
조건식을 작성할 때, 등가비교 연산자 ==
대신 대입 연산자 =
를 사용하는 것에 주의해야 한다. 조건식의 결과는 반드시 true 또는 false여야 한다.
public class App {
public static void main(String[] args) {
int x = 0;
System.out.printf("x = %d 일 때, 참인 것은%n", x);
if (x == 0)
System.out.println("x == 0");
if (x != 0)
System.out.println("x != 0");
if (!(x == 0))
System.out.println("!(x == 0)");
if (!(x != 0))
System.out.println("!(x != 0)");
x = 1;
System.out.printf("x = %d 일 때, 참인 것은%n", x);
if (x == 0)
System.out.println("x == 0");
if (x != 0)
System.out.println("x != 0");
if (!(x == 0))
System.out.println("!(x == 0)");
if (!(x != 0))
System.out.println("!(x != 0)");
}
}
블럭 { }
괄호 { }를 이용해서 여러 문장을 하나의 단위로 묶을 수 있는데, 이것을 블럭(block)이라고 한다. 블럭은 {
로 시작해서 }
로 끝나고 }
뒤에 ;
를 붙이지 않는다.
블럭 내의 문장들은 탭(tab)으로 들여쓰기(indentation)를 해서 알기 쉽게 하는 것이 좋다.
블럭 안에는 보통 여러 문장이 들어가지만, 한 문장만 넣거나 문장을 넣지 않을 수도 있다. 만일 블럭 내의 문장이 하나뿐 일 때는 괄호 { }를 생략하거나 한 줄로 쓸 수도 있다.
if(조건식1)
...; // 조건식이 참(true)일 때 수행될 문장
if(조건식2) ...;
if-else문
if
문의 변형인 if-else
문은 if
문에 else
블럭이 추가 된 구조이다. ‘else’의 뜻이 ‘그 밖의 다른’이므로 조건식의 결과가 참이 아닐 때, 즉 거짓일 때 else
블럭의 문장을 수행한다.
if(조건식) {
// 조건식이 참(true)일 때 수행될 문장들
} else {
// 조건식이 거짓(false)일 때 수행될 문장들
}
조건식의 결과에 따라 두 개의 블럭 중 어느 한 블럭의 내용이 수행되고 전체 if
문을 벗어나게 된다. 두 블럭의 내용이 모두 수행되거나, 모두 수행되지 않는 경우는 없다.
두 개의 if
문이 가진 조건식이 서로 상반된 관계에 있으면 if-else
문으로 바꿀 수 있다.
if(input == 0) {
System.out.println("0이다.");
}
if(input != 0) {
System.out.println("0이 아니다.");
}
위 코드에 있는 두 개의 if
문은 서로 상반된 조건식을 가지고 있다. 따라서 if-else
문으로 바꿀 수 있고 바꾼 코드는 아래와 같다.
if(input == 0) {
System.out.println("0이다.");
} else {
System.out.println("0이 아니다.");
}
if-else
문 역시 블럭 내의 문장이 하나뿐인 경우 괄호를 생략할 수 있다.
if-else if문
두 가지 경우 중 하나를 수행해서 처리할 때는 if-else
문을 사용하면 된다.
처리해야 하는 경우의 수가 셋 이상인 경우에는 여러 개의 조건식을 사용할 수 있는 if-else if
문을 사용하면 된다.
if(조건식1) {
// 조건식1의 연산결과가 참일 때
} else if(조건식2) {
// 조건식2의 연산결과가 참일 때
} else if(조건식3) {
// 조건식3의 연산결과가 참일 때
} else {
// 위의 어느 조건식도 만족하지 않을 때
}
마지막은 보통 else
블럭으로 끝나며, else
블럭은 생략이 가능하다. else
블럭이 생략되었을 때는 if-else if
문의 어떠한 블럭도 수행되지 않을 수 있다.
import java.util.*;
public class App {
public static void main(String[] args) {
int score = 0;
char grade = ' ';
System.out.print("점수를 입력하세요. > ");
Scanner sc = new Scanner(System.in);
score = sc.nextInt();
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else {
grade = 'D';
}
System.out.println("당신의 학점은 " + grade + " 입니다.");
}
}
점수를 입력하면 해당하는 학점을 출력하는 코드이다.
두 번째 조건식을 보면 ‘80이상 90미만인 경우 B’, 즉 80 <= score && score < 90
이어야 하는데 score >= 80
으로 되어 있다.
이렇게 할 수 있는 이유는 첫 번째 조건인 score >= 90
이 거짓이기 때문이다. score >= 90
이 거짓이라는 말은 score < 90
이 참이라는 뜻이므로 두 번째 조건식에서 중복해서 확인할 필요가 없다.
세 번째 조건식도 같은 이유로 간단하게 쓸 수 있다.
이렇듯이 if-else if
문은 여러 개의 if
문을 합쳐놓은 것이지만, 조건식을 바꾸지 않고 쪼갠다면 전혀 다른 코드가 될 수 있으므로 주의해야 한다.
중첩 if문
if
문의 블럭 내에 또 다른 if
문을 포함시키는 것이 가능한데 이것을 중첩 if문이라고 부르며 중첩의 횟수에는 거의 제한이 없다.
if(조건식1) {
// 조건식1의 연산결과가 true일 때 실행될 문장들
if(조건식2) {
// 조건식1과 조건식2가 모두 true일 때 수행될 문장들
} else {
// 조건식1이 true이고, 조건식2가 false일 때 수행될 문장들
}
} else {
// 조건식1이 false일 때 수행될 문장들
}
내부 if
문은 외부 if
문보다 안쪽으로 들여쓰기를 해서 범위를 구분될 수 있도록 작성해야 한다.
중첩 if
문에서는 괄호의 생략에 주의해야 한다.
if(num > 0)
if(num != 0)
sign = '+';
else
sign = '-';
언뜻 보기에는 else
블럭이 바깥쪽 if
문에 속한 것처럼 보이지만 괄호가 생략되었을 때 else
블럭은 가까운 if
문에 속한 것으로 간주된다. 따라서 아래와 같이 되어 else
블럭은 어떠한 경우에도 수행이 되지 않는다.
if(num > 0) {
if(num != 0) {
sign = '+';
} else {
sign = '-';
}
}
switch문
if
문은 조건식의 결과가 참과 거짓, 두 가지 밖에 없기 때문에 경우의 수가 많아질수록 else if
를 계속 추가해야하므로 조건식이 많아져서 복잡해지고, 여러 개의 조건식을 계산해야하므로 처리시간도 많이 걸린다.
switch
문은 단 하나의 조건식으로 많은 경우의 수를 처리할 수 있고, 표현도 간결하여 알아보기 쉽다. 따라서 처리할 경우의 수가 많은 경우에는 if
문 보다 switch
문으로 작성하는 것이 좋다. 다만 switch
문은 제약조건이 있기 때문에, 경우의 수가 많아도 if
문으로 작성해야하는 경우가 있다.
switch
문은 조건식을 먼저 계산한 다음, 결과와 일치하는 case
문으로 이동한다. 이동한 case
문 아래에 있는 문장들을 수행하며, break
문을 만나면 전체 switch
문을 빠져나가게 된다.
switch(조건식) {
case 값1 :
... // 조건식의 결과가 값 1과 같을 경우
break;
case 값2 :
... // 조건식의 결과가 값 2와 같을 경우
break;
...
default :
... // 조건식의 결과와 일치하는 case문이 없을 때
}
default
문은 if
문의 else
블럭과 같은 역할을 한다. default
문의 위치는 어디라도 상관없지만 보통 마지막에 두기 때문에 break
문을 쓰지 않아도 된다.
break
문은 case
문의 영역을 구분하는 역할을 하는데, 이를 생략할 경우 다른 break
문을 만나거나 switch
문 블럭의 끝을 만날 때까지 나오는 모든 문장들을 수행한다. 따라서 각 case
문의 마지막에 break
문을 빼먹지 않도록 해야한다.
그러나 경우에 따라서 고의적으로 생략하는 경우도 있다.
switch(level) {
case 3 :
grantDelete(); // 삭제 권한을 준다.
case 2 :
grantWrite(); // 쓰기 권한을 준다.
case 1 :
grantRead(); // 읽기 권한을 준다.
}
회원제 웹 사이트에서 쓸 법한 간단한 코드의 일부이다.
등급에 따라서 권한을 부여하는 코드인데, 제일 높은 등급인 3을 가진 사용자는 모두 수행되어 읽기, 쓰기, 삭제 권한까지 모두 가지게 되고, 제일 낮은 등급인 1을 가진 사용자는 읽기 권한만 가지게 된다.
level
이 2일 때 다음과 같은 흐름으로 진행된다.
이미지 삽입
int level = 2;
...
switch(level) {
case 3 :
grantDelete(); // 삭제 권한을 준다.
case 2 :
grantWrite(); // 쓰기 권한을 준다.
case 1 :
grantRead(); // 읽기 권한을 준다.
}
break
문이 없기 때문에 해당 case
문 아래에 존재하는 case
문이 시행되고 더 이상 문장이 없으면 switch
문을 빠져나온다.
case
문은 한 줄에 하나씩 쓰던, 한 줄에 붙여서 쓰던 상관없다.
switch문의 제약조건
switch
문의 조건식은 결과값이 반드시 정수이어야 하며, 이 값과 일치하는 case
문으로 이동하기 때문에 case
문의 값 역시 정수이어야 한다. 그리고 중복되지 않아야 한다.
게다가 case
문의 값은 반드시 상수이어야 한다. 변수나 실수, 문자열은 사용할 수 없다.
public static void main(String[] args) {
int num, result;
final int ONE = 1;
...
switch(result) {
case '1' : // OK. 문자 상수
case ONE : // OK. 정수 상수
case "YES" : // OK. 문자열 상수. JDK 1.7부터 허용
case num : // 에러. 변수 불가능
case 0.1 : // 에러. 실수 불가능
...
}
}
switch문의 중첩
switch
문도 중첩이 가능하다. 중첩 switch
문에서 break
문을 빼먹지 않도록 주의해야한다.
import java.io.*;
import java.util.*;
class App {
public static void main(String[] args) throws Exception {
System.out.print("당신의 주민번호를 입력하세요.(ex:201231-3333222) > ");
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
char gender = str.charAt(7);
switch (gender) {
case '1':
case '3':
switch (gender) {
case '1':
System.out.println("2000년 이전 출생 남자입니다.");
break;
case '3':
System.out.println("2000년 이후 출생 남자입니다.");
}
break;
case '2':
case '4':
switch (gender) {
case '2':
System.out.println("2000년 이전 출생 여자입니다.");
break;
case '4':
System.out.println("2000년 이후 출생 여자입니다.");
}
break;
default:
System.out.println("유효하지 않은 주민번호 입니다.");
}
}
}
반복문
반복문은 어떤 작업이 반복적으로 수행되도록 할 때 사용하며, for
문, while
문, do-while
문이 있다.
for
문이나 while
문에 속한 문장은 조건에 따라 한 번도 수행되지 않을 수 있지만 do-while
문에 속한 문장은 무조건 최소 한 번의 수행이 보장된다.
반복문은 주어진 조건을 만족하는 동안 주어진 문장들을 반복적으로 수행하므로 조건식을 포함하며, 조건식의 결과가 true
면 참이고 false
면 거짓으로 간주된다.
for
문과 while
문은 구조와 기능이 유사하여 어느 경우에나 서로 변환이 가능하지만 for
문은 주로 반복횟수를 알고 있을 때 사용한다.
for문
for
문은 반복횟수를 알고 있을 때 사용하기 적합하다. 다음은 기본적인 for
문의 예시이다.
이미지 삽입
for(int i = 1; i <= 5; i++) {
System.out.println("I can do it");
}
변수 i
에 1을 저장하고, 매 반복마다 i
의 값을 1씩 증가시키다가 5를 넘으면 반복을 마친다.
for문의 구조와 수행순서
for(초기화; 조건식; 증감식) {
... // 조건식이 참일 때
}
for
문은 ‘초기화’, ‘조건식’, ‘증감식’, ‘블럭’ 총 4부분으로 이루어져 있으며, 조건식이 참인 동안 블럭 내의 문장을 반복하다가, 거짓이 되면 반복문을 벗어난다.
이미지 삽입
초기화
반복문에 사용될 변수를 초기화하는 부분이며 처음 한 번만 수행된다. 일반적으로 변수 하나로 for
문을 제어하지만 둘 이상의 변수가 필요할 때는 콤마 ,
를 구분자로 변수를 초기화하면 된다. 단, 두 변수의 타입은 같아야 한다.
for(int i = 1; ...; ...) { ... }
for(int i = 1, j = 0; ...; ...) { ... }
조건식
조건식의 값이 참(true)이면 반복을 계속하고, 거짓(false)이면 반복을 중단하고 for
문을 벗어난다.
for( ...; i <= 10; ...) { ... }
조건식을 잘못 작성하면 블럭 내의 문장이 수행되지 않거나 무한루프에 빠지기 쉬우므로 주의해야 한다.
증감식
반복문을 제어하는 변수의 값을 증가 또는 감소시키는 식이다. 매 반복마다 변수의 값이 증감식에 의해서 점진적으로 변하다가 조건식이 거짓이 되어 반복문을 벗어나게 된다.
증감식은 대부분 ++
를 사용하지만 아래와 같이 다양한 연산자들을 사용할 수 있다.
for(...; ...; i++) { ... } // 1씩 증가
for(...; ...; i--) { ... } // 1씩 감소
for(...; ...; i += 2) { ... } // 2씩 증가
for(...; ...; i *= 3) { ... } // 3배씩 증가
증감식도 콤마 ,
를 이용해서 두 문장 이상을 하나로 연결해서 쓸 수 있다.
for(...; ...; i++, j--) { ... }
다음 코드는 두 개 이상의 변수를 사용할 때의 예시이다.
class App {
public static void main(String[] args) throws Exception {
for (int i = 1, j = 5; i <= 5; i++, j--) {
System.out.printf("%d \t %d%n", i, j);
}
}
}
초기화, 조건식, 증감식 세 가지 요소는 필요하지 않으면 각각 생략할 수 있고, 모두 생략하는 것도 가능하다.
for(;;) { ... } // 모두 생략. 조건식은 참이 된다.
조건식이 생략된 경우, 참(true)으로 간주되어 무한 반복문이 된다. 대신 블럭 안에 if
문을 넣어 특정 조건을 만족하면 반복문을 빠져 나오게 해야 한다.
중첩 for문
for
문 역시 중첩이 가능하고 중첩의 횟수는 거의 제한이 없다.
중첩 for
문을 이해하기에 좋은 예시는 별찍기이다.
한 줄에 별 *
10개씩, 총 다섯 줄을 표현할 때 가장 단순한 방법은 한 줄씩 5번 출력하는 것이다.
System.out.println("**********");
System.out.println("**********");
System.out.println("**********");
System.out.println("**********");
System.out.println("**********");
하지만 for
문을 배웠으니 이를 활용하여서 간단히 나타낼 수 있다.
for(int i = 0; i < 5; i++) {
System.out.println("**********");
}
생각해보면 출력하는 문장 System.out.println("**********");
또한 반복적인 일을 하는 문장이다. 이를 for
문으로 바꾸면 아래처럼 중첩된 형태가 된다.
for(int i = 0; i < 5; i++) {
for(int j = 0; j < 10; j++) {
System.out.print("*");
}
System.out.println();
}
향상된 for문(enhanced for statement)
JDK 1.5부터 배열과 컬렉션에 저장된 요소에 접근할 때 기존보다 편리한 방법으로 처리할 수 있도록 for
문의 새로운 문법이 추가되었다.
for(타입 변수명 : 배열 또는 컬렉션) {
// 반복할 문장
}
타입은 배열 또는 컬렉션의 요소의 타입이어야 한다. 배열 또는 컬렉션에 저장된 값이 매 반복마다 하나씩 순서대로 읽혀서 변수에 저장된다. 그리고 반복문의 괄호 내에서는 이 변수를 사용해서 코드를 작성한다.
int[] arr = {10, 20, 30, 40, 50};
for(int tmp : arr) {
System.out.println(tmp);
}
위에 작성된 향상된 for
문은 일반적인 for
문과 동일하지만, 배열이나 컬렉션에 저장된 요소들을 읽어오는 용도로만 사용할 수 있다는 제약이 있다.
class App {
public static void main(String[] args) throws Exception {
int[] arr = { 10, 20, 30, 40, 50 };
for (int i = 0; i < arr.length; i++) {
System.out.printf("%d ", arr[i]);
}
System.out.println();
for (int tmp : arr) {
System.out.printf("%d ", tmp);
}
System.out.println();
}
}
while문
while
문은 for
문에 비해 구조가 간단하다. 조건식과 블럭만으로 이루어져 있는데, 조건식이 ‘참(true)인 동안’ 즉, 조건식이 거짓이 될 때까지 블럭 내의 문장을 반복한다.
while(조건식) {
... // 조건식의 연산결과가 참(true)인 동안
}
while
문은 먼저 조건식을 평가해서 조건식이 거짓이면 문장 전체를 벗어나고, 참이면 블럭 내의 문장을 수행하고 다시 조건식으로 돌아간다. 조건식이 거짓이 될 때까지 이 과정이 계속 반복된다.
while
문은 for
문과 달리 조건식을 생략할 수 없다.
class App {
public static void main(String[] args) throws Exception {
int i = 5;
while (i-- != 0) {
System.out.println(i + " - I can do it");
}
}
}
do-while문
do-while
문은 while
문의 변형으로 기본적인 구조는 while
문과 같으나 조건식과 블럭의 순서를 바꿔놓은 것이다. 그래서 while
문과는 반대로 블럭을 먼저 수행한 후에 조건식을 평가한다.
do {
... // 조건식의 연산결과가 참일 때
} while(조건식); // 끝에 ';'을 써줘야 한다.
반복적으로 사용자의 입력을 받아서 처리할 때 유용하다.
import java.util.*;
class App {
public static void main(String[] args) throws Exception {
int input = 0, answer = 0;
answer = (int) (Math.random() * 100) + 1;
Scanner sc = new Scanner(System.in);
do {
System.out.print("1과 100 사이의 정수를 입력하세요. > ");
input = sc.nextInt();
if (input > answer) {
System.out.println("더 작은 수를 입력하세요.");
} else if (input < answer) {
System.out.println("더 큰 수를 입력하세요.");
}
} while (input != answer);
System.out.println("정답입니다!");
}
}
break문
반복문에서도 break
문을 사용할 수 있는데 자신이 포함된 가장 가까운 반복문을 벗어난다. 주로 if
문과 함께 사용되어 특정 조건을 만족하면 반복문을 벗어나도록 한다.
class App {
public static void main(String[] args) throws Exception {
int sum = 0;
int i = 0;
while (true) {
if (sum > 100)
break;
++i;
sum += i;
}
System.out.println("i = " + i);
System.out.println("sum = " + sum);
}
}
continue문
continue
문은 반복문 내에서만 사용될 수 있으며, 반복이 진행되는 도중에 continue
문을 만나면 반복문의 끝으로 이동하여 다음 반복으로 넘어간다. for
문은 증감식으로, while
문과 do-while
문은 조건식으로 이동한다.
break
문과 달리 반복문 전체를 벗어나지 않는다. 전체 반복 중에 특정조건을 만족하는 경우를 제외하고자 할 때 유용하다.
class App {
public static void main(String[] args) throws Exception {
for (int i = 0; i <= 10; i++) {
if (i % 3 == 0)
continue;
System.out.println(i);
}
}
}
이름 붙은 반복문
break
문은 근접한 단 하나의 반복문만 벗어날 수 있기 때문에, 여러 개의 반복문이 중첩된 경우에는 완전히 벗어날 수 없다.
이 때 중첩 반복문 앞에 이름을 붙이고 break
문과 continue
문에 이름을 지정해 주면 하나 이상의 반복문을 벗어나거나 건너뛸 수 있다.
import java.util.*;
class App {
public static void main(String[] args) throws Exception {
int menu = 0;
int num = 0;
Scanner sc = new Scanner(System.in);
outer: while (true) {
System.out.println("(1) square");
System.out.println("(2) square root");
System.out.println("(3) log");
System.out.println("(0) exit");
System.out.print("원하는 계산(1 ~ 3)을 선택하세요. > ");
String str = sc.nextLine();
menu = Integer.parseInt(str);
if (menu == 0) {
System.out.println("프로그램을 종료합니다.");
break;
} else if (!(1 <= menu && menu <= 3)) {
System.out.println("잘못된 입력입니다. 다시 입력해주세요.");
continue;
}
for (;;) {
System.out.print("계산할 값을 입력하세요. (계산 종료: 0, 전체 종료: 99) > ");
str = sc.nextLine();
num = Integer.parseInt(str);
if (num == 0)
break;
if (num == 99) {
System.out.println("시스템을 종료합니다.");
break outer;
}
switch (menu) {
case 1:
System.out.println("result = " + num * num);
break;
case 2:
System.out.println("result = " + Math.sqrt(num));
break;
case 3:
System.out.println("result = " + Math.log(num));
break;
}
}
}
}
}
과제
과제 0. JUnit 5를 학습하세요.
JUnit이란?
JUnit
은 보이지 않고 숨겨진 단위 테스트를 끌어내어 정형화시켜 단위 테스트를 쉽게 해주는 테스트용 Framework로 쉽게 말해 단위 테스트 도구이다.
외부 테스트 프로그램(케이스)을 작성하여 System.out
으로 번거롭게 디버깅하지 않아도 되며 프로그램 테스트 시 걸릴 시간도 관리할 수 있게 해준다.
JDK 1.4
에서 추가된 assertXXX
를 사용하여 Test를 진행할 수 있는데 결과를 Test클래스로 남겨서 개발자에게 테스트 방법 및 클래스의 History를 넘겨줄 수도 있다.
JUnit 5
JUnit 5는 Platform
, Jupiter
, Vintage
세 가지 서브 프로젝트의 여러 모듈로 구성되어 있다.
Architecture
- Platform
- Test를 실행하고 관리하는 플랫폼 모듈.
- JVM에서 테스트를 진행하기 위한 TestEngine API를 제공.
- Jupiter
- JUnit 5의 TestEngine API의 구현체.
- JUnit 5에서 테스트 및 확장하기 위한 프로그래밍 모델과 확장 모델의 조합.
- Vintage
- JUnit 3과 JUnit 4의 TestEngine API 구현체.
- 하위 버전 기반의 테스트를 실행시키기 위해 해당 TestEngine을 제공.
JUnit5 사용해보기!
이 후 next next해서 프로젝트를 생성한다.
사용된 어노테이션(Annotations)
Jupiter에서 테스트와 확장된 기능을 위해 다양한 어노테이션을 제공하는데 그 중에 사용된 어노테이션은 다음과 같다.
-
@Test
테스트 메소드임을 알린다. -
@DisplayName
해당 테스트 클래스 혹은 메소드에 대한 이름을 정할 수 있다. -
@BeforeAll
클래스의 모든 테스트 메서드가 실행하기 전 가장 먼저 실행된다. -
@AfterAll
클래스의 모든 테스트 메서드가 실행된 후 가장 마지막에 실행된다. -
@BeforeEach
클래스의 각각의 테스트 메서드가 실행되기 전 실행된다. -
@AfterEach
클래스의 각각의 테스트 메서드가 실행된 후 실행된다.
Assertions
Assertion
을 통해 자신의 로직이 정확한지 테스트 해볼 수 있다.
Jupiter에서는 기존 버전에 존재했던 Assertion
메소드를 포함하고, 람다와 함께 사용하기 적합한 추가 메소드를 제공한다.
Assertions | 설 명 |
@assertEquals(expected, actual) | 기대한 값(expected)이 실제 값(actual)과 일치하는지 확인한다. |
@assertArrayEquals(expected, actual) | 기대한 배열의 값(expected)이 실제 배열의 값(actual)과 일치하는지 확인한다. |
@assertIterableEquals(expected, actual) | 기대한 iterable 자료구조의 값(expected)이 실제 iterable 자료구조의 값(actual)과 일치하는지 확인한다. |
@assertNotNull(actual) | null인지 아닌지 확인한다. |
@assertTrue(boolean) | 참인지 아닌지 확인한다. |
@assertAll(executable...) | 여러 개의 케이스를 하나로 묶어 테스트 하는데 사용한다. |
@assertThrows(expectedType, executable) | 기대한 예외가 나오는지 확인한다. |
이 외에도 더 많은 기능들이 제공되고 있다.
과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.
만들기 전에
우선 무엇을 만들어야하는지를 확인해보자.
깃헙 이슈 1번부터 18번까지 댓글을 순회하며 댓글을 남긴 사용자를 체크하여 참여율을 계산하는 대시보드를 만들어야 한다.
깃헙 API
깃헙 API를 활용하려면 인증이 필요한데, 인증 방법은 총 4가지이며 각각에 맞는 정보가 필요하다.
- Username, Password
- Personal access token
- JWT token
- GitHub App installation token
여기서 Personal Access Token 방법을 사용해 볼 것이고 자세한 방법은 여기를 참고하면 된다.
인증 정보를 얻었다고 끝이 아니라 객체를 불러와야 사용할 수 있다.
GitHub gitHub = new GitHubBuilder().withOAuthToken("얻은 토큰").build();
우리가 얻은 토큰은 개인정보이기 때문에 공개적인 공간에 배포해서는 안된다. 로컬에서만 관리하고 유출되지 않도록 조심해야 한다.
해당 라이브러리에서는 Property File과 Environmental variables를 사용할 수 있도록 메소드를 만들어 놓았다. 이 것들을 활용하도록 하자.
// token.properties
oauth = 토큰 정보
oauth
라는 key에 토큰 정보를 value값으로 저장해야 한다. API 내부에서는 oauth
를 식별자로 key를 찾기 때문이다.
위 과정을 거치고 나면 다음 코드로 객체를 얻을 수 있다.
GitHub gitHub = GitHubBuilder.fromPropertyFile("token.properties의 위치").build();
이제 레포 정보를 가져오자. 이전에 얻은 객체를 통해서 쉽게 가져올 수 있다.
GHRepository liveStudyRepo = gitHub.getRepository("owner/repo_name");
이슈 가져오기
이슈 또한 Repo 정보에서 쉽게 가져올 수 있다.
List<GHIssue> issueList = liveStudyRepo.getIssue(GHIssueState.ALL);
GHIssueState()
는 Open, Close, All 중에서 설정하여 원하는 상태의 이슈를 가져오는 것이다. 모든 이슈를 가져와야 하므로 All로 설정한다.
출석 체크하기
이슈를 가져왔으니 순회하면서 각각의 댓글들을 확인하고 작성자를 가져와야 한다. 댓글이 중복될 가능성을 고려하여 아이디 중복 체크를 해주도록 하자. 또한 과제가 아직 시작되지 않은 상태라면 제외시켜서 동작 시간을 조금 줄였다.
private Map<String, Integer> takeAttendance() {
final int INITIAL_COUNT_NUM = 1;
// 출석 체크를 위한 HashMap -> <id, 출석 횟수>
Map<String, Integer> attendanceInfo = new HashMap<>();
try {
List<GHIssue issue : issueList) {
// 이슈가 시작되지 않았다면
if(!isOngoingAssignment(issue.getLabels())) continue;
// 중복 댓글을 막기 위한 HashMap
Set<String> curIssueUserList = new HashSet<>();
// 댓글 순회
for(GHIssueComment comment : issue.getComments()) {
String userId = comment.getUser().getLogin();
// 오너는 제외
if(userId.equals(REPO_OWNER_ID)) continue;
// 중복 제외
if(curIssueUserList.contains(userId)) continue;
curIssueUserList.add(userId);
// 출석체크
if(attendanceInfo.containsKey(userId)) {
attendanceInfo.put(userId, attendanceInfo.get(userId) + 1);
continue;
}
attendanceInfo.put(userId, INITIAL_COUNT_NUM);
}
}
return attendanceInfo;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private boolean isOngoingAssignment(Collection<GHLabel> labels) {
final String DRAFT = "draft";
for(GHLabel label : labels) {
if(label.getName().equals(DRAFT)) {
return false;
}
}
return true;
}
테스트 코드
LiveStudyDashboard
클래스에 3개의 메소드가 있다.
-
takeAttendance
Issue의 댓글들을 읽고 출석 체크 -
isOngoingAssignment
현재 과제가 진행중이거나 진행했었는지 판단 -
getUserParticipationRate
참여율을 계산하고 이를 출력
getUserParticipationRate
메소드는 takeAttendance
메소드와 결합되어 출력만 하기때문에 테스트에서 제외했다. 따라서 takeAttendance
와 isOngoingAssignment
를 테스트 해볼 것이다.
테스트를 해보려니 두 메소드의 접근 제어자가 private
이어서 외부에서의 접근이 불가능하다. 이럴 때 Java의 Reflection을 사용하여 접근이 가능하다.
Reflection
은 런타임에 특정한 클래스의 정보(메소드, 변수)를 찾아 호출하거나, 객체를 생성할 수 있는 기법으로 자세한 내용은 구글링을 하거나 Ref를 참고하자.
테스트에도 많은 패턴이 존재하는데 사실 JUnit5를 처음 접하고 테스트 코드를 작성하는 것도 처음이기에 방법에 대해서 찾아봤다. 그 중에 기계인간 John Grib님의 JUnit5로 계층 구조의 테스트 코드 작성하기를 참고했다.
과제 2. LinkedList를 구현하세요.
LinkedList
구현하기 전에 LinkedList에 대해 공부했다.
배열은 가장 기본적인 형태의 자료구조로 구조가 간단하며 사용하기 쉽다. 데이터를 읽어오는 시간도 가장 빠르다는 장점이 있지만, 크기를 변경할 수 없고 비순차적인 데이터의 추가와 삭제에 시간이 많이 걸린다는 단점이 있다.
이러한 단점을 보완하기 위해서 링크드 리스트(LinkedList)라는 자료구조가 고안되었다. 배열은 모든 데이터가 연속적으로 존재하지만 링크드 리스트는 불연속적으로 존재하는 데이터를 서로 연결(link)한 형태로 구성되어 있다.
링크드 리스트에서의 데이터 삭제는 삭제하고자 하는 요소의 이전요소가 삭제하고자 하는 요소의 다음 요소를 참조하도록 변경하면 된다. 하나의 참조만 변경하면 삭제가 이루어지는 것이다. 데이터의 이동을 위한 복사 과정이 없기 때문에 처리속도가 매우 빠르다.
새로운 데이터의 추가는 새로운 요소를 생성한 후 추가하려는 위치의 이전 요소의 참조를 새로운 요소에 대한 참조로 변경해주고, 새로운 요소가 그 다음 요소를 참조하도록 하면된다.
구현
학습한 내용을 바탕으로 코드를 작성해보았다.
import java.util.*;
import java.io.*;
class App {
}
과제 3. Stack을 구현하세요.
과제 4. 앞서 만든 ListNode를 사용해서 Stack을 구현하세요.
과제 5. Queue를 구현하세요.
Reference
Comments
JAVA STUDY HALLE 의 다른 글
-
15주차 피드백 06 Mar 2021
-
15주차 과제: 람다식. 28 Feb 2021
-
14주차 피드백 27 Feb 2021
-
14주차 과제: 제네릭. 22 Feb 2021
-
13주차 피드백 20 Feb 2021
-
13주차 과제: I/O. 08 Feb 2021
-
12주차 피드백 06 Feb 2021
-
12주차 과제: 애노테이션. 01 Feb 2021
-
11주차 피드백 30 Jan 2021
-
11주차 과제: Enum. 24 Jan 2021
-
10주차 피드백 23 Jan 2021
-
10주차 과제: 멀티쓰레드 프로그래밍. 18 Jan 2021
-
9주차 피드백 16 Jan 2021
-
9주차 과제: 예외 처리. 10 Jan 2021
-
8주차 피드백 09 Jan 2021
-
8주차 과제: 인터페이스. 03 Jan 2021
-
7주차 피드백 02 Jan 2021
-
7주차 과제: 패키지. 28 Dec 2020
-
6주차 피드백 27 Dec 2020
-
6주차 과제: 상속. 21 Dec 2020
-
5주차 피드백 20 Dec 2020
-
5주차 과제: 클래스. 14 Dec 2020
-
4주차 피드백 13 Dec 2020
-
4주차 과제: 제어문. 30 Nov 2020
-
3주차 피드백 29 Nov 2020
-
3주차 과제: 연산자. 22 Nov 2020
-
2주차 피드백 21 Nov 2020
-
2주차 과제: 자바 데이터 타입, 변수 그리고 배열. 15 Nov 2020
-
1주차 피드백 15 Nov 2020
-
1주차 과제: JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가. 08 Nov 2020