목표
자바가 제공하는 제어문을 학습하세요.
학습할 것 (필수)
- 선택문
- 반복문
과제 (옵션)
과제 설명
과제 0. JUnit 5 학습하세요.
- 인텔리J, 이클립스, VS Code에서 JUnit 5로 테스트 코드 작성하는 방법에 익숙해 질 것.
- 이미 JUnit 알고 계신분들은 다른 것 아무거나!
- 더 자바, 테스트 강의도 있으니 참고하세요~
과제 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()을 구현하세요.
과제 5. Queue를 구현하세요.
- 배열을 사용해서 한번
- ListNode를 사용해서 한번.
1. 선택문
조건문의 경우 조건식과 문장을 포함하는 블럭({}
)으로 구성됭 있으며, 조건식의 연산결과에 따라 실행할 문장이 달라져서 프로그램의 실행흐름을 변경할 수 있다.
if문
public static void main(String[] args) {
int score = 80;
if (score > 70) {
System.out.println("합격입니다.");
}
}
=========================================
합격입니다.
조건식(if
)이 참이면 괄호 안의 문장들을 수행하는 문이다.
if (score > 70)
System.out.println("합격입니다.");
if (score > 70) System.out.println("합격입니다.");
블럭 안의 문장이 하나뿐 일때 위와 같이 괄호를 생략하거나 한줄로 쓸 수 있다.
if-else문
public static void main(String[] args) {
int input = 1;
if (input == 0) {
System.out.println("0입니다.");
} else {
System.out.println("0이 아닙니다.");
}
}
======================================
0이 아닙니다.
조건식의 결과가 참이 아닐 때, 즉 거짓일 때 else
블럭의 문장을 수행하라는 뜻이다.
if문과 마찬가지로 블럭 내의 문장이 하나뿐인 경우 괄호를 생략할 수 있다.
if-else if문
public static void main(String[] args) {
int score = 70;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else if (score >= 70) {
System.out.println("C");
} else {
System.out.println("D");
}
}
=====================================
C
처리해야할 경우의 수가 셋 이상인 경우에 사용한다.
else
블럭은 생략 가능하다.
중첩 if문
public static void main(String[] args) {
int num = 0;
if (num >= 0) {
if (num == 0) {
System.out.println("0입니다.");
} else {
System.out.println("양수입니다.");
}
}
}
==============================================
0입니다.
if문 블럭 내에 또 다른 if문을 포함시키는 문이다.
switch문
if문의 경우 조건식의 결과가 참과 거짓 두 가지뿐이기에 경우의 수가 많아질수록 else-if가 추가되야해서 복잡해질 수 잇고 여러 조건식을 처리해야 해서 처리시간도 더 길다.
switch문은 단 하나의 조건식으로 많은 경우의 수를 처리할 수 있기 때문에 처리할 경우의 수가 많을수록 if문보다는 switch문을 사용하는 게 좋다.
switch (조건식) {
case 값1:
//조건식의 결과가 값1과 같을 경우 수행될 코드
break;
case 값2:
//조건식의 결과가 값2과 같을 경우 수행될 코드
break;
default:
//조건식의 결과와 일치하는 case문이 없을 경우 수행될 코드
}
코드에서 조건식의 결과와 일치하는 값이 있는 case가 있는지 찾아서 찾는다면 해당 케이스의 코드를 수행하고, 만일 적절한 값을 찾지 못한다면 default의 코드를 수행한다. 여기서 각각의 케이스는 코드 수행 후 break문을 만나 전체 switch문을 빠져나가는데 만약 break문이 없다면 종료되지 않고 계속 진행이 되며 이것을 폴 스루(fall - through)
라고 한다.
추가적으로 default는 보통 마지막에 놓기 때문에 break문을 생략하는 경우도 있다.
하지만, 고의적으로 break문을 생략하는 경우도 있다. 예를 들어, 회원제로 운영되는 웹 사이트에서 사용자의 등급(level
)을 체크하여 등급에 맞는 권한을 부여하는 방식으로 구현하는 경우 break문을 생략할 수 도 있다.
public static void main(String[] args) {
int level = 2;
switch (level) {
case 3:
grandDelete(); // 삭제권한
case 2:
grantWrite(); // 쓰기권한
case 1:
grantRead(); // 읽기권한
}
}
위와 같이 사용한다면 레벨에 따라 3인 사람은 읽기, 쓰기, 삭제 권한을 모두 가지고, 레벨 2는 쓰기, 읽기 권한만, 레벨1은 읽기 권한만 가지도록 할 수 있다.
switch문의 제약조건
- switch문의 조건식 결과는 정수 또는 문자열이어야 한다.
- case문의 값은 정수 상수만 가능하며, 중복되지 않아야 한다.
int num, result;
final int ONE = 1;
switch(condition){
case '1': //OK. 문자 리터럴(정수 상수 49와 동일)
case ONE: //OK. 정수 상수
case "YES": //OK. 문자열 리터럴, JAVA 7부터 허용
case num; //에러. 변수는 불가
case 1.0: //에러. 실수는 불가
}
if문과 switch문
- if문
if (month == 3 || month == 4 || month == 5) {
System.out.println("봄");
} else if (month == 6 || month == 7 || month == 8) {
System.out.println("여름");
} else if (month == 9 || month == 10 || month == 11) {
System.out.println("가을");
} else { // month == 12 || month == 1 || month == 2
System.out.println("겨울");
}
- switch문
switch (month) {
case 3: case 4: case 5:
System.out.println("봄");
break;
case 6: case 7: case 8:
System.out.println("여름");
break;
case 9: case 10: case 11:
System.out.println("가을");
break;
default: //case 12: case 1: case 2
System.out.println("겨울");
}
이런 경우 if문 보다 switch문이 한결 간결한 표현이 된다. 또한, case문은 한 줄에 하나씩 쓰던, 한 줄에 붙여서 쓰던 상관없다.
switch문의 중첩사용
switch문 역시 중첩사용이 가능하다. 주의할 점은 중첩 switch문에서 break문을 빼먹기 쉽다는 것이다.
public static void main(String[] args) {
char gender = '3';
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("유효하지 않은 주민등록번호입니다.");
}
}
2. 반복문
반복문은 어떤 작업을 반복수행하기 위해 사용된다.
for
while
do-while
for
과 while
의 경우 조건에 따라 한 번도 수행되지 않을 수 있지만 do-while
문에 속한 문장은 무조건 최소한 번은 수행될 것이 보장된다.
for문
반복 횟수를 알고 있을 때 적합하다.
for (초기화; 조건식; 증감식) {
// 조건식이 참일때 수행될 문장들을 적는다.
}
① ② ③
for (int i = 1; i <= 5; i++) {
수행될 코드
}
- 초기화 ① : for문의 기준이 될 변수의 초기값 설정으로 위 코드에서는
int
타입의i
에1
을 설정한다. - 조건식 ② : for문이 수행될 조건으로 해당 조건이
true
가 되는 동안 반복이 수행된다. - 증감식 ③ : 코드 수행 후 조건평가 전 수행되며, 위 코드에서는
i
를 1씩 후위증가 시킨다.
for문 수행순서
① ② ④
for (int i = 1; i <= 5; i++) {
수행될 코드 ③
}
초기화 ①
가 먼저 수행되고, 그 이후부터는 조건식이 참인 동안 조건식 ② -> 수행될 코드 ③->증감식 ④
의 순서로 계속 반복되고 조건식이 거짓이 되면 for문 전체를 빠져나가게 된다.
중첩 for문
for문 안에 또 다른 for문을 포함시키는 것이 가능하다.
public static void main(String[] args) {
for (int i = 2; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
System.out.printf("%d X %d = %d\n", i, j, i * j);
}
}
}
===========================================
2 X 1 = 2
2 X 2 = 4
2 X 3 = 6
2 X 4 = 8
......
9 X 8 = 72
9 X 9 = 81
향상된 for문 (enhanced for statement)
JDK 1.5부터 배열과 컬렉션에 저장된 요소에 접근할 때 기존보다 편리한 방법으로 처리할 수 있도록 for문의 새로운 문법이 추가되었다.
for( 타입변수명 : 배열 또는 컬렉션 ) {
//반복될 코드
}
위의 코드에서 타입은 배열 또는 컬렉션의 요소의 타입이어야 한다.
public static void main(String[] args) {
String[] names = new String[] {"pobi", "min", "hwan"};
for (String name : names) {
System.out.println(name);
}
}
==========================================
pobi
min
hwan
- 인텔리제이에서
iter
명령어를 입력하면 향상된 for문을 사용할 수 있다. 일반적인 for문의 경우itar
명령어를 입력하면 된다.
while문
while (조건식) {
//조건식의 연산결과가 참(true)인 동안, 반복될 코드들을 적는다.
}
if문과 유사하지만 while문은 조건식이 참(true)인동안 블럭내의 문장을 반복한다.
조건식이 참일 경우 블럭안으로 들어가서 문장을 수행하고 다시 조건식으로 돌아가고 거짓일 경우 while문을 벗어난다.
public static void main(String[] args) {
int i = 2;
int j = 1;
while (i <= 9) {
while (j <= 9) {
System.out.printf("%d X %d = %d\n", i, j, i * j);
j++;
}
i++;
j = 1;
}
}
============================
2 X 1 = 2
2 X 2 = 4
2 X 3 = 6
2 X 4 = 8
......
9 X 8 = 72
9 X 9 = 81
for문과 while문
상황에 따라 초기화나 증감식이 필요하지 않은 경우 while문을 사용하고 그렇지 않다면 for문을 사용하면 된다.
while문의 조건식은 생략불가
for문과 다르게 while문의 조건식은 생략할 수 없다. 그렇기에 true를 넣어 항상 참이 되도록 만들어 줘야 한다.
while () {
//수행될 코드
}
for (;;) { //조건식이 항상 참
//수행될 코드
}
do-while문
기본적인 구조는 while문과 같으나 조건식과 블럭의 순서를 바꾼것으로, 기존의 while문과는 반대로 블럭을 먼저 수행 후 조건식을 평가한다.
그렇기에 다른 반복문과 다르게 최소한 한번은 수행될 것을 보장한다.
do {
//조건식의 연산결과가 참일때 수행될 문장들을 적는다.
} while (조건식);
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int input = 0;
int answer = 0;
answer = new Random().nextInt(10);
do {
System.out.println("1과 10사이의 정수를 입력하세요.");
input = scanner.nextInt();
if (input > answer) {
System.out.println("더 작은 수로 다시 시도해보세요.");
} else if (input < answer) {
System.out.println("더 큰 수로 다시 시도해보세요.");
}
} while (input != answer);
System.out.println("정답");
}
======================================
1과 10사이의 정수를 입력하세요.
2
더 작은 수로 다시 시도해보세요.
1과 10사이의 정수를 입력하세요.
1
정답
break문
break문은 자신이 포함된 가장 가까운 반복문을 벗어난다. 그렇기에 보통 if문과 함께 사용되어 특정 조건을 만족하면 반복문을 벗어나도록 한다.
- 숫자를 1부터 계속 더해 나가서 몇까지 더하면 합이 100을 넘는지 확인하는 예제
public static void main(String[] args) {
int sum = 0;
int i = 0;
while (true) {
if (sum > 100) {
break;
}
++i;
sum += i;
}
System.out.printf("i = %d, sum = %d\n", i, sum);
}
continue문
continue문은 반복문 내에서만 사용되며, 반복이 진행되는 도중에 continue문을 만나면 반복문의 끝으로 이동하여 다음 반복으로 넘어간다. for문의 경우 증감식으로 이동하며 while문과 do-while문은 조건식으로 이동한다.
반복문을 벗어나지 않고 다음 반복을 수행할 수 있기에 주로 if문과 함께 사용되여 특정 조건을 만족할 경우 continue문이 다음 코드를 수행하지 않고 다음 반복으로 넘어가서 진행하도록 한다.
- 홀수만 출력하는 에제
public static void main(String[] args) {
for (int i = 1; i < 30; i++) {
if (isEven(i)) {
continue;
}
System.out.println(i);
}
}
public static boolean isEven(int num) {
return num % 2 == 0;
}
======================================
1
3
5
...
27
29
이름 붙은 반복문
break문은 근접한 하나의 반복문만 벗어날 수 있기에, 여러 반복문이 중첩된 경우 break문으로 중첩 반복문을 완전히 벗어날 수 없다. 이때는 중첩 반복문 앞에 이름을 붙혀 break문과 continue문에 이름을 지정해 하나의 반복문을 벗어나거나 건너뛸 수 있다.
public static void main(String[] args) {
Loop1:
for (int i = 2; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
if (i + j > 6) {
continue Loop1;
}
System.out.printf("%d X %d = %d\n", i, j, i * j);
}
System.out.println();
}
}
====================================
2 X 1 = 2
2 X 2 = 4
2 X 3 = 6
2 X 4 = 8
3 X 1 = 3
3 X 2 = 6
3 X 3 = 9
4 X 1 = 4
4 X 2 = 8
5 X 1 = 5
public static void main(String[] args) {
Loop1:
for (int i = 2; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
if (j == 4) {
continue Loop1;
}
System.out.printf("%d X %d = %d\n", i, j, i * j);
}
System.out.println();
}
}
===========
2 X 1 = 2
2 X 2 = 4
2 X 3 = 6
3 X 1 = 3
....
9 X 1 = 9
9 X 2 = 18
9 X 3 = 27
과제 0. JUnit5 학습
JUnit이란?
- 자바 개발자의 93%가 사용하는 단위 테스트 프레임워크
- 스프링 부트 2.2버전 이상부터 기본 제공
JUnit5 란?
JUnit5 = Junit Platform + Junit Jupiter + Junit Vintage
JUnit5에서는 이전 버전의 Junit과는 다르게 3가지 하위 프로젝트의 여러 모듈로 구성된다.
- Platform : 테스트를 실행해주는 런처 제공. TestEngine API 제공
- Jupiter : JUnit 5를 지원하는 TestEngine API 구현체
- Vintage : JUnit 4와 3을 지원하는 TestEngine 구현체
추가적으로 JAVA 8부터 지원하며, 이전 버전으로 컴파일된 코드는 계속 테스트가 가능하다.
JUnit5 설정
스프링 부트 프로젝트
- 스프링 부트 2.2버전 이상부터는 기본적으로 JUnit5 의존성이 추가되어 있다.
스프링 부트 프로젝트가 아닐 경우
아래와 같이 의존성을 추가해준다.
Maven
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency>
Gradle
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.8.2'
의존성 사이트 : https://mvnrepository.com
테스트 클래스와 메서드
테스트 클래스
- 최상위 클래스, 스태틱 멤버 클래스,
@Nested
클래스에 적어도 한개의@Test
어노테이션이 달린 테스트 메서드가 포함되어 있는 것을 말한다. - 테스트 클래스는
abstract
이면 안되고, 하나의 생성자가 있어야 한다. ( 어차피 생성자가 없을 경우 컴파일러가 자동으로 만들어준다.)
테스트 메서드
@Test
,@RepeatedTest
,@ParamterizedTest
,@TestFactory
,@TestTemplate
같은 메타 어노테이션이 메소드에 붙여진 메소드를 말한다.
라이플사이클 메서드
@BeforeAll
,@AfterAll
,@BeforeEach
,@AfterEach
같은 메타 어노테이션이 메소드에 붙여진 메소드를 말한다.
테스트 메서드와 라이플사이클 메서드는 테스트 할 클래스, 상속한 부모클래스 또는 인터페이스에 선언된다. 추가로 테스트 메서드와 라이플사이클 메서드는 abstract
선언하면 안되고, 어떠한 값도 리턴되선 안된다.
또한, 접근제어자를 public
으로 선언을 꼭 안해줘도 되지만 private
로 선언하면 안된다.
Junit5 Annotations
기본 어노테이션
@Test
- 테스트 메서드라는 것을 나타내는 어노테이션
- Junit4와 다르게 어떠한 속성도 선언하지 않는다.
- Junit Jupiter의 테스트 확장 프로그램은 자체 전용 주석을 기반으로 작동하기 때문이다.
- 접근제한자가 Default여도 된다. (Junit4 까지는 public이어야 했다.)
@BeforeAll
/@AfterAll
- 해당 클래스에 위치한 모든 테스트 메서드 실행 전/후에 딱 한번 실행되는 메서드
- static void 여야 한다.
- JUnit4의
@BeforeClass
/@AfterClass
와 유사
@BeforeEach
/@AfterEach
- 해당 클래스에 위치한 모든 테스트 메서드 실행 전/후에 실행되는 메서드
- JUnit4의
@Before
/@After
와 유사 - 매 테스트 메서드마다 새로운 클래스를 생성(new)하여 실행 (비효율적)
@BeforeAll
과@BeforeEach
모두 여러 개의 테스트 조건을 setup할 때 사용한다.@BeforeAll
의 경우 한 번만 실행되므로 테스트가 조건들에 대해 어떠한 변경도 하지 않는다는 확신이 있을 때 사용한다.@BeforeEach
의 경우 여러번 실행되므로 테스트가 조건들에 영향을 미친다면 매 테스트 실행 때마다 조건들이 초기화시킬 때 사용한다.
@Disabled
- 테스트를 하고 싶지 않은 클래스나 메서드에 붙이는 어노테이션
- JUnit4의
@Ignore
과 유사
class JunitTest {
@BeforeAll
static void beforeAll() {
System.out.println("beforeAll");
}
@AfterAll
static void afterAll() {
System.out.println("afterAll");
}
@BeforeEach
void beforeEach() {
System.out.println("beforeEach");
}
@AfterEach
void afterEach() {
System.out.println("afterEach");
}
@Test
void test1() {
System.out.println("test1");
}
@Test
void test2() {
System.out.println("test2");
}
@Disabled
@Test
void test3() {
System.out.println("test3");
}
}
@RepeatedTest
- 특정 테스트를 반복시키고 싶을 때 사용하는 어노테이션
- 반복 횟수와 반복 테스트 이름을 설정가능
class JunitTest {
@RepeatedTest(10)
@DisplayName("횟수만 지정한 반복 테스트")
void repeatedTest() {
System.out.println("횟수 지정 반복 테스트");
}
@RepeatedTest(value = 10, name = "{displayName} 중 {currentRepetition} of {totalRepetitions}")
@DisplayName("이름도 지정한 반복 테스트")
void repeatedTest2() {
System.out.println("이름도 지정한 테스트");
}
}
@ParameterizedTest
- 테스트에 여러 다른 매개변수를 대입해가며 반복 실행할 때 사용하는 어노테이션
public class Junit { public static boolean isOdd(int number) { return number % 2 != 0; } } class JunitTest { @ParameterizedTest @ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE}) void isOdd_ShouldReturnTrueForOddNumbers(int number) { Assertions.assertTrue(Junit.isOdd(number)); } }
- 만약 JUnit5 의존성 추가시
junit-jupiter-api
만 추가했을 경우junit-jupiter-params
도 추가해줘야지 사용가능하다.
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.2' // api와 params를 한번에 추가할 경우 아래와 같이 추가해주면 된다. testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.8.2'
@Nested
- 테스트 클래스 안에서 내부 클래스를 정의해 테스트를 계층화할 때 사용한다.
- 내부클래스는 부모클래스의 멤버 필드에 접근 가능하다.
- Before / After와 같은 테스트 생명주기에 관계된 메소드들도 계층에 맞춰 동작한다.
public class Junit { public static boolean isOdd(int number) { return number % 2 != 0; } public static boolean isEven(int number) { return number % 2 == 0; } } class JunitTest { @DisplayName("isOdd 메서드는") @Nested class isOdd { @DisplayName("숫자가 홀수일 때 true를 반환한다.") @Test void returnTrue() { Assertions.assertTrue(Junit.isOdd(1)); } @DisplayName("숫자가 짝수일 때 false를 반환한다.") @Test void returnFalse() { Assertions.assertFalse(Junit.isOdd(2)); } } @DisplayName("isEven 메서드는") @Nested class isEven { @DisplayName("숫자가 홀수일 때 false를 반환한다.") @Test void returnTrue() { Assertions.assertTrue(Junit.isEven(2)); } @DisplayName("숫자가 짝수일 때 true를 반환한다.") @Test void returnFalse() { Assertions.assertFalse(Junit.isEven(1)); } } }
@TestFactory
@Test
로 선언된 정적 테스트가 아닌 동적으로 테스트를 사용한다.
@TestInstance
- 테스트 클래스의 생명주기를 설정한다.
@TestTemplate
- 공급자에 의해 여러 번 호출될 수 있도록 설계된 테스트 케이스 템플릿임을 나타낸다.
@TestMethodOrder
- 테스트 메서드 실행 순서를 구성하는 데 사용한다.
@Tag
- 클래스 또는 메서드 레벨에서 태그를 선언할 때 사용한다.
@Timeout
- 테스트 실행 시간을 선언 후 초과되면 실패하도록 설정한다.
@ExtendWith
- 확장을 선언적으로 등록할 때 사용한다.
@RegisterExtension
- 필드를 통해 프로그래밍 방식으로 확장을 등록할 때 사용한다.
@TempDir
- 필드 주입 또는 매개변수 주입을 통해 임시 디렉토리를 제공하는 데 사용한다.
테스트 명 표시 어노테이션
@DisplayName
- 어떤 테스트인지 쉽게 표현할 수 있도록 해주는 어노테이션
- 공백, Emoji, 특수문자 등을 모두 지원
@DisplayNameGeneration
- 테스트 클래스에 대한 사용자 정의 표시 이름 생성기를 선언한다.
- Method와 Class 래퍼런스를 사용해 테스트 이름을 표기하는 방법 설정으로 기본 구현체로 ReplaceUnderscores를 제공한다.
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class Junit_Test {
@Test
@DisplayName("이것은 DisplayName 테스트입니다.")
void test() {
}
}
Assertions
- 테스트 케이스의 수행 결과를 판별하는 메서드이다.
- 모든 Junit Jupiter Assertions는
static
메서드이다.
assertEquals(expected, actual, (message OR Supplier))
- 실제 값이 기대한 값과 같은지 확인
- 인자를 2개만 사용할 시 그 두개가 같은 값인지 비교
- 인자를 3개를 사용할 때 테스트가 실패하였을 경우 3번째 인자를 메시지로 출력
- 세번째 인자에 String이나 Supplier을 넣어줄 수 있는 데 String의 경우 테스트가 실패하든 성공하든 매번 메시지를 생성하지만, Supplier는 실패할 때만 메시지를 생성한다.
- 조그마한 성능차이에도 불안하다면 Supplier를 사용하자.
- 이는 assertTrue(), assertNotNull()도 마찬가지
public class Study { private final StudyStatus status; private final int limit; private final int numberOfMinEnrolment; public Study(int limit, int numberOfMinEnrolment) { if (numberOfMinEnrolment < 0) { throw new IllegalArgumentException("최소 참석인원은 0 보다 커야 합니다."); } status = StudyStatus.DRAFT; this.limit = limit; this.numberOfMinEnrolment = numberOfMinEnrolment; } public StudyStatus getStatus() { return status; } public int getLimit() { return limit; } public int getNumberOfMinEnrolment() { return numberOfMinEnrolment; } } public enum StudyStatus { DRAFT } class JunitTest { @Test void messageTest() { Study study = new Study(-50, 0); assertEquals(StudyStatus.DRAFT, study.getStatus(), "처음 스터디를 만들면 상태값은" + StudyStatus.DRAFT + "여야 한다."); assertEquals(StudyStatus.DRAFT, study.getStatus(), () -> "처음 스터디를 만들면 상태값은" + StudyStatus.DRAFT + "여야 한다."); assertEquals(StudyStatus.DRAFT, study.getStatus(), new Supplier<String>() { @Override public String get() { return "처음 스터디를 만들면 상태값은" + StudyStatus.DRAFT + "여야 한다."; } }); } }
실패할 경우
assertNotNull(actual)
- 값이 null이 아닌 지 확인
assertTrue(boolean)
- 다음 조건이 참인지 확인
public class Calculator {
public int add(int numberA, int numberB) {
return numberA + numberB;
}
public int multiply(int numberA, int numberB) {
return numberA * numberB;
}
public Integer divide(int numberA, int numberB) {
if (numberB == 0) {
return null;
}
return numberA / numberB;
}
}
class JunitTest {
@Test
void standardAssertions() {
Calculator calculator = new Calculator();
assertEquals(2, calculator.add(1, 1));
assertEquals(4, calculator.multiply(2, 2),
"The optional failure message is now the last parameter");
assertNotNull(calculator.divide(1, 1));
assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- "
+ "to avoid constructing complex messages unnecessarily.");
}
}
assertAll(executables ...)
매개변수로 받는 모든 테스트코드를 한번에 실행
오류가 나도 끝까지 실행한 뒤 한 번에 모아서 출력
인자로 람다식을 사용하며, 여러 개의 람다식이 동시에 실행되는 특징
원래 assertions는 assertion이 실패하면 그 밑의 코드는 더 이상 진행하지 않는다.
//위 Study 클래스 예제에 생성자 추가 public Study() { this.status = null; this.limit = -1; this.numberOfMinEnrolment = 0; } @Test void create_study() { Study study = new Study(); assertAll( () -> assertNotNull(study), () -> assertEquals(StudyStatus.DRAFT, study.getStatus(), () -> "스터디를 처음 만들면 " + StudyStatus.DRAFT + "상태 여야 합니다."), () -> assertTrue(study.getLimit() > 0, "스터디 최대 참석 가능 인원은 0보다 커야 합니다.") ); }
assertThrows(expectedType, executable)
예외 발생을 확인하는 테스트
executable의 로직이 실행하는 도중 expectedType의 에러를 발생시키는 지 확인
예외 발생을 검증하고 예외를 반환 받아서 예외의 상태까지 검증 가능
추가적으로 예외가 던져지지 않음을 검증하는 단정문인
assertDoesNotThrow
도 Junit 5.2부터 추가됨.@Test void exceptionThrows() { Exception e = assertThrows(Exception.class, () -> new Study(10, -10)); // IllegalArgumentException illegalArgumentException = // assertThrows(IllegalArgumentException.class, () -> new Study(10, -1)); String message = e.getMessage(); assertEquals("최소 참석인원은 0 보다 커야 합니다.", message); assertDoesNotThrow(() -> new Study(10, 5)); }
assertTimeout(duration, executable)
특정 시간 안에 실행이 완료되는 지 확인
Duration : 원하는 시간
Executable : 테스트할 로직
주의할 점은 원하는 시간이 지나자마자 테스트가 실패하는 게 아니라, Executable 코드가 모두 완료된 후에야 테스트 성공/실패를 결정짓는다.
이와 반대로
assertTimeoutPreemptively()
는 원하는 시간 안에 실행이 끝나지 않으면 바로 종료해 버린다.public class TimeOutExample { @Test @DisplayName("타임아웃 준수") void timeOutNotExceeded() { assertTimeout(Duration.ofMinutes(2), () -> { new Study(10, 10); Thread.sleep(10); }); } @Test @DisplayName("타임아웃 초과") void timeOutExceeded() { assertTimeout(Duration.ofMillis(10), () -> { new Study(10, 10); Thread.sleep(100); }); } } //Duration의 경우 static으로 import해서 사용하는 것이 더 깔끔하다.
Assumption
- 특정 조건(환경)을 전제하고 테스트를 수행할 수 있음
- 즉, 전제문이
true
라면 실행,false
라면 종료 - CI와 같이 특정 환경에서만 테스트를 진행해야 하는 경우 사용 가능
assumeTrue
- false일 때 이후 테스트 전체가 실행되지 않음
- false일 경우 테스트가 실패하는 것이 아닌 건너 뛰게 됨
assumingThat
- 파라미터로 전달된 코드블럭만 실행되지 않음
public class AssumptionsDemo {
private final Calculator calculator = new Calculator();
@Test
void dev_env_only() {
assumeTrue("DEV".equals(System.getenv("ENV")), () -> "개발 환경이 아닙니다.");
assertEquals(2, calculator.add(1, 1)); // 단정문이 실행되지 않음
}
@Test
void testInAllEnvironments() {
assumingThat("DEV".equals(System.getenv("ENV")),
() -> {
assertEquals(2, calculator.divide(4, 2)); // 단정문이 실행되지 않음
});
assertEquals(42, calculator.multiply(6, 7)); // 단정문이 실행됨
}
}
- assumeTrue를 통해 전제조건을 검증한 경우 결과가 false라면 이후 테스트들이 전부 실행되지 않음.
- assumingThat을 통해 검증한 경우 파라미터로 전달된 코드블럭만 실행되지 않고 (false인 경우) 그 이후에 테스트들은 정상적으로 수행하게 됨.
- dev_env_only()의
System.out(”ENV”)
는 환경변수를 가져오는 메서드이다.- 해당 메서드를 통해 현재 환경변수를 가져오고, 이것이 DEV와 같다면 테스트를 실행하게 된다.
과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.
- 깃헙 이슈 1 ~ 18번까지 댓글 순회 후 댓글 남긴 사용자 체크
- 참여율 계산
- 총 18회 중 몇 %를 참여했는 지 소숫점 두자리 까지 보이기
구현
Dashboard.java
public class Dashboard {
private static final int endIndex = 15;
private final GHRepository repository;
private final List<Participant> participants;
public Dashboard(String token, String repositoryName) throws IOException {
GitHub gitHub = new GitHubBuilder().withOAuthToken(token).build();
this.repository = gitHub.getRepository(repositoryName);
this.participants = new ArrayList<>();
}
public void printBoard() throws IOException {
for (int index = 1; index <= endIndex; index++) {
GHIssue issue = repository.getIssue(index);
List<GHIssueComment> comments = issue.getComments();
for (GHIssueComment comment : comments) {
if (Objects.isNull(comment.getUser().getName())) {
continue;
}
Participant participant = findParticipant(participants, comment.getUser().getName());
participant.markStatus(index);
}
}
Collections.sort(participants);
for (Participant participant : participants) {
System.out.printf("| %s %s | %.2f%% |\n", participant.getUserName(), checkMark(participant),
participant.getRate(endIndex));
}
}
private Participant findParticipant(List<Participant> participants, String userName) {
if (isNewUser(participants, userName)) {
Participant participant = new Participant(userName);
participants.add(participant);
return participant;
}
return participants.stream()
.filter(p -> p.isSameUser(userName))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
private boolean isNewUser(List<Participant> participants, String userName) {
return participants.stream()
.noneMatch(p -> p.isSameUser(userName));
}
private String checkMark(Participant participant) {
StringBuilder result = new StringBuilder();
for (int index = 1; index <= endIndex; index++) {
if (participant.isMarking(index) && participant.haveResult(index)) {
result.append("| O ");
} else {
result.append("| ");
}
}
return result.toString();
}
Participant.java
public class Participant implements Comparable<Participant> {
private final String userName;
private final Map<Integer, Boolean> status;
public Participant(String userName) {
this.userName = userName;
this.status = new HashMap<>();
}
public double getRate(double total) {
long count = status.values()
.stream()
.filter(value -> value)
.count();
return count * 100 / total;
}
public void markStatus(int index) {
this.status.put(index, true);
}
public boolean isSameUser(String userName) {
return this.userName.equals(userName);
}
public boolean isMarking(int index) {
return status.containsKey(index);
}
public boolean haveResult(int index) {
return status.get(index);
}
public String getUserName() {
return userName;
}
@Override
public int compareTo(Participant o) {
return this.userName.compareTo(o.userName);
}
}
Application.java
public class Application {
public static void main(String[] args) throws IOException {
Dashboard dashboard = new Dashboard("토큰", "레포지토리이름");
dashboard.printBoard();
}
}
결과 (일부분)
문제점
과제에는 1 ~ 18번까지의 이슈를 봐야하지만 16번 이슈를 호출하게 되면 에러가 발생했다.
- 이에 확인해보니 15번 이슈 이후 16번 이슈는 없고 zeze라는 28번 이슈가 나와 생긴 오류였다...
- 아마 스터디가 진행되면서 변경된 듯하다.
- 이에
endIndex
를 15까지 설정하여 진행하였다.
@Test void DashboardTest() throws IOException { GitHub gitHub = new GitHubBuilder().withOAuthToken("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").build(); GHRepository repository = gitHub.getRepository("whiteship/live-study"); List<GHIssue> issues = new ArrayList<>(repository.getIssues(GHIssueState.ALL)); issues.sort(Comparator.comparingInt(GHIssue::getNumber)); for (GHIssue issue : issues) { System.out.println(issue.getNumber() + " " +issue.getTitle()); } }
- 이슈를 모두 확인하는 테스트 코드를 작성하던 중 그냥 출력하면 내림차순으로 출력해주기 위해 정렬하였지만
UnsupportedOperationException
가 발생하였다. - 확인해보니
getIssue
메서드가toList
메서드로 리스트를 반환해주는 데 이 메서드는Collections.unmodifiableList
를 사용하여 불변 리스트로 반환해주기 때문에 정렬이 불가능한 것이였다. - 그래서 바로 사용하지 않고
new ArrayList<>()
로 한번 감싸주어 가변 리스트로 사용하여 정렬해주었다.
- 댓글 남긴 사용자의 이름을 가져오는 도중 에러가 발생하였다.
- 확인해보니 댓글을 남긴 사용자가 탈퇴한 경우
null
로 반환해주고 있어null
체크를 해주어 문제를 해결하였다.
- 확인해보니 댓글을 남긴 사용자가 탈퇴한 경우
과제 2. LinkedList를 구현하세요.
구현
ListNode.java
public class ListNode {
private final int data;
private ListNode nextNode;
public ListNode(int data) {
this.data = data;
this.nextNode = null;
}
public void linking(ListNode nextNode) {
this.nextNode = nextNode;
}
public ListNode next() {
return nextNode;
}
public int getData() {
return data;
}
}
LinkedList.java
public class LinkedList {
private ListNode head;
private ListNode tail;
private int size;
public LinkedList() {
this.head = null;
this.tail = null;
this.size = 0;
}
public ListNode add(ListNode nodeToAdd, int position) {
if (position < 0 || position > size) {
throw new IndexOutOfBoundsException();
}
if (position == 0) {
return addFirst(nodeToAdd);
}
if (position == size) {
return addLast(nodeToAdd);
}
ListNode prevNode = search(position - 1);
ListNode nextNode = prevNode.next();
prevNode.linking(nodeToAdd);
nodeToAdd.linking(nextNode);
size++;
return nodeToAdd;
}
private ListNode addFirst(ListNode nodeToAdd) {
nodeToAdd.linking(head);
this.head = nodeToAdd;
size++;
if (head.next() == null) {
this.tail = head;
}
return nodeToAdd;
}
private ListNode addLast(ListNode nodeToAdd) {
tail.linking(nodeToAdd);
tail = nodeToAdd;
size++;
return nodeToAdd;
}
private ListNode search(int position) {
if (position < 0 || position >= size) {
throw new IndexOutOfBoundsException();
}
ListNode findNode = head;
for (int index = 0; index < position; index++) {
findNode = findNode.next();
}
return findNode;
}
public ListNode remove(int positionToRemove) {
if (positionToRemove < 0 || positionToRemove >= size) {
throw new IndexOutOfBoundsException();
}
if (positionToRemove == 0) {
return removeHead();
}
ListNode prevNode = search(positionToRemove - 1);
ListNode removeNode = prevNode.next();
ListNode nextNode = removeNode.next();
prevNode.linking(nextNode);
if (prevNode.next() == null) {
tail = prevNode;
}
removeNode.linking(null);
size--;
return removeNode;
}
private ListNode removeHead() {
ListNode headNode = head;
if (headNode == null) {
throw new NoSuchElementException();
}
ListNode nextNode = head.next();
headNode.linking(null);
head = nextNode;
size--;
if (size == 0) {
tail = null;
}
return headNode;
}
public boolean contains(ListNode nodeToCheck) {
ListNode findNode = head;
while (findNode != null) {
if (findNode.equals(nodeToCheck)) {
return true;
}
findNode = findNode.next();
}
return false;
}
}
테스트
LinkedListTest.java
@DisplayName("LinkedList 테스트")
class LinkedListTest {
@DisplayName("add 메서드는")
@Nested
class Describe_add {
@DisplayName("잘못된 position이 주어졌다면")
@Nested
class Context_with_invalid_position {
final int FIRST_NODE_VALUE = 10;
@DisplayName("예외를 던진다.")
@ParameterizedTest
@ValueSource(ints = {2, -1, 5})
void it_throws_IndexOutOfBoundsException(int INVALID_POSITION) {
LinkedList linkedList = new LinkedList();
assertThrows(IndexOutOfBoundsException.class,
() -> linkedList.add(new ListNode(FIRST_NODE_VALUE), INVALID_POSITION));
}
}
@DisplayName("유효한 position이 주어졌다면")
@Nested
class Context_with_valid_position {
@DisplayName("맨 앞의 경우")
@Nested
class Sub_context_with_front {
final int FIRST_NODE_VALUE = 10;
final int FIRST_POSITION = 0;
@DisplayName("리턴 값이 head와 같다.")
@Test
void it_returns_list_node_same_head() throws NoSuchFieldException, IllegalAccessException {
LinkedList linkedList = new LinkedList();
ListNode addNode = linkedList.add(new ListNode(FIRST_NODE_VALUE), FIRST_POSITION);
Field headField = linkedList.getClass().getDeclaredField("head");
headField.setAccessible(true);
assertEquals(addNode, headField.get(linkedList));
}
}
@DisplayName("첫 번째 노드와 두 번째 노드 사이에 넣을 경우")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Nested
class Sub_context_with_between_first_second {
final int FIRST_NODE_VALUE = 10;
final int SECOND_NODE_VALUE = 5;
final int BETWEEN_NODE_VALUE = 3;
final int FIRST_POSITION = 0;
final int SECOND_POSITION = 1;
ListNode first, second, between, head;
@BeforeAll
void prepare_add_test() throws IllegalAccessException, NoSuchFieldException {
LinkedList linkedList = new LinkedList();
first = linkedList.add(new ListNode(FIRST_NODE_VALUE), FIRST_POSITION);
second = linkedList.add(new ListNode(SECOND_NODE_VALUE), SECOND_POSITION);
between = linkedList.add(new ListNode(BETWEEN_NODE_VALUE), SECOND_POSITION);
Field headField = linkedList.getClass().getDeclaredField("head");
headField.setAccessible(true);
head = (ListNode)headField.get(linkedList);
}
@DisplayName("리턴 값이 head의 다음 노드와 같다.")
@Test
void it_returns_same_head_next() {
assertAll(
() -> assertEquals(head, first),
() -> assertNotEquals(head.next(), second),
() -> assertEquals(head.next(), between)
);
}
@DisplayName("리턴 값의 다음 노드는 두번째 삽입한 노드와 같다.")
@Test
void it_returns_same_second_add_node() {
assertEquals(second, between.next());
}
}
}
}
@DisplayName("remove 메서드는")
@Nested
class Describe_remove {
@DisplayName("잘못된 position이 주어졌다면")
@Nested
class Context_with_invalid_position {
final int FIRST_NODE_VALUE = 10;
final int INVALID_POSITION = 1;
@DisplayName("예외를 던진다.")
@ParameterizedTest
@ValueSource(ints = {1, -1, 5})
void it_throws_IndexOutOfBoundsException() {
LinkedList linkedList = new LinkedList();
linkedList.add(new ListNode(FIRST_NODE_VALUE), 0);
assertThrows(IndexOutOfBoundsException.class,
() -> linkedList.remove(INVALID_POSITION));
}
}
@DisplayName("유효한 position이 주어졌다면")
@Nested
class Context_with_valid_position {
final int FIRST_NODE_VALUE = 10;
final int SECOND_NODE_VALUE = 5;
final int THIRD_NODE_VALUE = 3;
final int FIRST_POSITION = 0;
final int SECOND_POSITION = 1;
final int THIRD_POSITION = 2;
@DisplayName("두 개의 노드가 존재하고 맨 앞을 삭제했을 경우")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Nested
class Sub_context_with_front_remove {
ListNode first, second, removed, head;
@BeforeAll
void prepare_remove_front_node() throws NoSuchFieldException, IllegalAccessException {
LinkedList linkedList = new LinkedList();
first = linkedList.add(new ListNode(FIRST_NODE_VALUE), FIRST_POSITION);
second = linkedList.add(new ListNode(SECOND_NODE_VALUE), SECOND_POSITION);
removed = linkedList.remove(FIRST_POSITION);
Field headField = linkedList.getClass().getDeclaredField("head");
headField.setAccessible(true);
head = (ListNode)headField.get(linkedList);
}
@DisplayName("리턴 값은 첫번째 노드와 같다.")
@Test
void it_returns_same_first_add_node() {
assertEquals(first, removed);
}
@DisplayName("두 번째 삽입한 노드와 head와 같다.")
@Test
void it_second_add_node_same_head() {
assertEquals(head, second);
}
}
@DisplayName("세 개의 노드가 존재하고 중간을 삭제했을 경우")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Nested
class sub_context_with_between_remove {
ListNode first, second, third, removed;
@BeforeAll
void prepare_remove_between_node() throws NoSuchFieldException, IllegalAccessException {
LinkedList linkedList = new LinkedList();
first = linkedList.add(new ListNode(FIRST_NODE_VALUE), FIRST_POSITION);
second = linkedList.add(new ListNode(SECOND_NODE_VALUE), SECOND_POSITION);
third = linkedList.add(new ListNode(THIRD_NODE_VALUE), THIRD_POSITION);
removed = linkedList.remove(SECOND_POSITION);
}
@DisplayName("리턴 값은 두번째 삽입 노드와 같다.")
@Test
void it_returns_same_second_add_node() {
assertEquals(second, removed);
}
@DisplayName("리턴 값은 다음 노드를 가지지 않는다.")
@Test
void it_returns_remove_next_node() {
assertNull(removed.next());
}
@DisplayName("첫 번째 삽입 노드의 다음 노드는 세번째 삽입한 노드와 같다.")
@Test
void it_same_first_node_next_third_add_node() {
assertAll(
() -> assertEquals(third, first.next()),
() -> assertNotEquals(second, first.next())
);
}
}
}
}
@DisplayName("contains 메서드는")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Nested
class Describe_contains {
final int FIRST_NODE_VALUE = 10;
final int SECOND_NODE_VALUE = 5;
final int THIRD_NODE_VALUE = 3;
final int NOT_CONTAINS_NODE_VALUE = -1;
final int FIRST_POSITION = 0;
final int SECOND_POSITION = 1;
final int THIRD_POSITION = 2;
LinkedList linkedList;
ListNode first, second, third, contains, notContains;
@BeforeAll
void prepare_contains_test() {
linkedList = new LinkedList();
first = linkedList.add(new ListNode(FIRST_NODE_VALUE), FIRST_POSITION);
second = linkedList.add(new ListNode(SECOND_NODE_VALUE), SECOND_POSITION);
third = linkedList.add(new ListNode(THIRD_NODE_VALUE), THIRD_POSITION);
contains = first;
notContains = new ListNode(NOT_CONTAINS_NODE_VALUE);
}
@DisplayName("연결 리스트에 존재하는 노드가 주어질 경우")
@Nested
class Context_with_contains {
@DisplayName("true를 리턴한다.")
@Test
void it_returns_true() {
assertTrue(linkedList.contains(contains));
}
}
@Nested
@DisplayName("연결 리스트에 존재하지 않는 노드가 주어질 경우")
class Context_with_not_contains {
@DisplayName("false를 리턴한다.")
@Test
void it_returns_false() {
assertFalse(linkedList.contains(notContains));
}
}
}
}
배운 점
이때까지 테스트를 진행할 땐
Given - When - Then
패턴을 많이 사용했다.- 하지만 테스트를 공부하던 중
Describe - Context - It
패턴을 알게 되었고 계층적인 테스트를 위해 사용해 보았다. - https://johngrib.github.io/wiki/junit5-nested/
- 하지만 테스트를 공부하던 중
이전에
private
메서드를 테스트할 때 Java의 Reflection을 사용하여private
메서드를 테스트해본 적이 있다.- 메서드 뿐만 아니라 변수까지도 접근할 수 있는 방법을 알게 되어 사용해보았다.
Field headField = linkedList.getClass().getDeclaredField("head"); headField.setAccessible(true); ListNode head = (ListNode)headField.get(linkedList);
과제 3. Stack을 구현하세요.
구현
ArrayStack.java
public class ArrayStack {
private static final int DEFAULT_CAPACITY = 10;
private static final int[] EMPTY_ARRAY = {};
private int[] array;
private int size;
public ArrayStack() {
this.array = new int[DEFAULT_CAPACITY];
this.size = 0;
}
public ArrayStack(int capacity) {
this.array = new int[capacity];
this.size = 0;
}
public void push(int data) {
if (this.size == array.length) {
resize();
}
array[this.size] = data;
size++;
}
public int pop() {
if (size == 0) {
throw new EmptyStackException();
}
int data = array[size - 1];
size--;
resize();
return data;
}
private void resize() {
if (Arrays.equals(array, EMPTY_ARRAY)) {
array = new int[DEFAULT_CAPACITY];
return;
}
int arrayCapacity = array.length;
if (size == arrayCapacity) {
int newCapacity = arrayCapacity * 2;
array = Arrays.copyOf(array, newCapacity);
return;
}
if (size < (arrayCapacity / 2)) {
int newCapacity = arrayCapacity / 2;
array = Arrays.copyOf(array, Math.max(DEFAULT_CAPACITY, newCapacity));
}
}
}
테스트
ArrayStackTest.java
@DisplayName("Stack 테스트")
class ArrayStackTest {
@DisplayName("생성자는")
@Nested
class Describe_constructor {
private int getArraySize(ArrayStack stack) throws IllegalAccessException, NoSuchFieldException {
Field array = stack.getClass().getDeclaredField("array");
array.setAccessible(true);
return ((int[])array.get(stack)).length;
}
@DisplayName("아무 인자를 주지 않았다면")
@Nested
class Context_with_no_param {
@DisplayName("크기가 10인 스택이 생성된다")
@Test
void it_created_stack_size_ten() throws NoSuchFieldException, IllegalAccessException {
final int DEFAULT_CAPACITY = 10;
ArrayStack stack = new ArrayStack();
int arrayCapacity = getArraySize(stack);
assertEquals(DEFAULT_CAPACITY, arrayCapacity);
}
}
@DisplayName("인자를 주었다면")
@Nested
class Context_with_param {
@DisplayName("준 인자 크기의 스택이 생성된다")
@Test
void it_created_stack_size_give_param() throws NoSuchFieldException, IllegalAccessException {
final int CAPACITY = 20;
ArrayStack stack = new ArrayStack(20);
int arrayCapacity = getArraySize(stack);
assertEquals(CAPACITY, arrayCapacity);
}
}
}
@DisplayName("push 메서드는")
@Nested
class Describe_push {
@DisplayName("3개의 데이터가 들어갔을 때")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Nested
class Context_with_three_data_push {
final int FIRST_DATA = 10;
final int SECOND_DATA = 20;
final int THIRD_DATA = 30;
final int STACK_SIZE = 3;
ArrayStack stack;
@BeforeAll
void setup() {
stack = new ArrayStack();
stack.push(FIRST_DATA);
stack.push(SECOND_DATA);
stack.push(THIRD_DATA);
}
@DisplayName("최상위 데이터는 세번째 데이터와 같다")
@Test
void it_top_data_same_third_data() throws IllegalAccessException, NoSuchFieldException {
Field array = stack.getClass().getDeclaredField("array");
array.setAccessible(true);
Field size = stack.getClass().getDeclaredField("size");
size.setAccessible(true);
int topData = ((int[])array.get(stack))[(int)size.get(stack) - 1];
assertEquals(THIRD_DATA, topData);
}
@DisplayName("스택 크기는 3이다.")
@Test
void it_stack_size_three() throws IllegalAccessException, NoSuchFieldException {
Field size = stack.getClass().getDeclaredField("size");
size.setAccessible(true);
int stackSize = (int)size.get(stack);
assertEquals(STACK_SIZE, stackSize);
}
}
@DisplayName("스택이 꽉 찬 상태라면")
@Nested
class Context_with_stack_full {
final int CAPCITY = 2;
ArrayStack stack;
@DisplayName("새로운 용적을 현재 용적의 2배로 설정한다.")
@Test
void it_capacity_double() throws IllegalAccessException, NoSuchFieldException {
stack = new ArrayStack(CAPCITY);
for (int i = 0; i < 3; i++) {
stack.push(i);
}
Field array = stack.getClass().getDeclaredField("array");
array.setAccessible(true);
int arraySize = ((int[])array.get(stack)).length;
assertEquals(CAPCITY * 2, arraySize);
}
}
}
@DisplayName("pop 메서드는")
@Nested
class Describe_pop {
@DisplayName("스택이 비워져 있을 때")
@Nested
class Context_with_empty_stack {
@DisplayName("예외를 발생한다.")
@Test
void it_thorws_exception() {
ArrayStack stack = new ArrayStack();
assertThrows(EmptyStackException.class, stack::pop);
}
}
@DisplayName("3개의 데이터가 존재할 경우")
@Nested
class Context_with_exist_three_data {
final int FIRST_DATA = 10;
final int SECOND_DATA = 20;
final int THIRD_DATA = 30;
ArrayStack stack;
@BeforeEach
void setup() {
stack = new ArrayStack();
stack.push(FIRST_DATA);
stack.push(SECOND_DATA);
stack.push(THIRD_DATA);
}
@DisplayName("리턴 값과 마지막으로 넣은 데이터가 같다.")
@Test
void it_returns_same_last_push_data() {
assertEquals(THIRD_DATA, stack.pop());
}
@DisplayName("최상위 데이터는 두번째로 넣은 값과 같다.")
@Test
void it_top_data_same_second_data() throws IllegalAccessException, NoSuchFieldException {
stack.pop();
Field array = stack.getClass().getDeclaredField("array");
array.setAccessible(true);
Field size = stack.getClass().getDeclaredField("size");
size.setAccessible(true);
int topData = ((int[])array.get(stack))[(int)size.get(stack) - 1];
assertEquals(SECOND_DATA, topData);
}
}
@DisplayName("스택이 용적의 반보다 작아질 때")
@Nested
class Context_with_stack_size_half {
final int OVER_CAPACITY = 30;
final int NOT_OVER_CAPACITY = 18;
final int DEFAULT_CAPACITY = 10;
ArrayStack stack;
@DisplayName("용적의 반이 기본 용적보다 클 경우")
@Nested
class SubContext_with_stack_size_half_over_default_size {
@DisplayName("새로운 용적을 용적의 반으로 설정한다.")
@Test
void it_capcity_size_half() throws IllegalAccessException, NoSuchFieldException {
stack = new ArrayStack(OVER_CAPACITY);
for (int i = 0; i < 15; i++) {
stack.push(i);
}
stack.pop();
Field array = stack.getClass().getDeclaredField("array");
array.setAccessible(true);
int arraySize = ((int[])array.get(stack)).length;
assertEquals(OVER_CAPACITY / 2, arraySize);
}
}
@DisplayName("용적의 반이 기본 용적보다 작을 경우")
@Nested
class SubContext_with_stack_size_half_not_over_default_size {
@DisplayName("새로운 용적을 기본 용적으로 설정한다.")
@Test
void it_capcity_default() throws IllegalAccessException, NoSuchFieldException {
stack = new ArrayStack(NOT_OVER_CAPACITY);
for (int i = 0; i < 9; i++) {
stack.push(i);
}
stack.pop();
Field array = stack.getClass().getDeclaredField("array");
array.setAccessible(true);
int arraySize = ((int[])array.get(stack)).length;
assertEquals(DEFAULT_CAPACITY, arraySize);
}
}
}
}
}
과제 4. 앞서 만든 ListNode를 사용해서 Stack을 구현하세요.
구현
ListNodeStack.java
public class ListNodeStack {
private ListNode head;
private int size;
public ListNodeStack() {
this.head = null;
this.size = 0;
}
public void push(int data) {
if (head == null) {
head = new ListNode(data);
size++;
return;
}
ListNode lastNode = head;
while (lastNode.next() != null) {
lastNode = lastNode.next();
}
lastNode.linking(new ListNode(data));
size++;
}
public int pop() {
if (head == null) {
throw new EmptyStackException();
}
ListNode beforeNode = null;
ListNode lastNode = head;
while (lastNode.next() != null) {
beforeNode = lastNode;
lastNode = lastNode.next();
}
int data = lastNode.getData();
if (beforeNode == null) {
head = null;
} else {
beforeNode.linking(null);
}
size--;
return data;
}
}
ListNodeStackTest.java
테스트
@DisplayName("ListNodeStack 테스트")
class ListNodeStackTest {
@DisplayName("push 메서드는")
@Nested
class Describe_push {
@DisplayName("3개의 데이터가 들어갔을 때")
@Nested
class Context_with_three_data_push {
final int FIRST_DATA = 10;
final int SECOND_DATA = 20;
final int THIRD_DATA = 30;
final int STACK_SIZE = 3;
ListNodeStack stack;
@BeforeEach
void setup() {
stack = new ListNodeStack();
stack.push(FIRST_DATA);
stack.push(SECOND_DATA);
stack.push(THIRD_DATA);
}
@DisplayName("최상위 데이터는 세번째 데이터와 같다")
@Test
void it_top_data_same_third_data() throws IllegalAccessException, NoSuchFieldException {
assertEquals(THIRD_DATA, stack.pop());
}
@DisplayName("스택 크기는 3이다.")
@Test
void it_stack_size_three() throws IllegalAccessException, NoSuchFieldException {
Field size = stack.getClass().getDeclaredField("size");
size.setAccessible(true);
int stackSize = (int)size.get(stack);
assertEquals(STACK_SIZE, stackSize);
}
}
@DisplayName("1개의 데이터가 들어갔을 때")
@Nested
class Context_with_one_data_push {
final int FIRST_DATA = 10;
final int STACK_SIZE = 1;
ListNodeStack stack;
@DisplayName("스택 크기는 1이다.")
@Test
void it_stack_size_one() throws IllegalAccessException, NoSuchFieldException {
Field size = stack.getClass().getDeclaredField("size");
size.setAccessible(true);
int stackSize = (int)size.get(stack);
assertEquals(STACK_SIZE, stackSize);
}
@BeforeEach
void setup() {
stack = new ListNodeStack();
stack.push(FIRST_DATA);
}
@DisplayName("최상위 데이터는 head와 같다.")
@Test
void it_top_data_same_head() throws NoSuchFieldException, IllegalAccessException {
Field head = stack.getClass().getDeclaredField("head");
head.setAccessible(true);
assertEquals(FIRST_DATA, ((ListNode)head.get(stack)).getData());
}
}
}
@DisplayName("pop 메서드는")
@Nested
class Describe_pop {
@DisplayName("스택이 비워져 있을 때")
@Nested
class Context_with_empty_stack {
@DisplayName("예외를 발생한다.")
@Test
void it_thorws_exception() {
ListNodeStack stack = new ListNodeStack();
assertThrows(EmptyStackException.class, stack::pop);
}
}
@DisplayName("3개의 데이터가 존재할 경우")
@Nested
class Context_with_exist_three_data {
final int FIRST_DATA = 10;
final int SECOND_DATA = 20;
final int THIRD_DATA = 30;
ListNodeStack stack;
@BeforeEach
void setup() {
stack = new ListNodeStack();
stack.push(FIRST_DATA);
stack.push(SECOND_DATA);
stack.push(THIRD_DATA);
}
@DisplayName("리턴 값과 마지막으로 넣은 데이터가 같다.")
@Test
void it_returns_same_last_push_data() {
assertEquals(THIRD_DATA, stack.pop());
}
@DisplayName("최상위 데이터는 두번째로 넣은 값과 같다.")
@Test
void it_top_data_same_second_data() throws IllegalAccessException, NoSuchFieldException {
stack.pop();
assertEquals(SECOND_DATA, stack.pop());
}
}
}
}
과제 5. Queue를 구현하세요.
배열 기반 Queue
구현
ArrayQueue.java
public class ArrayQueue {
private static final int DEFAULT_CAPACITY = 64;
private int[] array;
private int size;
private int front;
private int rear;
public ArrayQueue() {
this.array = new int[DEFAULT_CAPACITY];
this.size = 0;
this.front = 0;
this.rear = 0;
}
public ArrayQueue(int capacity) {
this.array = new int[capacity];
this.size = 0;
this.front = 0;
this.rear = 0;
}
private void resize(int newCapacity) {
int arrayCapacity = array.length;
int[] newArray = new int[newCapacity];
for (int i = 1, j = front + 1; i <= size; i++, j++) {
newArray[i] = array[j % arrayCapacity];
}
this.array = newArray;
front = 0;
rear = size;
}
public boolean offer(int data) {
if (isFull()) {
resize(array.length * 2);
}
rear = (rear + 1) % array.length;
array[rear] = data;
size++;
return true;
}
private boolean isFull() {
return (rear + 1) % array.length == front;
}
//본래 add는 용적이 제한되어있는 데 용적보다 많은 요소를 추가할 경우 예외를 던지지만
//여기서는 최대 제한을 걸지 않았기 때문에 넘어간다.
public boolean add(int data) {
return offer(data);
}
public Integer poll() {
if (isEmpty()) {
return null;
}
front = (front + 1) % array.length;
int data = array[front];
size--;
if (array.length > DEFAULT_CAPACITY && size < (array.length / 4)) {
resize(Math.max(DEFAULT_CAPACITY, array.length / 2));
}
return data;
}
public int remove() {
Integer data = poll();
if (data == null) {
throw new NoSuchElementException();
}
return data;
}
public Integer peek() {
if (isEmpty()) {
return null;
}
int data = array[(front + 1) % array.length];
return data;
}
public int element() {
Integer data = peek();
if (data == null) {
throw new NoSuchElementException();
}
return data;
}
private boolean isEmpty() {
return size == 0;
}
}
테스트
ArrayQueueTest.java
@DisplayName("ArrayQueue 테스트")
class ArrayQueueTest {
@DisplayName("생성자는")
@Nested
class Describe_constructor {
private int getArraySize(ArrayQueue queue) throws IllegalAccessException, NoSuchFieldException {
Field array = queue.getClass().getDeclaredField("array");
array.setAccessible(true);
return ((int[])array.get(queue)).length;
}
@DisplayName("아무 인자를 주지 않았다면")
@Nested
class Context_with_no_param {
@DisplayName("크기가 64인 큐가 생성된다")
@Test
void it_created_queue_size_sixty_four() throws NoSuchFieldException, IllegalAccessException {
final int DEFAULT_CAPACITY = 64;
ArrayQueue queue = new ArrayQueue();
int arrayCapacity = getArraySize(queue);
assertEquals(DEFAULT_CAPACITY, arrayCapacity);
}
}
@DisplayName("인자를 주었다면")
@Nested
class Context_with_param {
@DisplayName("준 인자 크기의 큐가 생성된다")
@Test
void it_created_stack_size_give_param() throws NoSuchFieldException, IllegalAccessException {
final int CAPACITY = 20;
ArrayQueue queue = new ArrayQueue(20);
int arrayCapacity = getArraySize(queue);
assertEquals(CAPACITY, arrayCapacity);
}
}
}
@DisplayName("offer 메서드는")
@Nested
class Describe_offer {
@DisplayName("여러개의 데이터를 삽입한다면")
@Nested
class Context_with_insert_data {
final int FIRST_DATA_VALUE = 5;
final int SECOND_DATA_VALUE = 4;
final int THIRD_DATA_VALUE = 3;
final int INITIAL_INDEX = 0;
ArrayQueue queue;
@BeforeEach
void setup() {
queue = new ArrayQueue();
}
private int getFieldFromQueue(String str) throws NoSuchFieldException, IllegalAccessException {
Field field = queue.getClass().getDeclaredField(str);
field.setAccessible(true);
return (int)field.get(queue);
}
@DisplayName("각각 참을 반환한다.")
@Test
void it_returns_true() {
assertAll(
() -> assertTrue(queue.offer(FIRST_DATA_VALUE)),
() -> assertTrue(queue.offer(SECOND_DATA_VALUE)),
() -> assertTrue(queue.offer(THIRD_DATA_VALUE))
);
}
@DisplayName("rear 필드만 1씩 증가한다.")
@Test
void it_increased_rear() {
assertAll(
() -> {
int front = getFieldFromQueue("front");
int rear = getFieldFromQueue("rear");
assertAll(
() -> assertEquals(INITIAL_INDEX, front),
() -> assertEquals(INITIAL_INDEX, rear)
);
}
);
assertAll(
() -> {
queue.add(FIRST_DATA_VALUE);
int front = getFieldFromQueue("front");
int rear = getFieldFromQueue("rear");
assertAll(
() -> assertEquals(INITIAL_INDEX, front),
() -> assertEquals(INITIAL_INDEX + 1, rear)
);
}
);
assertAll(
() -> {
queue.add(SECOND_DATA_VALUE);
int front = getFieldFromQueue("front");
int rear = getFieldFromQueue("rear");
assertAll(
() -> assertEquals(INITIAL_INDEX, front),
() -> assertEquals(INITIAL_INDEX + 2, rear)
);
}
);
}
}
@DisplayName("큐가 꽉 찬 상태라면")
@Nested
class Context_with_queue_full {
final int CAPCITY = 2;
ArrayQueue queue;
@DisplayName("새로운 용적을 현재 용적의 2배로 설정한다.")
@Test
void it_capacity_double() throws IllegalAccessException, NoSuchFieldException {
queue = new ArrayQueue(CAPCITY);
for (int i = 0; i < 3; i++) {
queue.offer(i);
}
Field array = queue.getClass().getDeclaredField("array");
array.setAccessible(true);
int arraySize = ((int[])array.get(queue)).length;
assertEquals(CAPCITY * 2, arraySize);
}
}
}
@DisplayName("poll 메서드는")
@Nested
class Describe_poll {
@DisplayName("큐가 비어있는 상태라면")
@Nested
class Context_with_queue_empty {
@DisplayName("null을 반환한다.")
@Test
void it_returns_null() {
ArrayQueue queue = new ArrayQueue();
assertNull(queue.poll());
}
}
@DisplayName("여러개의 데이터가 존재할 때")
@Nested
class Context_with_exist_data {
final int FIRST_DATA_VALUE = 5;
final int SECOND_DATA_VALUE = 4;
final int THIRD_DATA_VALUE = 3;
final int INITIAL_FRONT = 0;
final int INITIAL_REAR = 3;
ArrayQueue queue;
@BeforeEach
void setup() {
queue = new ArrayQueue();
queue.offer(FIRST_DATA_VALUE);
queue.offer(SECOND_DATA_VALUE);
queue.offer(THIRD_DATA_VALUE);
}
private int getFieldFromQueue(String str) throws NoSuchFieldException, IllegalAccessException {
Field field = queue.getClass().getDeclaredField(str);
field.setAccessible(true);
return (int)field.get(queue);
}
@DisplayName("가장 앞에 있는 데이터를 반환한다.")
@Test
void it_returns_first_data() {
assertAll(
() -> assertEquals(FIRST_DATA_VALUE, queue.poll()),
() -> assertEquals(SECOND_DATA_VALUE, queue.poll())
);
}
@DisplayName("front 필드만 1씩 증가한다.")
void it_increased_front() {
assertAll(
() -> {
int front = getFieldFromQueue("front");
int rear = getFieldFromQueue("rear");
assertAll(
() -> assertEquals(INITIAL_FRONT, front),
() -> assertEquals(INITIAL_REAR, rear)
);
}
);
assertAll(
() -> {
queue.add(FIRST_DATA_VALUE);
int front = getFieldFromQueue("front");
int rear = getFieldFromQueue("rear");
assertAll(
() -> assertEquals(INITIAL_FRONT + 1, front),
() -> assertEquals(INITIAL_REAR, rear)
);
}
);
assertAll(
() -> {
queue.add(FIRST_DATA_VALUE);
int front = getFieldFromQueue("front");
int rear = getFieldFromQueue("rear");
assertAll(
() -> assertEquals(INITIAL_FRONT + 2, front),
() -> assertEquals(INITIAL_REAR, rear)
);
}
);
}
}
}
@DisplayName("remove 메서드는")
@Nested
class Describe_remove {
@DisplayName("큐가 비어있는 상태라면")
@Nested
class Context_with_queue_empty {
@DisplayName("예외를 반환한다.")
@Test
void it_throws_exception() {
ArrayQueue queue = new ArrayQueue();
assertThrows(NoSuchElementException.class, queue::remove);
}
}
}
@DisplayName("peek 메서드는")
@Nested
class Describe_peek {
@DisplayName("큐가 비어있는 상태라면")
@Nested
class Context_with_queue_empty {
@DisplayName("null을 반환한다.")
@Test
void it_returns_null() {
ArrayQueue queue = new ArrayQueue();
assertNull(queue.peek());
}
}
@DisplayName("여러 개의 데이터가 존재할 때")
@Nested
class Context_with_exist_data {
final int FIRST_DATA_VALUE = 5;
final int SECOND_DATA_VALUE = 4;
final int THIRD_DATA_VALUE = 3;
@DisplayName("가장 앞에 있는 데이터를 반환한다.")
@Test
void it_returns_front_data() {
ArrayQueue queue = new ArrayQueue();
assertAll(
() -> {
queue.add(FIRST_DATA_VALUE);
assertEquals(FIRST_DATA_VALUE, queue.peek());
},
() -> {
queue.add(SECOND_DATA_VALUE);
assertEquals(FIRST_DATA_VALUE, queue.peek());
},
() -> {
queue.add(THIRD_DATA_VALUE);
assertEquals(FIRST_DATA_VALUE, queue.peek());
},
() -> {
queue.poll();
assertEquals(SECOND_DATA_VALUE, queue.peek());
}
);
}
}
}
@DisplayName("element 메서드는")
@Nested
class Describe_element {
@DisplayName("큐가 비어있는 상태라면")
@Nested
class Context_with_queue_empty {
@DisplayName("예외를 던진다.")
@Test
void it_throws_exception() {
ArrayQueue queue = new ArrayQueue();
assertThrows(NoSuchElementException.class, queue::element);
}
}
}
}
ListNode 기반 Queue
구현
ListNodeQueue.java
public class ListNodeQueue {
private ListNode front;
private ListNode rear;
private int size;
public ListNodeQueue() {
this.front = null;
this.rear = null;
this.size = 0;
}
public boolean offer(int data) {
ListNode offerNode = new ListNode(data);
if (isEmpty()) {
front = offerNode;
} else {
rear.linking(offerNode);
}
rear = offerNode;
size++;
return true;
}
public boolean add(int data) {
return offer(data);
}
public Integer poll() {
if (isEmpty()) {
return null;
}
int data = front.getData();
front = front.next();
size--;
return data;
}
public int remove() {
Integer data = poll();
if (data == null) {
throw new NoSuchElementException();
}
return data;
}
public Integer peek() {
if (isEmpty()) {
return null;
}
return front.getData();
}
public int element() {
Integer data = peek();
if (data == null) {
throw new NoSuchElementException();
}
return data;
}
private boolean isEmpty() {
return size == 0;
}
}
테스트
ListNodeQueueTest.java
@DisplayName("ListNodeQueue 테스트")
class ListNodeQueueTest {
@DisplayName("offer 메서드는")
@Nested
class Describe_offer {
@DisplayName("여러개의 데이터를 삽입한다면")
@Nested
class Context_with_insert_data {
final int FIRST_DATA_VALUE = 5;
final int SECOND_DATA_VALUE = 4;
final int THIRD_DATA_VALUE = 3;
final int INITIAL_INDEX = 0;
ListNodeQueue queue;
@BeforeEach
void setup() {
queue = new ListNodeQueue();
}
private ListNode getFieldFromQueue(String str) throws NoSuchFieldException, IllegalAccessException {
Field field = queue.getClass().getDeclaredField(str);
field.setAccessible(true);
return (ListNode)field.get(queue);
}
@DisplayName("각각 참을 반환한다.")
@Test
void it_returns_true() {
assertAll(
() -> assertTrue(queue.offer(FIRST_DATA_VALUE)),
() -> assertTrue(queue.offer(SECOND_DATA_VALUE)),
() -> assertTrue(queue.offer(THIRD_DATA_VALUE))
);
}
@DisplayName("rear 노드만 변경된다.")
void it_changed_only_rear() {
assertAll(
() -> compareBeforeAfterToAdd(FIRST_DATA_VALUE),
() -> compareBeforeAfterToAdd(SECOND_DATA_VALUE),
() -> compareBeforeAfterToAdd(THIRD_DATA_VALUE)
);
}
private void compareBeforeAfterToAdd(int data) throws NoSuchFieldException, IllegalAccessException {
ListNode beforeFront = getFieldFromQueue("front");
ListNode beforeRear = getFieldFromQueue("rear");
queue.add(data);
ListNode afterFront = getFieldFromQueue("front");
ListNode afterRear = getFieldFromQueue("rear");
assertAll(
() -> assertEquals(beforeFront, afterFront),
() -> assertNotEquals(beforeRear, afterRear)
);
}
}
}
@DisplayName("poll 메서드는")
@Nested
class Describe_poll {
@DisplayName("큐가 비어있는 상태라면")
@Nested
class Context_with_queue_empty {
@DisplayName("null을 반환한다.")
@Test
void it_returns_null() {
ListNodeQueue queue = new ListNodeQueue();
assertNull(queue.poll());
}
}
@DisplayName("여러개의 데이터가 존재할 때")
@Nested
class Context_with_exist_data {
final int FIRST_DATA_VALUE = 5;
final int SECOND_DATA_VALUE = 4;
final int THIRD_DATA_VALUE = 3;
final int INITIAL_FRONT = 0;
final int INITIAL_REAR = 3;
ListNodeQueue queue;
@BeforeEach
void setup() {
queue = new ListNodeQueue();
queue.offer(FIRST_DATA_VALUE);
queue.offer(SECOND_DATA_VALUE);
queue.offer(THIRD_DATA_VALUE);
}
private ListNode getFieldFromQueue(String str) throws NoSuchFieldException, IllegalAccessException {
Field field = queue.getClass().getDeclaredField(str);
field.setAccessible(true);
return (ListNode)field.get(queue);
}
@DisplayName("가장 앞에 있는 데이터를 반환한다.")
@Test
void it_returns_first_data() {
assertAll(
() -> assertEquals(FIRST_DATA_VALUE, queue.poll()),
() -> assertEquals(SECOND_DATA_VALUE, queue.poll())
);
}
@DisplayName("front 노드만 변경된다.")
@Test
void it_changed_only_front() {
assertAll(
this::compareBeforeAfterToAdd,
this::compareBeforeAfterToAdd,
this::compareBeforeAfterToAdd
);
}
private void compareBeforeAfterToAdd() throws NoSuchFieldException, IllegalAccessException {
ListNode beforeFront = getFieldFromQueue("front");
ListNode beforeRear = getFieldFromQueue("rear");
queue.poll();
ListNode afterFront = getFieldFromQueue("front");
ListNode afterRear = getFieldFromQueue("rear");
assertAll(
() -> assertNotEquals(beforeFront, afterFront),
() -> assertEquals(beforeRear, afterRear)
);
}
}
}
@DisplayName("remove 메서드는")
@Nested
class Describe_remove {
@DisplayName("큐가 비어있는 상태라면")
@Nested
class Context_with_queue_empty {
@DisplayName("예외를 반환한다.")
@Test
void it_throws_exception() {
ListNodeQueue queue = new ListNodeQueue();
assertThrows(NoSuchElementException.class, queue::remove);
}
}
}
@DisplayName("peek 메서드는")
@Nested
class Describe_peek {
@DisplayName("큐가 비어있는 상태라면")
@Nested
class Context_with_queue_empty {
@DisplayName("null을 반환한다.")
@Test
void it_returns_null() {
ListNodeQueue queue = new ListNodeQueue();
assertNull(queue.peek());
}
}
@DisplayName("여러 개의 데이터가 존재할 때")
@Nested
class Context_with_exist_data {
final int FIRST_DATA_VALUE = 5;
final int SECOND_DATA_VALUE = 4;
final int THIRD_DATA_VALUE = 3;
@DisplayName("가장 앞에 있는 데이터를 반환한다.")
@Test
void it_returns_front_data() {
ListNodeQueue queue = new ListNodeQueue();
assertAll(
() -> {
queue.add(FIRST_DATA_VALUE);
assertEquals(FIRST_DATA_VALUE, queue.peek());
},
() -> {
queue.add(SECOND_DATA_VALUE);
assertEquals(FIRST_DATA_VALUE, queue.peek());
},
() -> {
queue.add(THIRD_DATA_VALUE);
assertEquals(FIRST_DATA_VALUE, queue.peek());
},
() -> {
queue.poll();
assertEquals(SECOND_DATA_VALUE, queue.peek());
}
);
}
}
}
@DisplayName("element 메서드는")
@Nested
class Describe_element {
@DisplayName("큐가 비어있는 상태라면")
@Nested
class Context_with_queue_empty {
@DisplayName("예외를 던진다.")
@Test
void it_throws_exception() {
ListNodeQueue queue = new ListNodeQueue();
assertThrows(NoSuchElementException.class, queue::element);
}
}
}
}
Reference
- https://www.youtube.com/watch?v=EwI3E9Natcw&t=477s
- https://donghyeon.dev/junit/2021/04/11/JUnit5-완벽-가이드/
- https://junit.org/junit5/docs/current/user-guide/
- https://www.youtube.com/watch?v=vZm0lHciFsQ&t=69s
- https://www.baeldung.com/junit-5
- https://gracelove91.tistory.com/108?category=861835
- https://steady-coding.tistory.com/349
- https://johngrib.github.io/wiki/junit5-nested/
- https://st-lab.tistory.com/category/자료구조/Java
'Back-End > 백기선님의 자바 스터디' 카테고리의 다른 글
6주차 - 상속 (0) | 2022.03.31 |
---|---|
5주차 - 클래스 (0) | 2022.03.20 |
3주차 - 연산자 (0) | 2022.03.10 |
2주차 - 자바 데이터 타입, 변수 그리고 배열 (0) | 2022.03.10 |
1주차 - JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가. (0) | 2022.03.10 |
댓글