본문 바로가기

공부내용 정리/자바

객체지향 패러다임 1 (Password Validator)

패스트캠퍼스 "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