TIL - 2022.01.25 ~ 01.30

YulHee Kim·2022년 1월 25일
0
post-thumbnail

01.25 화

✏️ 추상 클래스, 추상 메서드

추상 클래스와 추상 메서드란 무엇인지 설명하고 관련된 예제 코드를 구현해보시오

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 구현
: 클래스와 인터페이스 이 둘의 가장 큰 차이점 중 하나는 "상속" 이다
: 클래스는 단일 상속만 가능, 인터페이스는 다중 상속 가능


01.26 수

✏️ 단위 테스트란?

하나의 모듈(하나의 기능 또는 메소드)을 기준으로 독립적으로 진행되는 가장 작은 단위의 테스트

단위 테스트는 테스트 유형에 따라 논리 단위 테스트, 통합 단위 테스트 등으로 나눌 수 있다.

  • 논리 단위 테스트: 한 메서드에 집중한 테스트로 mock이나 stub을 이용해 테스트 메서드의 경계를 제어할 수 있다
  • 통합 단위 테스트: 실제 운영 환경(혹은 그 일부)에서 컴포넌트 간 연동에 치중한 테스트, 예를 들어 데이터베이스를 사용하는 코드라면 데이터베이스를 효과적으로 호출하는가를 테스트할 수 있다.

✏️ 단위 테스트 필요성

단위 테스트는 해당 부분만 독립적으로 테스트하기 때문에 어떤 코드를 리팩토링하여도 빠르게 문제 여부를 확인할 수 있다.

  • 테스팅에 대한 시간과 비용을 절감할 수 있다
  • 새로운 기능 추가 시에 수시로 빠르게 테스트 할 수 있다
  • 리팩토링 시에 안정성을 확보할 수 있다
  • 코드에 대한 문서가 될 수 있다

✏️ 좋은 단위 테스트의 특징

  1. 1개의 테스트 함수에 대해 assert를 최소화하라
  2. 1개의 테스트 함수는 1가지 개념만을 테스트하라

좋고 깨끗한 테스트 코드는 FIRST라는 5가지 규칙을 따라야 한다.

  1. Fast: 테스틑 빠르게 동작하여 자주 돌릴 수 있어야 한다.
  2. Isolated: 좋은 단위 테스트는 작은 양의 코드(Unit)에 집중해야한다. 하나의 테스트가 다른 테스트와 상호작용하거나 의존할 경우 문제가 발생할 소지가 늘어난다.
  3. Repeatable: 반복 가능한 테스트는 실행할 때마다 항상 결과가 같아야 한다.
  4. Self-validating: 테스트는 기대하는 결과가 무엇인지 단언(assert)하지 않으면 올바른 테스트라고 볼 수 없다. JUnit 등에서 제공하는 검증 코드를 이용하자.
  5. Timely: 단위 테스트를 뒤로 미루지 말고 즉시 작성하자.

✏️ JUnit이란?

  • 자바 프로그래밍 언어용 유닛 테스트 프레임워크
  • JUnit5는 자바 8 이상부터 사용 가능
  • 단정문으로 테스트 케이스의 수행 결과를 판별함 (assert)
  • 어노테이션으로 간결하게 지원함

✏️ JUnit5 어노테이션

어노테이션내용
@Test테스트 Method임을 선언함
@ParameterizedTest매개변수를 받는 테스트를 작성할 수 있음
@TestFactory@Test로 선언된 정적 테스트가 아닌 동적으로 테스트를 사용함
@TestInstance테스트 클래스의 생명주기를 설정함
@TestMethodOrder테스트 메소드 실행 순서를 구성하는데 사용
@DisplayName테스트 클래스 또는 메소드의 사용자 정의 이름을 선언할 때 사용
@DisplayNameGeneration이름 생성기를 선언함
@BeforeEach모든 테스트 실행 전에 실행할 테스트에 사용함
@AfterEach모든 테스트 실행 후에 실행한 테스트에 사용함
@BeforeAll현재 클래스를 실행하기 전 제일 먼저 실행할 테스트 작성하는데, static으로 선언함
@AfterAll현재 클래스 종료 후 해당 클래스를 실행, static으로 선언
@Nested클래스를 정적이 아닌 중첩 테스트 클래스임을 나타냄
@Tag클래스 또는 메소드 레벨에서 태그를 선언할 때 사용함.
@Disabled이 클래스나 테스트를 사용하지 않음을 표시
@Timeout테스트 실행 시간을 선언 후 초과되면 실패하도록 설정함
@ExtendWith확장을 선언적으로 등록할 때 사용함
@RegisterExtension필드를 통해 프로그래밍 방식으로 확장을 등록할 때 사용함
@TempDir필드 주입 또는 매개변수 주입을 통해 임시 디렉토리를 제공하는데 사용

