본문 바로가기

개발(합니다)/방법론

TDD 학습 및 실습 정리7(TDD작성패턴)

반응형

TDD 학습 및 실습 정리6에 이어 정리합니다.




상황별 테스트


생성자 테스트

굳이 테스트 케이스를 작성하지 않습니다.
초기 값을 가져야 하는 생성자에 따라 생성하여 테스트를 작성합니다.

DTO 스타일의 객체 테스트

단순 setter/getter로 이루어진 DTO는 굳이 테스트 하지 않습니다.
특정 목적을 가진 불변 객체의 경우에는 getter나 is로 테스트를 작성합니다.

닭과 달걀 메소드 테스트

메소드가 서로 맞물려 있는 경우
로직 메소드(add, remove, set 계열 메소드)와 상태 확인 메소드(get, show, is 계열 메소드)가 짝일 경우

public class Test{
@Test
public void add() throws Exception{
User user = new User();
user.add("kim");
assertEquals("kim" ,user.get(1));
}

@Test
public void get() throws Exception{
User user = new User();
user.add("kim");
assertEquals("kim" ,user.get(1));
}
}

1. 실패하는 테스트 케이스가 두 개인 상태에서 작업(일반적인 방법)

케이스 난이도가 낮으면 좋지만 난이도가 높으면 파악이 어렵습니다.

2. 안전성이 검증된 제 3의 모듈을 사용(가능하다면 권장)

DbUnit과 같은 모듈을 사용합니다.

3. 자바 리플렉션을 이용해 강제로 확인(대체적으로 비권장)

해결 했다고 해도 대부분 잘못 된 접근 방식입니다.


배열 테스트

JUnit4의 assertArrayEquals를 활용합니다.

객체 동치성 테스트

assertEquals로는 객체 비교가 되지 않으니 내부 필드를 꺼내와서 비교합니다.
assertSame를 사용합니다.


컬렉션 테스트

case1 : 자바 기본형이나 String 컬렉션에 들어 있는 경우    

assertEquals를 사용하면 열거 형태로 꺼내서 순차적 비교를 합니다.



웹에서의 TDD

뷰 TDD

다소 테스트 하기가 까다로운 부분이 있습니다.
selenium과 같은 프레임워크를 사용합니다.

selenium 참고

pom.xml
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.141.59</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-server</artifactId>
            <version>3.141.59</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-chrome-driver</artifactId>
            <version>3.141.59</version>
        </dependency>

        <!-- guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>27.0.1-jre</version>
        </dependency>


SeleniumTest.java

package com.otrodevym.test.seleium;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

public class SeleniumTest {
//  private WebDriver driver;

    /*@Before
    public void setUp() throws Exception {
        System.setProperty("webdriver.chrome.driver", "src/test/resources/chromedriver.exe");
        driver = new ChromeDriver();
    }*/

    @Test
    public void seleniumTest_셀레니움테스트() throws Exception {
//      driver.get("http//www.google.com/xhtml");
//      Thread.sleep(5000);

        // Optional, if not specified, WebDriver will search your path for chromedriver.
        System.setProperty("webdriver.chrome.driver", "src/test/resources/chromedriver.exe");

        WebDriver driver = new ChromeDriver();
        driver.get("http://www.google.com/xhtml");
        Thread.sleep(5000); // Let the
//       user actually see something!
        WebElement searchBox = driver.findElement(By.name("q"));
        searchBox.sendKeys("ChromeDriver");
        searchBox.submit();
        Thread.sleep(5000); // Let the user actually see something!
        driver.quit();

    }

    /*@After
    public void tearDown() throws Exception {
        driver.quit();
    }*/

}

기본 사용 테스트



컨트롤 TDD

테스트에는 어려움이 없고 뷰에서 요청 받은 데이터를 전달하고 받고 응답을 확인합니다.


test와 main을 구분합니다.


EmployeeSearchServletTest.java
package com.otrodevym.test.servlet;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

public class EmployeeSearchServletTest {

    @Test
    public void SearchByEmid_직원을_아이디로_검색() throws Exception{
        MockHttpServletRequest req = new MockHttpServletRequest();
        MockHttpServletResponse res = new MockHttpServletResponse();
        
        req.addParameter("empid", "1234");
        
        EmployeeSearchServlet searchServlet = new EmployeeSearchServlet();
        searchServlet.service(req,res);
        
        Employee employee = (Employee)req.getAttribute("employee");
        assertEquals("이름 확인", "kim", employee.getName());
        assertEquals("아이디 확인", "1234", employee.getEmpid());
        
        assertEquals("URL 확인", "/searchResult.jsp", res.getForwardedUrl());
    }
}


EmployeeSearchServlet.java

package com.otrodevym.test.servlet;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

public class EmployeeSearchServlet extends HttpServlet {

    private SearchBiz searchBiz;

