반응형

요즘 프론트엔드 리액트를 학습 중인데, 빠르게 따라치다보면 줄 정렬도 엉망이고 보기 안좋은 코드가 되기 일쑤다.

이런 것을 일관되게 조율해주는게 툴을 쓰는 이유라 볼 수 있는데, snippet부터해서 prettier, styled-component extension등 이것 저것 다 설치를 했는데도, 저장 시 자동 정렬 처리를 해주지 않아서 일관된 코드 작성이 어려웠다.

 

설정이 풀려있어서 적용법을 공유해본다.

 

먼저, 당연히 Prettier는 설치가 되어 있어야한다.

VS CODE

파일 - 기본 설정 - 설정

formatter라고 검색을 하고

 

 

기본 Formatter가 없음으로 되어 있었는데, Prettier로 설정을 변경해준뒤, 잘 돌아가는 걸 볼 수 있었다.

반응형
반응형

자주 사용하는 명령어들을 모아두기 위해 포스팅을 진행합니다.

 

typescript for react

타입스크립트 버전으로 리액트 프로젝트가 생성된다. my-app부분에 프로젝트명으로 입력

npx create-react-app my-app --template typescript

 

 

styled-component

태그를 생성하고 css를 꾸밀수 있고, 변수를 활용할 수 있다.(1번은 일반, 2번은 타입스크립트버전 설치)

npm i styled-components // 일반.ver
npm i --save-dev @types/styled-components // typescript.ver

 

 

react-router-dom

BrowserRouter, Routes, Route, Link등의 컴포넌트를 사용하여 설정한 경로에 따라 페이지를 렌더링해주는걸 도와준다.

react route dom이 v6이상으로 올라오면서 사용법이 대체적으로 변경되었다. 

(예를들면 Link to prop에 object형태로 옵션을 줄 수 있었으나 각각 prop형태로 적용하도록 변경되었고, 기존에 Switch의 컴포넌트 대신 Routes로 변경되었다.)

npm i react-router-dom

 

 

react-query

useQuery를 사용하여 fetch함수등을 사용하여 api로 데이터를 요청하고 받아오던 행위들을 모아놓고 모듈화를 도와준다.

또한 데이터를 캐싱시켜서 페이지 로드시 속도 개선에도 도움을 준다.

npm i @tanstack/react-query

 

 

react-helmet

사용하면 어디서든 해당 페이지 document의 head태그로 접근 할 수 있다.

title이나 favicon, css, meta태그등 다양하게 접근하고 변경할 수 있도록 도와준다.

npm i --save-dev @types/react-helmet

 

 

recoil

Reactstate management 라이브러리로 페이스북에서 만들었다.

redux와 비슷한 개념으로 사용이 간편하고 쉽다 atom을 사용하여 어디서든 state에 접근하고 사용할 수 있으며, atom의 데이터를 원하는 형태로 변형하여 읽기전용 느낌의 seletor를 사용할 수도 있다.

npm install recoil

 

 

react-hook-form

form태그와 input, select등 form입력의 다양한 키입력 온클릭등의 이벤트 처리와 유효성을 도와준다.

npm install react-hook-form

 

 

react-beatiful-dnd

React프로젝트에서 드래그 앤 드랍처리를 도와주는 라이브러리다.

DragDropContext와 Droppable, Draggable 영역에 대한 이해와 magic(provider), snapshot의 대한 공부를 하고 작성하면 생각보다 쉽게 이벤트 처리를 할 수 있고, 드래그중 드래그 완료 후 각각의 요소별로 css등의 처리를 할 수 있다.

npm i react-beautiful-dnd // 일반.ver
npm i --save-dev @types/react-beautiful-dnd // typescript.ver

 

 

framer-motion

react페이지의 인터렉티브한 효과를 도와주는 애니메이션 라이브러리입니다.

간단한 prop이나 layout속성 추가만으로 컴포넌트간의 애니메이션을 연결해주거나 처리해줄수 있습니다.

npm install framer-motion

 

gh-pages

작성한 리액트 프로젝트를 빌드하여 자신의 github로 배포하도록 도와준다.

npm i gh-pages

 

 

반응형
반응형

React는 state의 값의 변화에 따라 UI가 변화하는 특징을 가지고 있다.

