추상 클래스와 추상 메서드란 무엇인지 설명하고 관련된 예제 코드를 구현해보시오
abstract class Animal {
//구체적인 내용은 작성하지 않고 공통적인 특징을 추상적으로 선언 -> 리턴값 조차도 없이 메서드명만 선언
abstract void cry();
void eat() { System.out.println("먹다"); }
}
class Dog extends Animal {
void cry() { System.out.println("멍멍"); }
}
class Cat extends Animal {
void cry() { System.out.println("야옹"); }
}
class Animal2 {
void fly() { System.out.println("날다"); }
}
public class Java100_oop_AbstractClassMethod {
public static void main(String[] args) {
// 추상 클래스는 구체적인 내용이 없기 때문에 객체를 생성할 수 없다
//Animal dog = new Animal(); // Err
Animal2 dragonfly = new Animal2();
dragonfly.fly();
// 추상 클래스 사용은? -> 상속을 받아서 사용
Dog dog = new Dog();
dog.cry(); // 멍멍
Cat cat = new Cat();
cat.cry(); // 야옹
}
}
(1) 추상 클래스, 추상 메서드?
: 추상(부모) 클래스는 다른(자식) 클래스들의 공통적인 특징을 변수나 메서드로 정의만 해놓는 것을 말한다 -> 추상 메서드
: Field, Constructor, Method(추상 메서드 말고 일반 메서드)도 포함할 수 있음
: 메서드 선언만 있고 구체적인 내용은 없으므로 객체를 생성할 수 없다
: 따라서, 부모 클래스로서의 역할은 하지만, 구체적인 사용은 상속받은 자식 클래스에서 오버라이딩하여 사용해야 한다 -> 강제성!
: 추상클래스에서 선언만 해놓음으로써 이후 새로운(자식) 클래스들이 이를 상속 받아 구현하므로 새로운 클래스를 작성할 때 하나의 틀이 된다
(2) 쓰는 이유?
: 우선 강제성을 부여하기 위함이다
: 부모(추상) 클래스가 선언해놓은 메서드를 상속받은 자식 클래스들이 이름 그대로 재정의해서 구현하라고 강제하는 것이다
: 상속받은 자식 클래스 입장에서는 자칫 상속만 받고 재정의해서 사용을 안할 수도 있기 때문이다
: 즉, 일반 메서드로 구현하면 누군가는 해당 메서드를 구현 안할 수도 있다.
: 무조건 상속받은 자식 클래스 입장에서는 추상 메서드를 재정의해서 구현하도록 강제하기 위함이다.
: 위의 cat 클래스에서 재정의한 cry() 메서드를 지워보면 에러가 난다. 이유는 추상 메서드를 재정의(오버라이딩)하지 않아서이다.
(3) 꼭 재정의(override)해야만 하는가?
: 일단 그렇다.
: 자식 클래스는 일단은 무조건 부모(추상)클래스로 부터 상속받은 추상 메서드는 오버라이딩해서 재정의해야 한다.
: 추상 메서드를 포함하고 있다면 곧 추상 클래스여야 한다.
: 컴파일 단계에서 에러 발생
(4) 만약 재정의하고 싶지 않다면?
: 자식 클래스에서 상속받은 추상 메서드를 구현하지 않는다면 자식 클래스도 abstract를 붙여서 추상으로 만들어준다.
(5) 결론
: 부모(추상) 클래스에서 구현을 하지 않는 이유는 해당 메서드의 구현이 상속받는 클래스에 따라서 달라질 수 있기 때문에 선언만 해둔 것이다. (강제성)
: 이러한 추상 클래스는 여러명의 개발자가 작업할 때 코드의 확장과 분업을 효율적으로 처리할 수 있게 해준다.
: 분업화된 시스템에서 공통의 프로젝트를 진행할 때 많이 사용되어지는 중요한 문법이다.
자바의 인터페이스에 대해서 설명해보시오
(1) 인터페이스란 무엇인가?
: 사전적 의미 -> 사용자간 또는 컴퓨터간 통신이 가능하도록 해주는 디바이스나 프로그램
: 큰 틀에서 본다면 자바에서의 인터페이스 개념도 사전적 의미와 비슷하다
: "규격"을 인터페이스라 할 수 있고, 인터페이스는 하나의 "표준화"를 제공하는 것이라 할 수 있다.
(2) 추상 클래스 vs 인터페이스?
: 추상 클래스와 거의 비슷하다 -> 그러나 그 추상화 정도가 더 높다(더 엄격하다)
-> 따라서 일반 메서드 멤버 필드(변수)를 가질 수 없다.
: 이러한 점들이 추상 클래스와 인터페이스간 가장 큰 차이점 중 하나이다.
(3) 자바에서 인터페이스 문법?
: 표준화 및 규격을 인터페이스로 제공
: 따라서 어떤 클래스가 해당 인터페이스를 사용(상속) 한다면 인터페이스에 선언되어져 있는 메서드를 구현해야 한다.
: 인터페이스는 class 키워드를 사용하지 않고 별도의 interface 키워드를 사용한다
: class는 extends / interface는 implement 사용
: 추상 클래스와 같이 메서드의 구체적인 내용은 기술되어져 있지 않으므로 인터페이스를 상속받은 클래스에서 재정의(오버라이딩) 하여 사용해야 한다
(4) 상속 vs 구현
: 클래스와 인터페이스 이 둘의 가장 큰 차이점 중 하나는 "상속" 이다
: 클래스는 단일 상속만 가능, 인터페이스는 다중 상속 가능
하나의 모듈(하나의 기능 또는 메소드)을 기준으로 독립적으로 진행되는 가장 작은 단위의 테스트
단위 테스트는 테스트 유형에 따라 논리 단위 테스트, 통합 단위 테스트 등으로 나눌 수 있다.
단위 테스트는 해당 부분만 독립적으로 테스트하기 때문에 어떤 코드를 리팩토링하여도 빠르게 문제 여부를 확인할 수 있다.
좋고 깨끗한 테스트 코드는 FIRST라는 5가지 규칙을 따라야 한다.
- Fast: 테스틑 빠르게 동작하여 자주 돌릴 수 있어야 한다.
- Isolated: 좋은 단위 테스트는 작은 양의 코드(Unit)에 집중해야한다. 하나의 테스트가 다른 테스트와 상호작용하거나 의존할 경우 문제가 발생할 소지가 늘어난다.
- Repeatable: 반복 가능한 테스트는 실행할 때마다 항상 결과가 같아야 한다.
- Self-validating: 테스트는 기대하는 결과가 무엇인지 단언(assert)하지 않으면 올바른 테스트라고 볼 수 없다. JUnit 등에서 제공하는 검증 코드를 이용하자.
- Timely: 단위 테스트를 뒤로 미루지 말고 즉시 작성하자.
어노테이션 | 내용 |
---|---|
@Test | 테스트 Method임을 선언함 |
@ParameterizedTest | 매개변수를 받는 테스트를 작성할 수 있음 |
@TestFactory | @Test로 선언된 정적 테스트가 아닌 동적으로 테스트를 사용함 |
@TestInstance | 테스트 클래스의 생명주기를 설정함 |
@TestMethodOrder | 테스트 메소드 실행 순서를 구성하는데 사용 |
@DisplayName | 테스트 클래스 또는 메소드의 사용자 정의 이름을 선언할 때 사용 |
@DisplayNameGeneration | 이름 생성기를 선언함 |
@BeforeEach | 모든 테스트 실행 전에 실행할 테스트에 사용함 |
@AfterEach | 모든 테스트 실행 후에 실행한 테스트에 사용함 |
@BeforeAll | 현재 클래스를 실행하기 전 제일 먼저 실행할 테스트 작성하는데, static으로 선언함 |
@AfterAll | 현재 클래스 종료 후 해당 클래스를 실행, static으로 선언 |
@Nested | 클래스를 정적이 아닌 중첩 테스트 클래스임을 나타냄 |
@Tag | 클래스 또는 메소드 레벨에서 태그를 선언할 때 사용함. |
@Disabled | 이 클래스나 테스트를 사용하지 않음을 표시 |
@Timeout | 테스트 실행 시간을 선언 후 초과되면 실패하도록 설정함 |
@ExtendWith | 확장을 선언적으로 등록할 때 사용함 |
@RegisterExtension | 필드를 통해 프로그래밍 방식으로 확장을 등록할 때 사용함 |
@TempDir | 필드 주입 또는 매개변수 주입을 통해 임시 디렉토리를 제공하는데 사용 |
org.junit.jupiter.api.Assertions 클래스는 값 검증을 위한 assert로 시작하는 static 메서드를 제공하고 있다.
자바의 인터페이스 문법을 예제 코드로 구현해보시오
(1) 인터페이스
: 추상 클래스와 거의 비슷하나 그 추상화 정도가 더 높다 -> 일반 메서드나 멤버 필드(변수)를 가질 수 없다.
: 표준화 및 규격을 인터페이스로 제공 -> 일종의 "설계도" 개념
: 따라서, 어떤 클래스가 해당 인터페이스를 사용(상속) 한다면 인터페이스에 선언되어져 있는 메서드를 오버라이딩 하여 사용
(2) 상속
: 클래스는 "단일 상속"만 가능, 인터페이스는 "다중 상속" 이 가능 -> 가장 큰 차이점
: 즉, 이를 이용하면 여러 개의 인터페이스로 부터 메서드를 받아올 수 있게 된다. -> 다중 상속 구현
(3) 장점
: 인터페이스를 이용하면 메서드의 추상적인 "선언"과 그 메서드들을 구체적인 "구현" 부분을 분리시킬 수 있다.
: 분업화된 시스템을 구축하여 각자 독립적으로 프로젝트 개발을 해나갈 수 있다
(4) 우선 순위 (extends vs implements)
: 상속을 받는 extends 키워드와 구현을 하는 implements 키워드가 동시에 쓰일 때 -> extends 키워드가 항상 먼저 쓰인다.
: 예시) class Student extends Person implements A, B
Class Person {
String name;
int age;
int weight;
Person() {}
Person(String name, int age, int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
void wash() {System.out.println("씻다.");}
void study() {System.out.println("공부하다.");}
void play() {System.out.println("놀다.");}
}
interface Allowance {
// 변수는 안되나 상수는 되므로 상수로 지정해주면 됨 -> public static final을 붙여주면 됨
// 인터페이스 내 모든 멤버 필드(변수)는 public static final이여야 함 -> 생략이 가능 -> 그냥 "타입 변수명" 지정해서 쓰면 됨.
public static final String aaa = "코리아";
int bbb = 100;
// 인터페이스 내 모든 메서드는 public abstract이어야 함 -> 생략이 가능
// private 사용하면 Err
abstract void in(int price, String name); //일반 메서드 사용 불가
abstract void out(int price, String name);
// 아래는 Err -> 위에처럼 선언만 되어져 있어야함. 일반 메서드처럼 구현 X
// void wash() {System.out.println("씻다");}
}
interface Train {
abstract void train(int training_pay, String name);
}
class Student extends Person implements Allowance, Train {
Student() {}
Student(String name, int age, int weight) {
super(name, age, weight);
}
public void in(int price, String name) {System.out.printf("%s에게서 %d원 용돈을 받았습니다 %n", name, price);}
public void out(int price, String name) {System.out.println(
"%d원 금액을 지출했습니다. 지출용도 -> %s %n", price, name);}
public void train(int training_pay, String name) {System.out.printf("[%s -> %d원 입금완료] %n", name, training_pay);}
}
public class Java100_oop_Interface3 {
public static void main(String[] args) {
// 객체 생성
Student s1 = new Student("홍길동", 20, 85);
// 클래스와 인터페이스로부터 상속(Person)과 구현 (Allowance,Train)을 한 메서드들 호출하기
s1.wash();
s1.study();
s1.play();
s1.in(10000, "엄마"); // 엄마에게서 10000원 용돈을 받았습니다
s1.out(5000, "편의점");
s1.train(50000, "아빠");
// 상수 필드 사용하기
System.out.println(s1.aaa); // 코리아
System.out.prntln(s1.bbb); // 100
// static은 객체를 생성하지 않아도 사용 가능
System.out.println(Allowance.aaa); // 코리아
System.out.println(Allowance.bbb); // 100
}
}
다형성(polymorphism)에 대해서 개념 설명을 해보시오
(1) 다형성이란?
: 다형성이란 다양한 형태 또는 특성을 가진다는 의미
: 자바와 같은 객체 지향 언어에서의 의미는 부모 클래스를 상속받은 자식 클래스의 인스턴스가 부모의 객체로도 사용이 되고, 뿐만 아니라 자식 클래스의 객체로도 사용될 수 있는 다양한 상황을 의미한다. ex) Person s1 = new Student();
(2) 결론
: 정리하면, 하위 클래스의 인스턴스(객체)는 보다 위인 상위 클래스의 인스턴스 (객체)로도 사용될 수 있다.
ex) Bird aaa = new Parrot();
: 그런데 그 반대는 안된다. 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 사용될 수 없다.
ex) Parrot bbb = new Bird(); ---- X Err
class Person {}
class Student extends Person {}
public class Java100_oop_Polymorphosm2{
public static void main(String[] args) {
// 객체 생성
Student s1 = new Student();
// 부모 타입으로 객체 생성
Person s2 = new Student(); //하위 클래스로 객체를 만들면서 타입은 상위 타입 쓰는 것이 가능 -> 다형성
// 객체 생성
Person aaa = new Person(); // 지극히 정상
// Err
Student bbb = new Person(); // 상위 클래스로 객체를 생성하면서 타입은 자식 타입을 쓰는경우는 Err
}
}
자식 클래스로 생성하는 객체를 부모의 타입으로 받아서 객체를 생성하면 사용범위가 어떻게 되는지 말해보시오
+이 문제는 다형성의 관계에서 부모, 자식 클래스 자원을 어떻게 쓸 수 있는지를 묻는 문제이다.
class Person {
String str1 = "난 부모 클래스";
void method1() {System.out.println("AAA");}
void ppp() {System.out.println("ppp");}
}
class Student extends Person {
String str2 = "난 자식 클래스";
void method1() {System.out.println("오버라이딩 -AAA");}
void sss() {System.out.println("sss");}
void x() {
method1();
super.method1();
}
}
public class Java100_oop_Polymorphosm3{
public static void main(String[] args) {
// 객체 생성 -> 부모 + 자식 클래스의 모든 자원을 다 쓸 수 있다.
Student s1 = new Student();
System.out.println(s1.str1);
System.out.println(s1.str2);
s1.method1(); // 오버라이딩 -AAA
s1.sss(); // sss
s1.ppp(); // ppp
// 그런데, 자식 클래스에서 오버라이딩된 부모 클래스의 원본 메서드를 호출하고 싶다면? -> super 사용
s1.x();
// 부모 타입으로 객체 생성 -> 범위는 부모의 자원만을 쓸 수 있다. 오버라이딩된 것은 자식의 메서드가 실행.
Person s2 = new Student(); //하위 클래스로 객체를 만들면서 타입은 상위 타입 쓰는 것이 가능 -> 다형성
System.out.println(s2.str1);
//System.out.println(s2.str2); // Err -> 자식의 자원은 쓸 수 없다.
s2.ppp(); // ppp
//s2.sss(); // Err
s2.method1(); // 오버라이딩 -AAA -> 오버라이딩한 것은 자식의 메서드로 실행됨!
// 그런데, 자식의 메서드를 바로 호출하고 싶다면? -> 캐스트 필요
((Student)s2).sss();
// 객체 생성
Person aaa = new Person(); // 지극히 정상
aaa.method1(); // AAA
// Err
Student bbb = new Person(); // 상위 클래스로 객체를 생성하면서 타입은 자식 타입을 쓰는경우는 Err
}
}
추상 클래스와 상속을 사용하여 다형성 예제를 만들어보시오
abstract class Car {
abstract void run();
}
class Ambulance extends Car {
void run() {System.out.println("앰블런스 지나가요~");}
}
class Cultivator extends Car {
void run() {System.out.println("경운기 지나가요~");}
}
class SportsCar extends Car {
void run() {System.out.println("스포츠카 지나가요~");}
}
public class Java100_oop_polymorphism5 {
public static void main(String[] args) {
// 객체 생성
Car c1 = new Ambulance(); // 정상
Car c2 = new Cultivator(); // 정상
Car c3 = new SportsCar(); // 정상
c1.run(); // 앰블런스 지나가요~
c2.run(); // 경운기 지나가요~
c3.run(); // 스포츠카 지나가요~
}
}
다형성을 활용한 객체 생성시 배열과 반복문을 사용하여 객체를 생성해보시오
abstract class Car {
abstract void run();
}
class Ambulance extends Car {
void run() {System.out.println("앰블런스 지나가요~");}
}
class Cultivator extends Car {
void run() {System.out.println("경운기 지나가요~");}
}
class SportsCar extends Car {
void run() {System.out.println("스포츠카 지나가요~");}
}
public class Java100_oop_polymorphism6 {
public static void main(String[] args) {
// 객체 생성
Car[] cars = new Car[3];
// cars 배열 초기화
cars = new Car[] {new Ambulance(), new Cultivator(), new SportsCar()};
System.out.println(cars[0]);
System.out.println(cars[1]);
System.out.println(cars[2]);
// 자식 클래스로 객체 생성 -> 타입은 부모 타입으로 -> 이렇게 생성된 객체들도 바로 배열 초기화 -> 다형성 덕분
Car[] cars = {new Ambulance(), new Cultivator(), new SportsCar()};
for(int i=0; i < cars.length; i++)
cars[i].run();
// 향상된 for문
for(Car obj : cars)
obj.run();
}
}
다형성을 사용하면 배열이나 매개변수 활용에서 좋다는데 자세히 코드로 설명해보시오
class Person {}
class Batman extends Person {}
class Human {}
class Superman extends Human {}
public class Java100_oop_Polymorphism7 {
public static void main(string[] args) {
// [1] 배열에서 다형성을 사용할 수 없다면
// 배열: 동일한 타입의 데이터를 하나로 묶어서 관리하는 자료구조 -> 다형성이 없다면 각 객체별로 관리해야 한다.
Person[] persons = new Person[10];
persons[0] = new Person();
Batman[] batmans = new Batman[10];
batmans[0] = new Batman();
// batmans[1] = new Person(); // Err
// [2] 배열에서 다형성을 사용할 수 있기에
Human[] humans = new Human[10];
humans[0] = new Human();
humans[1] = new Superman();
// [3] 매개변수의 다형성
// 프로그래밍 언어에서 함수나 메서드를 호출할 때는 그에 맞는 적절한 파라미터를 보내줘야 한다.
System.out.println(new Person());
System.out.println(new Batman());
}
}
결론적으로, Object는 가장 최상위 조상이므로 어떤 객체를 보내도 그 보다 상위 타입이 된다.
[참고 강의] 백기선님의 더 자바, 애플리케이션을 테스트하는 다양한 방법
: 자바 개발자가 가장 많이 사용하는 테스팅 프레임워크
: 자바 8 이상을 필요로 함
InteliJ는 Junit Platform을 사용해서 클래스에 들어있는 @Test가 붙어있는 메소드를 실행시키는 것임.
Jupiter와 Vintage는 JUnit Platform이 TestEngine API 구현체임
platform을 사용해서 실행을 시키는 것이고 Jupiter를 통해 JUnit 5를 사용
- @Test
- @BeforeAll / @AfterAll
- @BeforeEach / @AfterEach
- @Disabled
package study.test;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class StudyTest {
@Test
void create() {
Study study = new Study();
assertNotNull(study);
}
@Test
@Disabled // 테스트 실행하지 않을 때
void create1() {
System.out.println("create1");
}
//안에 잇는 여러 테스트가 모두 실행될 때 딱 한번 호출됨. 반드시 static으로 선언해야하며 return 타입이 있으면안됨
@BeforeAll
static void beforeAll() {
System.out.println("before all");
}
//마찬가지로 모든 테스트 후 딱 한번 호출됨
@AfterAll
static void afterAll() {
System.out.println("after all");
}
//모든 테스트를 실행하기 전에 한번씩 호출됨
@BeforeEach
void beforeEach() {
System.out.println("beforeEach");
}
@AfterEach
void afterEach() {
System.out.println("after each");
}
}
Ex)
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
표시 : create_new_study_again -> create new study again
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {
@Test
@DisplayName("스터디 만들기")
void create_new_study() {
Study study = new Study();
assertNotNull(study);
}
@Test
void create_new_study_again() {
System.out.println("create1");
}
}
org.junit.jupiter.api.Assertions.*
실제 값이 기대한 값과 같은지 확인 | assertEquals(expected,actual) |
값이 null이 아닌지 확인 | assertNotNull(actual) |
다음 조건이 참인지 확인 | assertTrue(boolean) |
모든 확인 구문 확인 | assertAll(executables...) |
예외 발생 확인 | assertThrows(expectedType, executable) |
특정 시간 안에 실행이 완료되는지 확인 | assertTimeout(duration,executable) |
public class Study {
private StudyStatus status = StudyStatus.DRAFT;
private int limit;
public Study(int limit) {
if (limit < 0) {
throw new IllegalArgumentException("limit은 0보다 커야 한다.");
}
this.limit = limit;
}
public StudyStatus getStatus() {
return this.status;
}
public int getLimit() {
return limit;
}
}
public enum StudyStatus {
DRAFT, STARTED, ENDED
}
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {
@Test
@DisplayName("스터디 만들기")
void create_new_study() {
IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class, () -> new Study(-10));
assertEquals("limit은 0보다 커야 한다.", exception.getMessage());
Study study = new Study(10);
assertAll(
() -> assertNotNull(study),
() -> assertEquals(StudyStatus.DRAFT, study.getStatus(),
() -> "스터디를 처음 만들면 " + StudyStatus.DRAFT + "상태다."),
() -> assertTrue(study.getLimit() > 0, "스터디 최대 참석 가능 인원은 0보다 커야한다.")
);
assertTimeout(Duration.ofMillis(100), () ->
new Study(10));
}
}
assertAll은 한번의 몇개의 테스트가 실패했는지 한번에 바로 알 수 있음. excutable 세개를 넘김
특정한 조건을 만족하는 경우에 테스트를 실행하는 방법
org.junit.jupiter.api.Assumptions.*
@Enabled 와 @Disabled
assumeTrue로 조건이 LOCAL 환경일때만 테스트하게 하는 코드 예시
class StudyTest {
@Test
@DisplayName("스터디 만들기")
@EnabledOnOs({OS.MAC, OS.LINUX}) //맥과 리눅스에서만 테스트 가능
@EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_11})
void create_new_study() {
String test_env = System.getenv("TEST_ENV");
System.out.println(test_env);
assumeTrue("LOCAL".equalsIgnoreCase(test_env));
assumingThat("LOCAL".equalsIgnoreCase(test_env), () -> {
//LOCAL 환경일 때
System.out.println("local");
});
assumingThat("OTHER".equalsIgnoreCase(test_env), () -> {
//OTHER 환경일 때
});
}
@Test
@DisabledOnOs(OS.MAC) //MAC에서 테스트 불가능
void create_new_study_again() {
System.out.println("create1");
}
}
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {
@Test
@DisplayName("스터디 만들기 fast")
@Tag("fast")
void create_new_study() {
}
@Test
@DisplayName("스터디 만들기 slow")
@Tag("slow")
void create_new_study_again() {
System.out.println("create1");
}
}
JUnit 5 애노테이션을 조합하여 커스텀 태그를 만들 수 있다
FastTest.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@Tag("fast")
public @interface FastTest {
}
어노테이션을 생성하면 아래처럼 간편하게 사용가능
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {
@FastTest
@DisplayName("스터디 만들기 fast")
void create_new_study() {
}
}
@RepeatedTest
: 반복 횟수와 반복 테스트 이름을 설정할 수 있다.
- {displayName}
- {currentRepetition}
- {totalRepetitions}
: RepetitionInfo 타입의 인자를 받을 수 있다.
@ParameterizedTest
: 테스트에 여러 다른 매개변수를 대입해가며 반복 실행한다.
- {displayName}
- {index}
- {arguments}
- {0}, {1}, ...
@DisplayName("스터디 만들기")
@RepeatedTest(value = 10, name = "{displayName}, {currentRepetition}/{totalRepetition}")
void repeatTest(RepetitionInfo repetitionInfo) {
System.out.println("test" + repetitionInfo.getCurrentRepetition() + "/" +
repetitionInfo.getTotalRepetitions());
}
@DisplayName("스터디 만들기")
@ParameterizedTest(name = "{index} {displayName} message = {0}")
@ValueSource(strings = {"날씨가", "많이", "추워지고", "있네요."})
void parameterizedTest(String message) {
System.out.println(message);
}
// 테스트 이름 : 1 스터디 만들기 message=날씨가 / 2 스터디 만들기 message=많이 ..
@DisplayName("스터디 만들기")
@ParameterizedTest(name = "{index} {displayName} message = {0}")
@ValueSource(ints = {10,20,40})
void parameterizedTest2 (@ConvertWith(StudyConverter.class) Study study) {
System.out.println(study.getLimit());
}
static class StudyConverter extends SimpleArgumentConverter {
@Override
protected Object convert(Object source, Class<?> targetType) throws ArgumentConversionException {
assertEquals(Study.class, targetType, "Can only convert to Study");
return new Study(Integer.parseInt(source.toString()));
}
}
Study
public class Study {
private StudyStatus status = StudyStatus.DRAFT;
private int limit;
private String name;
public Study(int limit, String name) {
this.limit = limit;
this.name = name;
}
public Study(int limit) {
if (limit < 0) {
throw new IllegalArgumentException("limit은 0보다 커야 한다.");
}
this.limit = limit;
}
public StudyStatus getStatus() {
return this.status;
}
public int getLimit() {
return limit;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Study{" +
"status=" + status +
", limit=" + limit +
", name='" + name + '\'' +
'}';
}
}
StudyTest
// 여러 인자값들을 받아서 조합해서 테스트 가능
@DisplayName("스터디 만들기")
@ParameterizedTest(name = "{index} {displayName} message = {0}")
@CsvSource({"10, '자바 스터디'", "20, 스프링"})
void parameterizedTest3 (@AggregateWith(StudyAggregator.class) Study study) {
System.out.println(study);
}
// static이거나 Public이어야 함
static class StudyAggregator implements ArgumentsAggregator {
@Override
public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException {
return new Study(accessor.getInteger(0), accessor.getString(1));
}
}
인자값들의 소스
인자값 조합