✏️ assert 메서드

org.junit.jupiter.api.Assertions 클래스는 값 검증을 위한 assert로 시작하는 static 메서드를 제공하고 있다.

  • assertEquals(expected,actual):int, long 등 기본타입과 Object에 대한 assertEquals 메서드가 존재한다
  • assertNotEquals(Object unexpected, Object actual)
  • assertTrue(boolean condition)
  • assertFalse(boolean condition)
  • assertNull(Object actual)
  • assertNotNull(Object actual)
  • assertAll(): assert로 여러개 검증할 때 사용
  • assertThrows(ArithmeticException.class, () -< divide(100,0)): 실행한 코드에서 특정 익셉션이 발생하는지 확인
  • fail()

01.28 금

✏️ 인터페이스 문법, 개념

자바의 인터페이스 문법을 예제 코드로 구현해보시오

(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
	}
}

01.29 토

✏️ 추상 클래스와 상속

추상 클래스와 상속을 사용하여 다형성 예제를 만들어보시오

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
    System.out.println() 메서드의 경우 어떤 타입, 객체를 매개변수로 받더라도 에러없이 해당 객체의 값을 출력
    그것이 가능한 이유는 바로 다형성을 활용하고 있기 때문이다.
    실제 메서드의 API를 보면 -> public void println(Object x)로 되어 있기에 어떤 객체 타입이 전달되더라도 에러없이 출력이 되는 것이다.

결론적으로, Object는 가장 최상위 조상이므로 어떤 객체를 보내도 그 보다 상위 타입이 된다.


01.30 일

[참고 강의] 백기선님의 더 자바, 애플리케이션을 테스트하는 다양한 방법

✏️ JUnit 5 소개

: 자바 개발자가 가장 많이 사용하는 테스팅 프레임워크
: 자바 8 이상을 필요로 함

  • Platform : 테스트를 실행해주는 런처 제공. TestEngine API 제공
  • Jupiter : TestEngine API 구현체로 JUnit 5를 제공
  • Vintage: JUnit 4와 3을 지원하는 TestEngine 구현체

InteliJ는 Junit Platform을 사용해서 클래스에 들어있는 @Test가 붙어있는 메소드를 실행시키는 것임.
Jupiter와 Vintage는 JUnit Platform이 TestEngine API 구현체임
platform을 사용해서 실행을 시키는 것이고 Jupiter를 통해 JUnit 5를 사용

✏️ 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");
    }
}

✏️ 테스트 이름 표기하기

  • @DisplayNameGeneration
    Method와 Class 레퍼런스를 사용해서 테스트 이름을 표기하는 방법 설정.
    기본 구현체로 ReplaceUnderscores 제공

Ex)
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
표시 : create_new_study_again -> create new study again

  • @DisplayName
    어떤 테스트인지 테스트 이름을 보다 쉽게 표현할 수 있는 방법을 제공하는 애노테이션.
    @DisplayNameGeneration 보다 우선 순위가 높다.
@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");
    }

 }

✏️ Assertion

org.junit.jupiter.api.Assertions.*

실제 값이 기대한 값과 같은지 확인assertEquals(expected,actual)
값이 null이 아닌지 확인assertNotNull(actual)
다음 조건이 참인지 확인assertTrue(boolean)
모든 확인 구문 확인assertAll(executables...)
예외 발생 확인assertThrows(expectedType, executable)
특정 시간 안에 실행이 완료되는지 확인assertTimeout(duration,executable)

Study

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;
    }
}

Study Status

public enum StudyStatus {
    DRAFT, STARTED, ENDED
}

StudyTest

@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.*

  • assumeTrue(조건)
  • assumingThat(조건, 테스트)

@Enabled 와 @Disabled

  • OnOS
  • OnJre
  • IfSystemProperty
  • IfEnvironmentVariable
  • If

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=많이 ..

int -> Study 클래스로 변환하여 반복

	@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()));
        }
    }

name 필드 추가하여 CsvSource 테스트 예시

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));
        }
    }

인자값들의 소스

  • @ValueSource
  • @NullSource, @EmptySource, @NullAndEmptySource
  • @EnumSource
  • @MethodSource
  • @CsvSource
  • @CvsFileSource
  • @ArgumentSource

인자값 조합

  • ArgumentsAccessor
  • 커스텀 Accessor
    • ArgumentsAggregator 인터페이스 구현
      - @AggregateWith
profile
백엔드 개발자

0개의 댓글