그렇다보니 데이터를 가지고 놀때 useState를 통해 값을 처리하고 setState를 통해 변경하고 변경된 UI부분만 재 처리하는 작업을 반복하는데, 해당값을 상위의 컴포넌트부터 최하단의 컴포넌트까지 복잡한 구조를 props를 통해 넘겨야한다면 굉장히 불필요한 데이터 전달처리로 소스도 난잡해지고 중간 중간의 컴포넌트들은 사용하지도 않는 props데이터를 넘기다보니 처음 소스를 받는사람은 이건 뭔데 넘기고 있을까? 라는 생각을 가질 수도 있을 것이다.

 

이럴때 전역 state처리를 해주는 redux나 recoil이 있는데, recoil에 대하여 알아보고자 한다.

 

Recoil

recoil은 state management 라이브러리로 페이스북팀에서 만들었고, 굉장히 간결하고 강력하다.

실제 예시를 보고 개념도 쉽고 그림도 잘 그려져서 너무 좋았다.

 

설치하기

먼저 사용을 해보기 위해 설치를 해야한다.

npm install recoil

 

설치가 끝나면 recoil설정 파일이 필요하다.

recoil에서 특정 전역처리를 하는 영역 덩어리를 atom이라고 부른다.

atoms.ts파일을 열어주고

전역 state를 처리할 값을 입력해주고 필요한 부분들에서 불러서 사용하면 된다.

 

그 전에 전체를 RecoilRoot로 App을 감싸주는걸 잊지 말자!

 

-atom

atoms.ts

import { atom } from "recoil";

export const isDarkAtom = atom({
    key: "isDark",
    default: true,
})

atom에는 key와 default값을 object형태로 넣어준다.

key값은 global state로 사용할 key값이고, default는 기본 값이다. 기본값으로 인해 자료형도 선택이 된다.

 

- useRecoilValue

전역 state값 불러오기

파라미터에는 호출할 atom을 넣어줍니다.

import { isDarkAtom } from "../atoms";
import { useRecoilValue } from "recoil";

const isDark = useRecoilValue(isDarkAtom);

 

- useSetRecoilState

전역 state값 변경하기

파라미터에는 호출할 atom을 넣어줍니다.

import { isDarkAtom } from "../atoms";
import { useSetRecoilState } from "recoil";

const setIsDark = useSetRecoilState(isDarkAtom);
setIsDark(true); //atom의 state값 변경

 

- 값불러오고 변경하기를 useState처럼 처리하기

파라미터에는 호출할 atom을 넣어줍니다.

import { useRecoilState } from "recoil";
import { isDarkAtom } from "../atoms";

const [isDark, setIsDark] = useRecoilState(isDarkAtom);
console.log(isDark);
setIsDark(true);

많이 보던 형태가 아닐까 싶습니다.

바로 useState랑 생김새가 똑같습니다.

값만 불러야하거나 변경만 해야하면 위에서처럼 하나만 import해서 쓰면 되지만, 둘다 필요한 경우에는 해당 방식이 좋아보입니다. :)

 

 

반응형
반응형

react-hook-form을 사용하여 form태그 내의 input태그의 onChange이벤트, 유효성 검사, submit후 데이터 값 확인 등 다양한 작업을 손쉽게 처리 할 수 있습니다.

 

기존의 react를 사용하여 해당 기능을 처리하려면 useState를 통해 처리할 값고 set함수를 선언하고 onChange이벤트에 넣어주고  value값에 state값을 넣어주고, 유효성을 체크하려면 onChange에 걸어둔 함수에서 길이를 체크하거나 정규식으로 패턴을 검사하거나 한개의 input만 해도 아주 복잡한 코드가 만들어지는데, 복잡한 회원가입이나 입력폼에서 이 과정을 실행하려면 상당한 긴 소스가 작성될 것입니다. 이런 문제를 간단하게 해결해 줄 수 있습니다.

 

먼저 설치를 진행합니다.

npm install react-hook-form

 

기본적으로 사용을 위해서 useForm이라는 hook을 항상 import해서 사용합니다.

import { useForm } from "react-hook-form"

 

1. register

register는 input 기능에 특화되어 있는 함수로 onBlur, onChange를 가지고 있는 함수입니다.

처리하고자 하는 input태그 내에 {...register("name")} 하나만으로 모든 처리가 끝납니다.

 

-사용 방법

import { useForm } from "react-hook-form";

interface IForm{
    userId: string;
    email: string;
}

