본문 바로가기
Spring

[웹프로젝트] 3. 단위테스트? 멤버 mapper

by moca7 2024. 10. 25.

 

 

ㅁ 회원서비스 한다.

 

 

 

ㅁ 

- 원래 개발은 쿼리부터 하고 dao 메소드 만들고

- 쿼리테스트라고해서 지금 실행할 이 쿼리문ㅇ ㅔ문제가 없는지 일괄적으로 테스트를 해볼 수 있다. 그걸 쿼리테스트라고 한다.

 

 

 

 

 

ㅁ member-mapper.xml

 

 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="memberMapper">

  <resultMap id="memberResultMap" type="MemberDto">
    <result column="user_no" property="userNo" />
    <result column="user_id" property="userId" />
    <result column="user_pwd" property="userPwd" />
    <result column="user_name" property="userName" />
    <result column="email" property="email" />
    <result column="gender" property="gender" />
   
    <result column="phone" property="phone" />
    <result column="address" property="address" />    
    <result column="profile_url" property="profileURL" />
    <result column="signup_date" property="signupDt" />
    <result column="modify_date" property="modifyDt" />
    <result column="status" property="status" />                                        
  </resultMap>


  <!-- 로그인 -->
  <select id="selectMember" resultMap="memberResultMap">
      select
             user_no
           , user_id
           , user_pwd
           , user_name
           , email
           , gender
           , phone
           , address
           , profile_url
           , signup_date
           , modify_date
           , status
        from member  
       where status in ('Y', 'A')
         and user_id = #{userId}
         and user_pwd = #{userPwd}
  </select>



  <!-- 회원가입 -->
  <insert id="insertMember">
      insert
        into member
        (
          user_no
        , user_id
        , user_pwd
        , user_name
        , email
        , gender
        , phone
        , address
        )
        values
        (
          seq_uno.nextval
        , #{userId}
        , #{userPwd}
        , #{userName}
        , #{email}
        , #{gender}
        , #{phone}
        , #{address}
        )
  </insert>
 
 

  <!-- 아이디중복체크(아이디 수 조회) -->
  <select id="selectUserIdCount" resultType="_int">
      select
             count(*)
        from member
       where user_id = #{checkId}      
  </select>
  <!-- 한 개의 컬럼, 한 개의 숫자만 조회된다. 자바에서의 int로 반환되게끔 한다. -->



  <!-- 회원정보수정 -->
  <update id="updateMember">
      update
             member
         set user_name = #{userName}
           , email = #{email}
           , phone = #{phone}
           , address = #{address}
           , gender = #{gender}
           , modify_date = sysdate
       where user_id = #{userId}        
  </update>
 
 

  <!-- 회원탈퇴 (행을 삭제하진 않고 개인정보만 일부 지운다. 이름 첫글자만 남기고 마스킹처리. -->
  <update id="deleteMember">
      update
             member
         set email = null
           , gender = null
           , phone = null
           , address = null
           , profile_url = null
<!--       , user_name = rpad( substr(user_name, 1, 1), 최종반환글자수, 덧붙일문자열 ) -->
           , user_name = rpad( substr(user_name, 1, 1), length(user_name), '*' )
           , status = 'N'
           , modify_date = sysdate
       where user_id = #{userId}
             
  </update>



</mapper>

 

 

 

ㅁ MemberDao

 

 
package com.br.spring.dao;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Repository;

import com.br.spring.dto.MemberDto;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Repository
public class MemberDao {

    private final SqlSessionTemplate sqlSession;
   
    public MemberDto selectMember(MemberDto m) {
      return sqlSession.selectOne("memberMapper.selectMember", m);
    }
   
    public int insertMember(MemberDto m) {
      return sqlSession.insert("memberMapper.insertMember", m);
    }
   
    public int selectUserIdCount(String checkId) {
      return sqlSession.selectOne("memberMapper.selectUserIdCount", checkId);
    }
   
    public int updateMember(MemberDto m) {
      return sqlSession.update("memberMapper.updateMember", m);
    }
   
    public int deleteMember(String userId) {
      return sqlSession.update("memberMapper.deleteMember", userId);
    }
   
}

 

 

 

 

 

ㅁ 회원서비스 쿼리들 쓰고 dao도 만들어봤다.

 

 

 

 

 

ㅁ 쿼리에 문제가없는지 테스트해볼 수 있다.

- junit 기반의 테스팅 기반의 프레임워크가 있다.

단위테스트를 진행할 수 있다.

- 단위테스트는 소스코드가 내가 의도한대로 잘 작동되는지 함수나 메소드 등을 테스트하는 것이다.

- 테스트 관련 클래스를 작성하면 된다.

그걸 테스트 케이스라고 한다.

 

 

 

ㅁ JUnit

- Java 개발시 가장 많이 사용되는 테스팅 기반 프레임워크

- 독립된 단위 테스트를 지원함

- 단위 테스트 : 소스코드가 의도한 대로 잘 작동되는지 함수 및 메소드에 대한 테스트

- assert(단정 메소드)로 테스트 케이스의 수행 결과를 판별할 수 있다.

- 개발 단계 초기에 문제점을 발견해서 빠르게 해결할 수 있다.

 

 

 

 

- JUnit도 라이브러리가 등록되어있다. 애초에 있던 라이브러리다.

pom.xml에 가보면 Lombok 위에 Test라고 junit이 하나 있다. 버전이 4로 시작학 ㅗ있다. 

junit 쥬피터는 junit 5버전으로 보면 된다.

 

 

 

 

- spring-test

- 5.3.27

 

 

 

 

- 이렇게 pom.xml을 수정했다.

 

 

 

 

 

 

 

 

 

ㅁ src/test로 시작하는 폴더가 있다.

 

 

- 이게 테스트케이스를 작성하는 소스폴더다.

- 열어보면 패키지가 하나 있다.

여기에 특정 패키지를 만들어서 테스트케이스에 해당하는 클래스를 작성해도 되는데

우리가 테스트할 클래스가 MemberDao다.

MemberDao의 메소드를 강제로 실행할 것이다.

- 멤버dao 우클릭 - new - junit test case

 

 

 

 

 

 

 

ㅁ MemberDaoUnitTest_JUnit4 

 
package com.br.spring.dao;

import static org.junit.Assert.*;

import org.junit.Test;

public class MemberDaoUnitTest_JUnit4 {

  @Test
  public void test() {
    fail("Not yet implemented");
  }

}

 

- 이렇게 만들어진다.

 

 

 

 

 

ㅁ MemberDaoUnitTest_JUnit4 수정

 

 

 

 

- 클래스 상단에 @RunWith(SpringJUnit4ClassRunner.class) 어노테이션을 붙여야 한다. 빨간줄이 뜬다. import 한다.

이걸 의미하는 것은 테스트를 JUnit4를 이용하겠다는 의미다.

JUnit4를 쓸 때는 반드시 이걸 명시해야 한다.

 

 

 

 

- 다 지운다.

 

 

 

 

 

ㅁ 테스트 케이스 작성 패턴

- given (준비) : 어떤 데이터를 가지고 

- when (실행) : 어떤 메소드 실행시

- then (검증)  : 어떤 결과가 나와야 하는지 

 

- assertEquals(예상값, 실행값) : 실행값이 예상값과 일치하는지 확인해주는 메소드

- assertNotNull(실행값) : 실행값이 null이 아닌지 확인해주는 메소드

- assertTrue(조건) : 해당 조건이 참인지를 확인해주는 메소드

 

 

 

 

 

ㅁ MemberDaoUnitTest_JUnit4 

 

 
package com.br.spring.dao;

import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@FixMethodOrder(MethodSorters.NAME_ASCENDING) // 메소드의 이름 순으로 테스트를 수행하겠다는걸 의미
@ContextConfiguration(locations= { "file:src/main/webapp/WEB-INF/spring/root-context.xml",
                  "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class) // JUnit4를 이용하겠다는 것을 의미한다.
public class MemberDaoUnitTest_JUnit4 {

  @Autowired
  private MemberDao memberDao;
 
 
  @Test
  public void test01_로그인테스트() {
   
  }
 
  @Test
  public void test02_회원가입테스트() {
   
  }
 
  @Test
  public void test03_일치하는아이디수조회테스트() {
   
  }
 
  @Test
  public void test04_회원정보변경테스트() {
   
  }
 
  @Test
  public void test05_회원탈퇴테스트() {
   
  }

}

 

- 각각의 메소드마다 dao의 메소드를 호출한다.

그러기 위해서는 객체가 필요하다. 전역 변수로 MemberDao 필드를 둔다.

여기에 생성된 객체가 주입되어야 한다. final을 붙이고 final에 대한 매개변수 생성자가 만들어지게 했었다ㅡ.

그런데 테스트할때는 서버를 구동하지 않는다.

JUnit 테스트를 하는 버튼을 클릭할 것이다.

그런데 MemberDao는 서버를 스타트 해야만 servlet-context.xml에 있는 빈 스캐닝에 의해서 읽혀지면서 빈등록이 진행된다.

서버를 구동하지 않아서 MemberDao는 빈등록이 되지 않는다.

- 테스트를 진행시킬 때 강제로 빈을 등록시키는 파일을 읽어들일 예정이다. 이걸로 주입을 받을 수 있다.

클래스 위에 @ContextConfiguration 어노테이션을 작성한다. import 한다.

 

 

 

※ @ContextConfiguration

- 테스트시에는 서버를 구동하지 않기 때문에 자동으로 빈 등록이 되지 않는다.

이때 해당 객체를 빈으로 등록시키는 파일을 강제로 읽어들일 때 필요한 어노테이션이다.

- 어노테이션 안에 아래를 작성한다.

 

(1) <bean>

- location="file:src/main/webapp/WEB-INF/spring/root-context.xml"

 

(2) @Bean

- 자바 방식. 

- classes="AppConfig.class"

 

(3) 빈스캐닝

- servlet-context.xml 파일을 읽어

- location="file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"

 

 

 

 

- 참고로 MemberDao는 SqlSessionTemplate 객체를 주입받고 있다. 이 객체가 필요하다.

얘는 root-context.xml에서 빈등록을 하고 있다.

- root-context.xml 파일도 읽어들여야 한다.

- 2개의 파일을 강제로 읽어들여서 빈등록이 되게 해야 MemberDao 객체를 주입받을 수 있다.

- @Autowired ~

 

- @FixMethodOrder(MethodSorters.NAME_ASCENDING) // 메소드의 이름 순으로 테스트를 수행하겠다는걸 의미

 

- 일부러 메소드명을 test01~, test02~로 작성했다. 순차적으로 테스트가 진행된다.

 

= 어떤 데이터를 가지고 어떤 메소드를 실행ㅅ이 어떤 결과가 나와야 되는지를 검증한다.

- 검증 단계에서는 내가 예상하는 값과 실행값을 가지고 비교한다. 

실행한 값을 가지고 true로 나오는지를 가지고 해석하면 된다.

 

 

 

 

 

ㅁ MemberDaoUnitTest_JUnit4 

 

 
package com.br.spring.dao;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.br.spring.dto.MemberDto;

@FixMethodOrder(MethodSorters.NAME_ASCENDING) // 메소드의 이름 순으로 테스트를 수행하겠다는걸 의미
@ContextConfiguration(locations= { "file:src/main/webapp/WEB-INF/spring/root-context.xml",
                  "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class) // JUnit4를 이용하겠다는 것을 의미한다.
public class MemberDaoUnitTest_JUnit4 {

  @Autowired
  private MemberDao memberDao;
 
 
  @Test
  public void test01_로그인테스트() {
    // given
    MemberDto m = MemberDto.builder()
                 .userId("admin01")
                 .userPwd("1234")
                 .build();
   
    // when
    MemberDto result = memberDao.selectMember(m); // 유효한 아이디 비번이기때문에 조회결과가 반드시 존재할거다. null이 아닐거다.
    // 우리가 예상하기로 result는 null이 아닐 거다. 그럼 검증알 해야 한다. result가 null이 아닌지.
   
    // then
    assertNotNull(result); // 스태틱 메소드다. ctrl shift o 하면 import static문이 생긴다.
    // ~~
   
   
  }
 
 
  @Test
  public void test02_회원가입테스트() {
    // given
    MemberDto m = MemberDto.builder()
                     .userId("test01") // insert를 위해 db에 없는 아이디 제시
                       .userPwd("1234")
                       .userName("테스트")
                       .email("test@br.or.kr")
                       .gender("M")
                       .phone("010-1111-2222")
                       .address("서울시 강서구 마곡동")
                       .build(); // 참고로 실제로 insert까지 진행된다. 이 데이터는 이미 들어가서 다시 실행하면 unique 제약조건 위배될 수 있다.
   
    // when
    int result = memberDao.insertMember(m); // 쿼리에 문제가 없고 예상하는 값으로는 1이 올것 같다.
   
    // then
    assertEquals(1, result); // 스태틱 메소드다. ctrl shift o 하면 import static문이 생긴다.
    // 둘이 일치하다면 테스트 성공, 일치하지 않으면 테스트 실패로 안내된다.
  }
 
 
  @Test
  public void test03_일치하는아이디수조회테스트() {
    // 중복체크할 아이디를 문자열로 쿼리에 넘긴다.
    // given
    String checkId = "user01";
   
    // when
    int result = memberDao.selectUserIdCount(checkId);// 존재하는 아이디이기 때문에 예상값이 1이다. 1이 돌아올것 같다.
       
    // then
    assertEquals(1, result);
  }
 
 
  @Test
  public void test04_회원정보변경테스트() {
    // given
    MemberDto m = MemberDto.builder()
                 .userId("user01")
                 .userName("변경테스트")
                 .email("updatetest@br.com")
                 .phone("010-0000-0000")
                 .address("서울시 강남구")
                 .gender("F")
                 .build();
   
    // when
    int result = memberDao.updateMember(m); // 문제없다면 1로 돌아올 것이다. 변경될 데이터도 딱히 제약조건에 위배되는 데이터가 아니다.
   
    // then
    assertEquals(1, result);
  }
 
 
  @Test
  public void test05_회원탈퇴테스트() {
    // given
    String userId = "user02";
   
    // when
    int result = memberDao.deleteMember(userId); // id 문제 없기 때문에 예상값 1.
   
    // then
    assertEquals(1, result);
  }

}

 

- admin01, 1234 회원이 있다.

 

- 테스트는 메소드를 다 작성하고 한꺼번에 테스트를 진행하는 것이다.

 

 

- 성공이라고 잘 뜨면 db가서 잘반영됐는지도 확인한다. 

- 테스트는 서버를 구동하지 않는다. 필요한 객체 주입은 빈등록하는 파일을 강제로 읽어들이게 해놨다.

- 테스트는 해당 클래스를 가지고 

현재 이 클래스가 src/test/java라는 소스폴더 안에 만들어져 있다.

 

 

 

 

 

 

- 확장했을 때 초록색 체크표시가 되어있으면 성공이다. 

 

 

 

 

 

 

 

 

- user02 이름이 마스킹처리되었다.

- user01 데이터가 바뀌었다.

- test01 데이터가 생성되었다. 

 

 

 

 

 

ㅁ 메소드 위에 @Ignore를 붙이면 그 메소드는 무시하고 테스트가 진행된다.

ㅁ 초반에 쿼리쪽 오류를 잡아두고 웹개발에 집중한다.