패스트캠퍼스 "10개 프로젝트로 완성하는 백엔드 웹개발" 중 홍종완님의 강의를 수강하고 작성한 내용입니다.
1. 테스트 코드 실습
자바 단위 테스팅 프레임 워크
- JUnit5 사용
https://junit.org/junit5/docs/current/user-guide/#writing-tests
JUnit 5 User Guide
Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo
junit.org
https://www.petrikainulainen.net/programming/testing/junit-5-tutorial-writing-parameterized-tests/
Writing Parameterized Tests With JUnit 5 - Petri Kainulainen
This blog post describes how we can write parameterized tests with JUnit 5 and configure the arguments which are passed to our test methods.
www.petrikainulainen.net
AssertJ
- 테스트 코드 가독성을 높여주는 자바 라이브러리
-https://assertj.github.io/doc/#assertj-core-assertions-guide
AssertJ - fluent assertions java library
Thanks to all the contributors of this release: Erhard Pointl, Stefano Cordio, BJ Hargrave, Jeremy Landis, Ashley Scopes, Roland Weisleder , Benedikt Bogason , Andreas Kutschera , Matthew , Chris HeZean , Leo0506 , Zhou Yicheng , Saria , Chunhao Liao , max
assertj.github.io
테스트 코드를 작성하는 이유
- 문서화 역할
- 코드에 결함을 발견
- 리팩토링시 안정성 확보
- 테스트하기 쉬운 코드를 작성하다 보면 더 낮은 결합도를 가진 설계를 얻을 수 있다.
TDD
- Test Driven Development(테스트 주도 개발)
- 프로덕션 코드보다 테스트 코드를 먼저 작성하는 개발 방법
- 기능 동작을 검증(메서드 단위)
패키지는 src/main/java/com/example/ooppractice/PassworValidator
테스트 코드의 패키지는 src/test/java/com/example/ooppractice/PasswordValidatorTest
src/main/java/com/example/ooppractice/PassworValidator
package com.example.ooppractice;
public class PasswordValidator {
public static final String WRONG_PASSWORD_LENGTH_EXCEPTION_MESSAGE = "비밀번호는 최소 8자 이상 12자 이하여야 한다.";
public static void validate(String password) {
int length = password.length();
if(length < 8 || length > 12) {
throw new IllegalArgumentException(WRONG_PASSWORD_LENGTH_EXCEPTION_MESSAGE); // CTRL+ALT+C
};
}
}
- CTRL + ALT + C 를 사용하면 상수로 쉽게 설정할 수 있습니다.
- parameter password 의 길이가 8자보다 짧거나, 12자보다 길면 IllegalArgumentException 을 발생시킵니다.
src/test/java/com/example/ooppractice/PasswordValidatorTest
package com.example.ooppractice;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.assertThatCode;
/**
* 요구사항
* • 비밀번호는 최소 8자 이상 12자 이하여야 한다.
* • 비밀번호가 8자 미만 또는 12자 초과인 경우 IllegalArgumentException 예외를 발생시킨다.
* • 경계조건에 대해 테스트 코드를 작성해야 한다(7자, 13자)
*/
public class PasswordValidatorTest {
@DisplayName("비밀번호가 최소 8자 이상, 12자 이하면 예외가 발생하지 않는다.")
@Test
void validatePasswordTest() {
assertThatCode(() -> PasswordValidator.validate("oop practice"))
.doesNotThrowAnyException();
}
@DisplayName("비밀번호가 8자 미만 또는 12자 초과하는 경우 IllegalArgumentException 예외가 발생한다.")
@ParameterizedTest
@ValueSource(strings = {"aabbcce", "aabbccddeeffg"}) // 7자, 13자
void validatePasswordTest2(String password) {
assertThatCode(() -> PasswordValidator.validate(password))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("비밀번호는 최소 8자 이상 12자 이하여야 한다.");
}
}
- @DisplayName 으로 테스트 동작에 대한 설명을 표시하고 가독성을 높일 수 있습니다. (요구사항 표시)
- @ParameterizedTest 는 하나의 메소드에서 여러 개의 테스트를 수행하는 경우 사용하는데,
@Test 대신 @ParameterizedTest 어노테이션을 사용하고 파라미터로 넘겨줄 값들을 지정해줄 땐 @ValueSource 어노테이션을 사용하면 됩니다.
테스트에 주입할 값을 배열로 지정합니다. (strings = {"aabbcce", "aabbccddeeffgg"})
- 해당 테스트 객체는 패스워드 글자수에 따라 예외가 발생하는지, 발생하지 않는지 테스트 합니다.
src/main/java/com/example/ooppractice/PasswordGenerator
package com.example.ooppractice;
@FunctionalInterface
public interface PasswordGenerator {
String generatePassword();
}
- @FunctionalInterface 는 함수형 인터페이스입니다.
함수형 인터페이스는 추상 메서드가 딱 하나만 존재하는 인터페이스입니다.
람다식은 이러한 함수형 인터페이스를 기반으로만 작성될 수 있기때문에 함수형 인터페이스를 사용합니다.
User
package com.example.ooppractice;
public class User {
private String password;
/**
* 초기화
*/
public void initPassword(PasswordGenerator passwordGenerator) {
// as-is
// 내부에서 생성하는 경우는 강한 결합: 해당 부분에 많은 영향을 받는다. -> 테스트하기 어렵다.
// RandomPasswordGenerator randomPasswordGenerator = new RandomPasswordGenerator();
// to-be
// 인터페이스를 주입받아서 RandomPasswordGenerator에 의존하지 않는다. -> 테스트하기 쉽다.
// 테스트 코드 작성 이유중에 '4. 테스트하기 쉬운 코드를 작성하다보면 더 낮은 결합도를 가진 설계를 얻을 수 있음' 에 해당(느슨한 결합을 가진 설계)
String randomPassword = passwordGenerator.generatePassword();
/**
* 비밀번호는 최소 8자 이상 12자 이하여야 한다.
*/
if (randomPassword.length() >= 8 && randomPassword.length() <= 12) {
this.password = randomPassword;
}
}
public String getPassword() {
return password;
}
}
- as-is) 주석처리 되어 있는
RandomPasswordGenerator randomPasswordGenerator = new RandomPasswordGenerator(); 를 사용했을 땐
내부에서 비밀번호 생성을 했기때문에 강한 결합이라고 하고 해당 부분에 많은 영향을 받기때문에
테스트하기가 어렵습니다.
- to-be) String randomPassword = passwordGenerator.generatePassword();
인터페이스를 주입받기때문에 RandomPasswordGenerator()에 의존하지 않아서
테스트하기가 쉽습니다.
그리고 더 낮은 결합도를 가진 설계를 가집니다.
인터페이스를 구현하는 객체는 아래의 CorrectFiedPasswordGenerator와 WrongFixedPasswordGenerator 입니다.
CorrectFixedPasswordGenerator
package com.example.ooppractice;
public class CorrectFixedPasswordGenerator implements PasswordGenerator{
@Override
public String generatePassword() {
return "abcdefgh"; // 8글자
}
}
- 함수형 인터페이스인 PasswordGenerator 를 구현합니다.
- 테스트에 무조건 성공할 수 있게 8글자 String 을 반환합니다.
WrongFixedPasswordGeneraotr
package com.example.ooppractice;
public class WrongFixedPasswordGenerator implements PasswordGenerator{
@Override
public String generatePassword() {
return "ab"; // 2글자
}
}
- 함수형 인터페이스인 PasswordGenerator 를 구현합니다.
- 테스트에 무조건 실패할 수 있게 2글자 String 을 반환합니다.
UserTest
package com.example.ooppractice;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class UserTest {
@DisplayName("패스워드를 초기화한다.")
@Test
void passwordTest() {
// given
User user = new User();
// when
user.initPassword(new CorrectFixedPasswordGenerator());
// 굳이 구현체를 만들지 않고(CorrectFixedPasswordGenerator()) 람다를 사용해도 됨. (@FunctionalInterface)
//user.initPassword(() -> "abcdefgh");
// then
assertThat(user.getPassword()).isNotNull();
}
@DisplayName("패스워드가 요구사항에 부합하지 않아 초기화가 되지 않는다.")
@Test
void passwordTest2() {
// given
User user = new User();
// when
user.initPassword(new WrongFixedPasswordGenerator());
// 굳이 구현체를 만들지 않고(CorrectFixedPasswordGenerator()) 람다를 사용해도 됨. (@FunctionalInterface)
//user.initPassword(() -> "ab");
// then
assertThat(user.getPassword()).isNull();
}
}
- User 인스턴스를 만들어서 위에서 함수형 인터페이스 PasswordGenerator 를 구현한
CorrectFixedPasswordGenerator() , WrongFixedPasswordGenerator() 를 주입해서 테스트 합니다.
'공부내용 정리 > 자바' 카테고리의 다른 글
의존성 주입 (Dependency Injection, DI) (2) | 2023.10.17 |
---|---|
JDBC 프로그래밍 (1) | 2023.10.11 |
객체지향 패러다임 3 (학점계산기) (1) | 2023.09.05 |
객체지향 패러다임 2 (계산기) (3) | 2023.09.05 |
접근 제어자 (2) | 2021.12.05 |