function App(){
    const { register } = useForm();

    return (
        <div>
            <form>
                <input {...register("userId")} placeholder="아이디" />
                <input {...register("email")} placeholder="이메일" />
                <button>추가</button>
            </form>
        </div>
    )
}

export default App;

단지 jsx input 내부에 {...register("이름")}만으로 onChange이벤트에 의해 value값이 처리되는 걸 볼 수있습니다.

 

-유효성 검사

register함수의 2번째 파라미터부터 유효성 검사를 할 요소를 object형태로 넣어주면 됩니다.

{
    required: "아이디를 입력하세요.",
    minLength: {
        value: 5,
        message: "아이디는 최소 5자리 이상입니다."
    },
    maxLength: {
        value: 12,
        message: "아이디는 최대 12자리 이상입니다."
    }    
}

required: true, minLength:5, maxLength: 12 이런식으로 넣어줘도 되지만, 오류메시지 출력을 위해 value값과 message로 나눠서 처리해주었습니다.

 

-유효성 검사 예시

import { useForm } from "react-hook-form";

interface IForm{
    userId: string;
    email: string;
}

function App(){
    const { register } = useForm();

    return (
        <div>
            <form>
                <input {
                    ...register("userId",
                    {
                        required: "아이디를 입력하세요.",
                        minLength: {
                            value: 5,
                            message: "아이디는 최소 5자리 이상입니다."
                        },
                        maxLength: {
                            value: 12,
                            message: "아이디는 최대 12자리 이상입니다."
                        },
                    }
                )} placeholder="아이디" />
                <input {...register("email")} placeholder="이메일" />
                <button>추가</button>
            </form>
        </div>
    )
}

export default App;

 

-pattern(정규식 체크)