    public void service(MockHttpServletRequest req, MockHttpServletResponse res)
throws ServletException, IOException {
        searchBiz = new SearchBiz();
        Employee employee = searchBiz.getEmployeeByEmpid(req.getParameter("empid"));

        req.setAttribute("employee", employee);
        RequestDispatcher dispatcher = req.getRequestDispatcher("/searchResult.jsp");
        dispatcher.forward(req, res);
    }
    
    public void setModel(SearchBiz biz) {
        this.searchBiz = biz;
    }

}


SearchBiz.java

package com.otrodevym.test.servlet;


public class SearchBiz {

    public Employee getEmployeeByEmpid(String parameter) {
        Employee emp = new Employee();
        
        emp.setName("kim");
        emp.setEmpid("1234");
        return emp;
    }
}



모델 TDD

모델은 크게 두 부분으로 구분합니다.
- 도메인 모델(DTO) : 단순하기에 작성 할 필요가 거의 없습니다.
- 서비스 모델(기능) : 어플리케이션의 핵심이므로 최대한 많이 작성합니다.



웹에 대한 TDD 접근 전략 정리

- 모델, 뷰, 컨트롤러를 최대한 분리
- 뷰는 단순히 표현 계층으로 보고 업무 로직이 들어가지 않도록 유지
- 뷰에 대한 ROI를 따져보고 필요하다면 TDD말고 Record&Play방식의 툴을 이용
- 컨트롤러가 프레임워크 차원에서 지원 될 때는 굳이 만들지 않음
- 모델에 대한 TDD는 최대한 적용


데이터베이스에서 TDD

데이터베이스는 어려움이 있습니다.
1. 테스트를 진행하면서 데이터베이스에 들어 있는 데이터의 상태가 바뀝니다.
2. 테스트 전/후의 데이터 비교가 쉽지 않습니다.

최선의 방법은 없으므로 상황에 따라 처리해야 합니다.

1. 데이터베이스 상태가 바뀌는 문제의 일반적인 해결 방법


- 트랜잭션을 선언하고 테스트 케이스를 수행한 다음 롤백처리합니다.
장점 : 선언하면 되기에 간단합니다.
단점 ; 작성 목적이 '트랜잭션 처리'인 경우 불가

- 테스트 케이스 작성 시 '입력 -> 수정 -> 삭제' 순서대로 테스트 케이스가 실행되도록 합니다.
장점 : CRUD를 작성 순서만 지켜줍니다.
단점 : CRUD가 아닌 경우에 불가

- SQL 스크립트가 테스트 수행 시에 실행되도록 만듭니다.
장점 : sql문장만 관리하면 됩니다.
단점 : sql파일이 많아지면 별도의 관리가 필요합니다.

- DbUnit을 사용합니다.
장점 : 여러 가지 기능으로 효과적으로 테스트가 가능하고 sql문을 작성하지 않아도 됩니다.
단점 : DbUnit을 배워야 합니다.


2. 테스트 전후의 데이터베이스 상태를 비교하는 방법

- 예상 결과를 미리 다른 테이블에 넣어 놓고, 대상 테이블과 예상 테이블을 각각 select문으로 결과를 받아와서 비교합니다.(가능하지만 불편)

- 예상 결과를 미리 문자열로 만들어 놓고 select문을 실행해서 이용해 비교합니다.
DBMS는 sql실행 결과를 파일로 만들어주는 기능을 제공합니다.

- DbUnit과 Unitils를 함께 사용합니다.
@Test
@ExpectedDataSet("expected_seller.xml")
public void testAddNewSeller() throws Exception {
Seller newSeller = new Seller("kim","김","kim@naver.com");
Repository repository = new DatabaseRepository();
repository.add(newSeller);
}



테스트 메소드의 리팩토링

테스트 픽스처에는 객체 초기화보다는 셋팅에 중점을 맞춰야합니다.

package anti_pattern;

import static org.junit.Assert.assertEquals;

import org.junit.Before;
import org.junit.Test;

// 권장하지 않는 패턴
// 테스트 픽스처는 셋팅에만 사용하길 권장.
public class AccountTest2 {

    private Account account;
    
    @Before
    public void setUp() throws Exception{
        this.account = new Account(1000);
    }
    @Test
    public void deposit() throws Exception{
        account.deposit(1000);
        assertEquals("입금", 1000, account.getMoney());
    }
    @Test
    public void withdraw() throws Exception{
        account.withdraw(100);
        assertEquals("출금", 900, account.getMoney());
    }
}



package anti_pattern;

import static org.junit.Assert.assertEquals;

import org.junit.Before;
import org.junit.Test;

// 권장 패턴
public class AccountTest {

    
    @Before
    public void setUp() throws Exception{
        
    }
    @Test
    public void deposit() throws Exception{
        Account account = new Account(1000);
        account.deposit(1000);
        assertEquals("입금", 1000, account.getMoney());
    }
    @Test
    public void withdraw() throws Exception{
        Account account = new Account(1000);
        account.withdraw(100);
        assertEquals("출금", 900, account.getMoney());
    }
}


반응형