<input {
    ...register("email", {
        required: "이메일을 입력하세요.",
        pattern: {
            value: /^[a-zA-Z0-9+-\_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/,
            message: "이메일 형식이 아닙니다."
        },
})} placeholder="이메일" />

 

-validate옵션을 통한 커스텀을 통한 유효성 검사

validate: {
    noAdmin: (value) => value.includes("admin") ? "admin은 포함 시킬 수 없습니다." : true,
    noShin: (value) => value.includes("Shin") ? "Shin은 포함 시킬 수 없습니다." : true,
}

validate의 return값이 false 또는 문자열이 발생하면 유효하지 않는 값으로 인식하기 때문에, 오류로 처리하고자 하는 값이면 false처리하거나 메시지를 입력하면 되고 유효한 데이터라면 true를 처리해주면 됩니다.

 

 

2. watch

form태그의 입력된 값들의 변화를 관찰해주는 함수입니다.

userForm에서 watch함수를 호출하기만 하면 값이 변경될 때 마다 동작하는 것을 확인 할 수 있습니다.

const { register, watch } = useForm<IForm>();

console.log(watch());

 

값을 입력할때마다 console.log에 와치함수가 동작하여 태그 값들이 찍히고 있다.

 

 

3. handleSubmit

onSubmit의 동작을 처리해줄 handleSubmit입니다.

handleSubmit에는 2개의 인자를 받습니다.

onValid와 onInvalid

타입스크립트 설명에서 알 수 있듯이 첫번째 파라미터는 필수값이며, 유효한 경우 동작할 함수를 넣어줍니다.

두번째 파라미터는 선택이며, 유효하지 않은 경우 동작할 함수를 넣어주면 됩니다.

 

사용법은 form태그 onSubmit에 해당 함수를 넣어주면 됩니다.

import { useForm } from "react-hook-form";

interface IForm{
    userId: string;
    email: string;
}

function App(){
    const { register, handleSubmit } = useForm<IForm>();

    const onValid = (data:IForm) => {
        console.log(data);
    }

    return (
        <div>
            <form onSubmit={handleSubmit(onValid)}>
            </form>
        </div>
    )
}

export default App;

onValid라는 함수를 만들었습니다.

IForm interface의 정의를 받는 타입의 object값들을 받아서 console.log에 찍어주는 함수를 생성하였습니다.

form태그 내부에는 onSubmit태그 내의 handleSubmit함수를 생성하였고 첫번째 인자에 onValid를 넣어주었습니다.

 

 

4. formState:erros

useForm내의 formState를 사용하면 유효하지 않는 값들을 걸러내고 어떤 오류의 종류인지 오류메시지는 무엇인지 알 수 있습니다.

formState를 호출하고 formState의 errors메시지를 호출하는 것으로 에러들을 체크 할 수 있습니다.

const { register, handleSubmit, formState } = useForm<IForm>();
console.log(formState.errors);

이것들을 각 항목별로 유효성 오류를 표출시킬 수 있습니다.

사진에서 볼 수 있듯이 register에서 등록한 이름별로 오류메시지들이 출력됩니다.

type에는 어떤 종류인지 나옵니다.

message에는 문자열로 정의한 오류 메시지가 출력됩니다.

해당 정보들을 토대로 아래처럼 각 항목 아래에 오류 메시지를 출력시킬 수 있습니다.

 

errors?.항목이름?.message

오류 데이터가 없는 경우가 있을 수 있으니 꼭 optional(?.)처리를 해주세요.

 

-오류 표출해보기

import { useForm } from "react-hook-form";

interface IForm{
    address: string;
    email: string;
    nickName: string;
    phone: string;
    userId: string;
    userPw: string;
    userPwConfirm: string;
}

function Join(){
    const { register, watch, handleSubmit, formState:{errors} } = useForm<IForm>();

    //handleSubmit은 2개의 인자를 받는데 첫번째는 유효한 데이터인 경우 호출되는 함수다.
    //두번째 인자는 유효하지 않는 경우 호출되는 함수로 첫번째 함수만 필수값이다.
    const onValid = (data:IForm) => {
        console.log(data);
    }
    
    //에러체크는 formState의 error함수를 사용하여 알 수 있다.
    console.log(errors);
    return (
        <div>
            <form 
                style={{display:"flex", flexDirection:"column"}} 
                onSubmit={handleSubmit(onValid)} >
                <input {...register("userId", {required: "회원 아이디를 입력하세요."})} placeholder="아이디"/>
                <span>{errors?.userId?.message}</span>
                <input {...register("userPw", {required: "비밀번호를 입력하세요.", minLength: 10})} placeholder="비밀번호"/>
                <span>{errors?.userPw?.message}</span>
                <input {...register("userPwConfirm", {required: "비밀번호 확인을 입력하세요.", minLength: 10})} placeholder="비밀번호확인"/>
                <span>{errors?.userPwConfirm?.message}</span>
                <input {...register("nickName", {required: "닉네임을 입력하세요.", minLength: 10})} placeholder="닉네임"/>
                <span>{errors?.nickName?.message}</span>
                <input {
                    ...register("email", {
                        required: "이메일을 입력하세요.",
                        pattern: {
                            value: /^[a-zA-Z0-9+-\_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/,
                            message: "이메일 형식이 아닙니다."
                        },
                    })}
                    placeholder="이메일"/>
                <span>{errors?.email?.message}</span>
                <input {...register("address", {required: "주소를 입력하세요."})} placeholder="주소"/>
                <span>{errors?.address?.message}</span>
                <input {...register("phone", {required: "전화번호를 입력하세요."})} placeholder="전화번호"/>
                <span>{errors?.phone?.message}</span>
                <button>회원가입</button>
            </form>
        </div>
    );
}

export default Join;

작성 후 회원가입 버튼을 눌러서 submit동작을 시켜보면 각 오류메시지 타입들이 노출됩니다.

formState의 errors메소들을 주로 많이 사용하기 때문에 아래처럼 바로 가져와서 사용합니다.

const { formState:{errors} } = useForm<IForm>();

 

 

5. defaultValues

defaultValues로 기본값을 처리할 수 있습니다.

useForm의 첫번째 인자에 object형태로 정의해주면 됩니다.

const { register, handleSubmit, formState:{errors} } = useForm<IForm>({
    defaultValues: {
        phone: "010"
    }
});

타입스크립트로 useForm<IForm> 을 처리해둔 덕에 자동완성으로 리스트들이 나옵니다.

defaultValues내부에 원하는 값을 넣고 기본값을 입력해주면 UI에 바로 입력되어서 나오는 걸 볼 수 있습니다.

 

 

6. setError

setError를 통해 강제로 에러를 발생시킬 수도 있습니다.

역시나 타입스크립트가 사용법에 대해서 알려주고 있습니다.

 

첫번째 인자에는 에러를 발생시킬 항목을  두번째에는 에러 옵션을 넣으라고 나옵니다. 세번째는 선택입니다.

 

아래는 비밀번호 확인을 하여 오류가 발생한 경우를 작성했습니다.

const { setError } = useForm<IForm>();

const onValid = (data:IForm) => {
    if(data.userPw !== data.userPwConfirm){
        setError("userPwConfirm", 
                    {message: "비밀번호가 일치하지 않습니다."},
                    {shouldFocus: true}
            );
    }
}

submit동작을 해보면 오류가 발생하는 것을 볼 수 있습니다.

캡처에서는 빨간 밑줄이 그어져있지만 바로 포커싱을 가게 하고 싶다면 shouldFocus옵션을 true로 주면 된다.

 

 

 

또한 onValid나 InValid쪽에서 서버 오류등이 발생한 경우에도 강제로 오류를 발생시켜주면 좋을텐데 이런 경우에도 사용할 수 있습니다.

 

예외 처리에 대한 값이 필요한데 타입스크립트이므로 interface에 form태그에 대한 값을 추가하고, jsx에서 오류처리를 하던 부분에 동일하게 처리를 해주면 됩니다.

다만 interface에서 해당 extra값은 필수값은 아니므로 '?'처리를 해줍니다.

 

-비밀번호와 비밀번호 확인이 다른 경우 오류 발생처리(Full ver_Join.tsx)  & 강제 오류발생으로 메시지 띄우기 소스

import { useForm } from "react-hook-form";

interface IForm{
    userId: string;
    extraError?: string;
}

function Join(){
    const { 
        register, 
        handleSubmit, 
        formState:{errors},
        setError
    } = useForm<IForm>({
        defaultValues: {
            phone: "010"
        }
    });

    //handleSubmit은 2개의 인자를 받는데 첫번째는 유효한 데이터인 경우 호출되는 함수다.
    //두번째 인자는 유효하지 않는 경우 호출되는 함수로 첫번째 함수만 필수값이다.
    const onValid = (data:IForm) => {
        if(data.userPw !== data.userPwConfirm){
            setError("userPwConfirm", {message: "비밀번호가 일치하지 않습니다."});
        }
        setError("extraError", {message: "알 수 없는 오류가 발생했습니다."});
    }
    
    return (
        <div>
            <form 
                style={{display:"flex", flexDirection:"column"}} 
                onSubmit={handleSubmit(onValid)} >
                <input {...register("userId", {required: "회원 아이디를 입력하세요."})} placeholder="아이디"/>
                <span>{errors?.userId?.message}</span>
                <input {...register("userPw", {required: "비밀번호를 입력하세요.", minLength: 10})} placeholder="비밀번호"/>
                <span>{errors?.userPw?.message}</span>
                <input {...register("userPwConfirm", {required: "비밀번호 확인을 입력하세요.", minLength: 10})} placeholder="비밀번호확인"/>
                <span>{errors?.userPwConfirm?.message}</span>
                <input {...register("nickName", {required: "닉네임을 입력하세요.", minLength: 10})} placeholder="닉네임"/>
                <span>{errors?.nickName?.message}</span>
                <input {
                    ...register("email", {
                        required: "이메일을 입력하세요.",
                        pattern: {
                            value: /^[a-zA-Z0-9+-\_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/,
                            message: "이메일 형식이 아닙니다."
                        },
                    })}
                    placeholder="이메일"/>
                <span>{errors?.email?.message}</span>
                <input {...register("address", {required: "주소를 입력하세요."})} placeholder="주소"/>
                <span>{errors?.address?.message}</span>
                <input {...register("phone", {required: "전화번호를 입력하세요."})} placeholder="전화번호"/>
                <span>{errors?.phone?.message}</span>
                <button>회원가입</button>
                <span>{errors?.extraError?.message}</span>
            </form>
        </div>
    );
}

export default Join;

 

submit동작 후 강제 에러가 발생된 것을 볼 수 있습니다.

 

 

7. setValue

setValue를 통해 강제로 값을 변경할 수 있다.

입력 후 특정 값을 비워야하거나 자동 맞춤을 해주거나 할 경우 필요한 기능으로 2개의 필수 파라미터와 세번째는 옵션값을 갖는다.

첫번째 파라미터는 대상(항목)이며, 두번째 파라미터는 처리할 값이다.

처리할 값은 타입스크립트라면 정의해놓은 값으로 넣어주면 된다. string > string, number > number

사용 예시

setValue("toDo", ""); //toDo항목의 값을 비워준다.
반응형
반응형

최근 프론트엔드로 전향하기 위해 리액트를 학습하기로 마음을 먹었습니다.

노마드 코더 영상을 보면서 기초부터 나중에 유료버전도 결제하면서 추가 학습을 할 예정인데, 이런 내용들을 정리하고 공부하는 흔적을 남기고 싶어서 티스토리 블로그도 좋지만 깃허브에 잔디심기를 해보는것으로 마음먹었습니다.

 

기존 회사에서도 깃을 사용해왔기에 깃허브정도는 어렵지 않게 사용할 수 있고, 깃 명령어나 사용법도 잊지 않기에 괜찮다고 생각이 들어서 어제부터 저장소를 생성하고 학습한 내용을 하나씩 올리고 오늘도 추가로 학습한 내용을 올리고 있는데, 잔디심기가 되지 않고 있었습니다.

 

확인해보니 깃허브의 메일주소와 로컬의 메일주소가 달라서 발생하는 현상이라고 합니다.

예전에 로컬에는 간단하게 깃 테스트정보로 마구 입력을 해둔값이라 전혀 유효한 값이 아닌 메일 값이 있었고,

git bash에서 아래 명령어를 입력하여 수정하였습니다.

git config user.email "이메일 주소"

 

변경된 이메일 주소는 아래 명령어로 확인 가능합니다.

git config --list

 

이제 귀염뽀짝한 2일차 잔디심기 현황입니다...

앞으로도 열심히 달려봐야겠습니다.

 

반응형
반응형

테스트 자동화에 대해 알아보겠습니다.

 

개발을 진행하다보면 다양한 함수를 작성하고 적용하는 과정에서 다양한 케이스별 테스트를 진행하게 됩니다.

단순한 코드라면 테스트도 크게 어렵지 않고 실수나 잘못된 연산 있더라도 쉽게 수정이 가능하지만, 복잡하고 케이스별로 오류가 발생할수 있는 함수를 작성 중이라면, 아래와 같은 경우를 많이 만나 볼 수 있습니다.

 

1. test( params ); 라는 함수를 작성하였다.

2. test(1)은 정상 동작한다.

3. test(2)는 오류가 발생한다.

4. test(2)에 대한 오류를 수정하고, test(2)가 정상 동작한다.

> 수정을 했으니 이제 정상 동작하는 소스일까? ===> 아니오

수정으로 인해 test(2)는 정상이 되었지만, 정상동작하던 test(1)이 오류가 발생할 수도 있고 다른 케이스별 오류가 발생할 수 있다.

 

개발을 하다보면 생각보다 굉장히 자주 만나는 현상입니다.

그럼 개발자는 무엇을 어떻게 할까요?

정상 동작을 하는지 유형 별로 정리를 하고 작성한 코드를 테스트 > 수정 > 테스트를 반복합니다.

 

이렇게 수동으로 테스트를 진행하다보면 에러가 발생할 여지가 있습니다.

 

 

 

 

 

 

BDD방법론

BDD는 테스트(test), 문서(documentation), 예시(example)를 한데 모아놓은 개념으로 실제 개발사례를 통해 BDD에 대해 알아보겠습니다.

 

 

 

 

거듭제곱 함수를 통해 자동화 테스트 적용해보기

pow라는 이름의 함수를 만들고 BDD에 적용해보겠습니다.
(자바스크립트 자체에 ** 연산자나 Math.pow 메소드를 활용해도 되지만 BDD 학습을 위해 작성합니다.)
 
describe("거듭제곱 출력 함수", function() {
	it("매개변수의 n제곱", function() {
		assert.equal(pow(2, 3), 8);
	});
});
 

describe("title", function(){...})

구현하고자 하는 기능에 대해 설명이 들어갑니다. 여기선 pow함수에 대한 설명을 입력합니다.

 

it("유형별 설명", function(){...})

it의 첫번째 인수엔 특정 유스 케이스에 대한 설명이 들어갑니다. 이곳에는 누가 읽고 이해할 수 있도록 자연어로 작성합니다. 두번째 인수엔 테스트 할 함수가 들어갑니다.

 

assert.equal(value1, value2)

기능을 제대로 작성했다면 해당 구문이 오류없이 실행됩니다.

value1과 value2가 일치하지 않는다면 오류가 발생합니다.

value1에 pow(3, 3)를 value2에는 81을 입력하면 일치할 것이고 value2에 다른값을 입력하면 에러가 발생합니다.

 

 

사용되는 라이브러리

3가지의 라이브러리를 활용합니다.

Mocha - describe, it와 같은 테스팅함수와 테스트 관련 주요함수를 제공합니다.

Chai - 다양한 assertion을 제공하는 라이브러리입니다. (assert.equal)

Sison - 함수의 정보를 캐내는 라이브러리로 내장 함수를 모방합니다.

 

 

 

 

 

자동화 테스트해보기

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css"> <!-- Mocha css -->
</head>
<body>
    <div id="mocha"></div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script> <!-- Mocha -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script> <!-- chai -->
    <script>
        let assert = chai.assert;
        window.onload = function(){
            mocha.setup('bdd'); // mocha setup

            describe("pow함수 검증하기", function(){
                it("주어진 숫자의 n 제곱", function(){
                    assert.equal(pow(3, 3), 27);
                    assert.equal(pow(3, 4), 81);
                    assert.equal(pow(3, 5), 243);
                });

            });

            mocha.run();
        };
        
        //pow 작성
        function pow(x, n) {
            if(n < 2){
                return x;
            }
            return x * pow(x, --n);
        }
    </script>
</body>
</html>

거듭제곱 메소드를 재귀함수를 통해 작성하였습니다.

작성한 pow를 테스트화 시킨 코드입니다. 아래에서 테스트 결과를 보겠습니다.

 

결과

테스트 정상 화면

title과 테스트 결과가 정상적인경우 위 사진처럼 발생합니다.

 

 

 

 

 

오류 발생시키기

describe("pow함수 검증하기", function(){
    it("주어진 숫자의 n 제곱", function(){
        assert.equal(pow(3, 3), 27);
        assert.equal(pow(3, 4), 81);
        assert.equal(pow(3, 5), 243);
        assert.equal(pow(2, 2), 222);
    });
});

일부러 마지막에 오류를 내면 어떻게 될까요?

 

오류 결과

정상동작하는 3개에 대해선 문제가 없지만 오류가 발생중인 부분에 대해서 오류 메시지를 출력해줍니다.

오류메시지를 세분화 해보겟습니다.

 

 

 

 

 

오류메시지 분기하기

describe("pow함수 검증하기", function(){
    it("주어진 숫자의 n 제곱 3^3 = 27", function(){
        assert.equal(pow(3, 3), 27);
    });
    it("주어진 숫자의 n 제곱 3^4 = 81", function(){
        assert.equal(pow(3, 4), 81);
    });
    it("주어진 숫자의 n 제곱 3^5 = 243", function(){
        assert.equal(pow(3, 5), 243);
    });
    it("주어진 숫자의 n 제곱 2^2 = 4", function(){
        assert.equal(pow(2, 2), 222);
    });
});

각 유형별로 it로 나누고 각각의 설명을 기재하였습니다.

아래는 오류 메시지 결과입니다.

 

분기처리한 오류 메시지 결과

체크박스 형태로 성공은 체크, 실패한 부분은 x와 함께 메시지를 출력해주고 있네요.

유형별 결과를 한눈에 보기엔 분기처리 한 것이 보기 좋은것 같습니다.

 

 

 

 

 

자료 출처: https://ko.javascript.info/testing-mocha

 

테스트 자동화와 Mocha

 

ko.javascript.info

반응형
반응형

일정 시간마다 특정 URL의 화면을 캡처하여 저장해달라는 요청이 있었다.

 

자바로 스케줄러를 구성하고 URL에 해당하는 정보를 가지고와서 JEditorPane 통해 이미지 컨텐츠를 구성하고 파일을 생성하는 예제이다. JEditorPane는 자바2에서부터 존재했던 클래스로 다양한 컨텐츠를 편집하기 위한 텍스트 컴퍼넌트이다.

해당 컴포넌트는 EditorKit 구현을 사용하여 동작하여 상속을 받은 클래스를 사용했습니다.

 

 

JAVA로 웹페이지 화면 저장하기

테스트용 화면 JSP페이지

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="/assets/css/test.css">
</head>
<body>
    <div>
        <p class="title">o 금일 할일</p>
        <p>- 프로젝트 검수</p>
        <p>- GIT 최신화</p>
    </div>
</body>
</html>

 

 

파일 생성처리

import java.awt.Container;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;

public class App {
	public static void main(String[] args) {
		BufferedImage ire;
		// 저장할 서버 url
		String requestUrl = "http://localhost/test/view";

		// 저장될 위치 + 파일명
		String path = "E:/test/test.jpg";

		App app = new App();
		// 사이즈 크기 설정 및 생성
		ire = app.create(requestUrl, 200, 200);
		try {
			ImageIO.write(ire, "PNG", new File(path));
		} catch (IOException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		}
	}

	static class Kit extends HTMLEditorKit {
		private static final long serialVersionUID = 2048542251827518481L;

		public Document createDefaultDocument() {
			HTMLDocument doc = (HTMLDocument) super.createDefaultDocument();
			doc.setTokenThreshold(Integer.MAX_VALUE);
			doc.setAsynchronousLoadPriority(-1);
			return doc;
		}
	}

	public BufferedImage create(String src, int width, int height) {
		BufferedImage image = null;
		JEditorPane pane = new JEditorPane();
		Kit kit = new Kit();
		pane.setEditorKit(kit);
		pane.setEditable(false);
		pane.setMargin(new Insets(0, 20, 0, 20));

		try {
			pane.setPage(new URL(src));
			image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
			Graphics g = image.createGraphics();
			Container c = new Container();
			SwingUtilities.paintComponent(g, pane, c, 0, 0, width, height);
			g.dispose();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return image;
	}
}

 

 

동작결과

 

다만, 웹페이지의 100% 유동적인 이미지로 생성하는 방식은 찾지를 못했다. web page에서 특정 데이터에 사이즈를 담아두고 파싱부분에서 처리하면 가능할 수도 있겠지만, 응답을 해주지 않는 타사이트라면 해당 방식 적용도 어려울 것으로 보인다.

 

 

반응형
반응형

자바에서 Stream 객체과 관련된 객체를 사용하다보면, finally부분에서 사용한 자원을 해제하는 로직을 입력하거나 예외처리가 발생한 부분에서 해제시켜야 했습니다.

아래 소스를 보겠습니다.

 

 

기존 자원 해제 방식

1. catch문에서 자원해제

public class TryWithResources {

	public void readTxtFile(String path) {
		BufferedReader bufferedReader = null;
		try {
			bufferedReader = new BufferedReader(new FileReader("e:\\text.txt"), 16 * 1024);
			String str;
			while ((str = bufferedReader.readLine()) != null) {
				System.out.println(str);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			try {
				if(bufferedReader != null) bufferedReader.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		} catch (IOException e) {
			e.printStackTrace();
			try {
				if(bufferedReader != null) bufferedReader.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		} 
	}
}

catch문이 많아진다면... 해제해야할 자원이 많아진다면... 상상만 해도 끔찍합니다.

 

 

2. finally를 통한 마지막에서 해제

public class TryWithResources {

	public void readTxtFile(String path) {
		BufferedReader bufferedReader = null;
		try {
			bufferedReader = new BufferedReader(new FileReader("e:\\text.txt"), 16 * 1024);
			String str;
			while ((str = bufferedReader.readLine()) != null) {
				System.out.println(str);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if(bufferedReader != null) bufferedReader.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
	}
}

finally는 무조건 실행되므로 마지막에 전부 자원이 해제됩니다.

catch가 더 많았다면 finally로 처리하는 모습이 비교적 가독성은 더 좋아보일 것 같습니다.

 

Try With Resources

public class TryWithResources {

	public void readTxtFile(String path) {
		try (BufferedReader bufferedReader = new BufferedReader(
        						new FileReader("e:\\text.txt"), 16 * 1024);){
			String str;
			while ((str = bufferedReader.readLine()) != null) {
				System.out.println(str);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}
}

try() 안에 자원 객체를 사용하면 try를 벗어날때 자동으로 자원을 해제해줍니다.

 

Java7부터 생긴 기능으로 자원을 실수로 해제하지 않는 실수를 막아줄 수 있기에 자원을 사용하고 예외처리를 해야할 때,  사용한다면 좋을 것 같습니다.

 

다만, 모든 자원을 무조건 해제해주는것은 아니고 AutoCloseable인터페이스로 구현된 객체들만 자원이 해제됩니다.

즉, 개발자가 작성한 코드도 자동으로 자원을 해제하고 싶다면 AutoCloseable인터페이스를 상속받아서 작성하셔야 합니다.

 

반응형