Download - Cygnus unit test
Cygnus Unit Test
전략개발팀 박성재
Agenda
• Test?• TDD• Database Test• Service Tier Test• Web Tier Test• Test Coverage
인수 테스트
스트레스 /부하 테스트
기능 테스트
통합 테스트
테스트 종류
유닛 테스트
테스트 종류• 유닛 테스트 - 작성이 쉽다 , 로직의 버그와 디자인
문제 ( 단일 책임 원칙 , 하드 코딩 ) 를 찾는다
• 통합 테스트 – 객체 간 , 서비스 간 , 서브 시스템 간 상호 작용 테스트
• 기능 테스트 – 공개된 api 의 가장 바깥 부분 테스트 , 유스케이스 단위 테스트
• 스트레스 테스트 – jMeter 등의 전용 도구 이용
• 인수 테스트 – 개발자가 아닌 고객 혹은 QA 에서 진행
Mockito
Tapestry Test
Test 관련 Tools
Spring Test
유닛 테스트
“ 유닛 테스트 (unit test) 는 소스 코드가 의도된 대로 정확히 작동하는지 검증하는 절차다 . 즉 , 모든 클래스와 메소드에 대한 테스트 케이스를 작성하는 절차를 말한다 .”
wikipedia.org
Why Unit Test
• 디버깅은 많은 시간을 소비하고 싶지 않아• 새로운 기능 추가나 리팩토링 후 기존
기능들이 잘 동작하는지 확신하고 싶어• 테스트 코드를 읽고 class 의 동작을
명확하게 이해할 수 있지• 유닛 테스트를 통해 프로젝트 헬스와 코드
퀄리티를 측정할 수 있거든
Why not Unit Test
• 난 절대 실수 하지 않아• 기능이 너무 단순해• 테스트 코드까지 만들 시간이 없어
• 테스트 하는 방법을 몰라 ㅠㅠ
유닛 테스트 특징• Isolated– 타이어 테스트 할 때 자동차까지 만들지 말자
• Repeatable– 모든 개발자에서 테스트 가능– 환경에 영향 받지 않게
• Fast– 시간은 돈이다 .– 쉽고 빠르게 테스트 코드를 만들 수 있어야 한다
• Self-Documenting– 테스트 코드는 단순해서 이해하기 쉬워야 한다– 테스트 코드를 설명하는 문서가 필요 없어야 한다
First Unit TestPublic class Calculator { public double add(double number1, double number2) { return number1 + number2; }}
import static org.junit.Assert.*;import org.junit.Test;public class CalculatorTest { 1.public class @Test 2.unit test public void test() { Calculator calc = new Calculator(); double result = calc.add(10, 20); 3.대상메소드콜 assertEquals(30, result, 0); 4. 결과 확인 }}
First Unit Test 실행
Test Driven Development(TDD)
“ 좋은 코드는 테스트하기 쉽다 . 그 반대도 마찬가지다 .”
Cygnus Tier Flow
CreateServer
ServerService
ServerDao
saveServer(server)
saveServer(server)doSomething(server)
session.save(server)
ViewServer
ServerService
ServerDao
getServerById(id)
getServerById(id)
session.get(Server.class, id)
http://.../viewserver/{id}
Server
Web Tier :
Service Tier :
Dao Tier :
Persistent Tier :
Database Test 어려움
• Isolated– DB 는 외부에 있다
• Repeatable– Test 할 때마다 DB 가 변경된다
• Fast– DB 접속은 상대적으로 느리다– DB 접속 코드는 복잡하고 어렵다• 초기 데이터 입력• 평가 코드 작성
Database Test 전략
• Embedded DB(H2, HSQL) 사용– Fast–개발자 별로 독립적 실행 가능
• DbUnit 사용–테스트 코드 작성 용이–쉬운 초기 데이터 셋팅 –쉬운 평가 방법 제공
Database Test 대상
• 클래스와 테이블간 맵핑 오류– DB 예약어 사용 예 )user, index, unique, max– 제품 DB 변경 시 활용
• 테이블 릴레이션– one-to-many 등에서 이상한 forign 키 관계가
없는지 ?– Cascade 가 잘 동작하는지 ?
• 조회 쿼리– 단일 객체 반환을 원하는데 복수 객체가 리턴되지
않는지 ?
Database Test Code@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("/META-INF/spring/test-applicationContext.xml")@Transactional@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, TransactionDbUnitTestExecutionListener.class, DbUnitTestExecutionListener.class })
public class ServerDaoTest {
@Test @DatabaseSetup("testServer.xml") 1. 초기 데이터 @ExpectedDatabase(value="empty.xml", 2. 기대 데이터 검증 assertionMode=DatabaseAssertionMode.NON_STRICT) public void testDeleteServer() { Server server = serverDao.getById(1); serverDao.delete(server); 3. 테스트 대상 serverDao.flush(); 4. ORM 캐시 비우기 assertThat(serverDao.findAll().size(), is(0)); 5. delete 검증 }
Service 계층 Test 어려움
• Isolated–다른 서비스 /계층 (DAO) 이 연관되어 있다–다른 시스템 (Agent, Mail Server, REST) 과
연관되어 있다• Repeatable– Test 할 때마다 모든 환경을 구축하기 어렵다
• Fast–테스트 코드 작성시 많은 노력이 필요하다
Service 계층 Test 전략
• Stub 객체 작성– Stub 이란 실제 대상과 유사하게 동작하는
객체 (Agent 시뮬레이터 )–동작 방식을 stub 객체에 코딩한다– Stub 제작의 어려움이 남아 있다
• Mock 객체 이용–Mock 이란 실제 객체와 유사한 동작을 하지만
시키는
Service 계층 Test 전략
• Mock 객체 이용–Mock 이란 실제 객체를 흉내내는 객체–동작 방식은 외부에서 알려준다 .–Mock Framework 활용한 손쉬운 작성• Mockito, EasyMock, Jmock
Service 계층 Test 코드@Servicepublic class ServerServiceImpl implements ServerService { @Autowired ServerDao serverDao;
@Transactional public Server discoveryServer(String ipAddress) { Server server = serverDao.findServerByIpAddress(ipAddress); if(server != null) { throw new ServerDuplicatedException("Duplicated ipAddres=" + ipAddress); } Server newServer = new Server(); newServer.setIpAddress(ipAddress); serverDao.save(newServer); return newServer; }
Service 계층 Test 코드@RunWith(MockitoJUnitRunner.class) 1. mockito 러너 사용public class ServerServiceTest {@InjectMocks 2. mock 주입ServerService serverService = new ServerServiceImpl();@Mock 3. mock 객체 생성ServerDao serverDao;
@Testpublic void testDiscoveryServer() { String ipAddress = "127.0.0.1"; //stub 4. mock 동작 정의 when(serverDao.findServerByIpAddress(ipAddress)).thenReturn(null);
//run Server server=serverService.discoveryServer(ipAddress); 5. 대상 메소드 실행
//assert assertThat(server.getIpAddress(), equalTo(ipAddress)); 6. 메소드 리턴 결과 판정 verify(serverDao).findServerByIpAddress(ipAddress); 7. mock 호출 여부 확인 verify(serverDao).save(server); 8. mock 호출 여부 확인}
Service 계층 Test 코드
@Test(expected=ServerDuplicatedException.class) 1. 예외 기대 및 판정public void testDiscoveryServerDuplicated() { String ipAddress = "127.0.0.1"; Server existServer = new Server(); when(serverDao.findServerByIpAddress(ipAddress)).thenReturn(existServer); 2. mock 동작
Server server = serverService.discoveryServer(ipAddress); 3. 테스트 메소드 실행}
Web 계층 test 어려움 • 컴파일 타임에 문법 오류를 잡을 수 없다– template 파일 , javascript 등
• 페이지 /컴포넌트간 링크가 잘 동작하는 테스트기 어렵다
• 국제화나 validate 등의 리소스 버그를 테스트 하기 어렵다
• Isolate– 서비스 계층이 구현되지 않은 경우 테스트가
어렵다• Fast– 웹 어플리케이션 기동 후 육안 검사에 의존
Web 계층 테스트 전략
• 서비스 계층을 mock 객체로 활용• Tapestry Test framework 활용– PageTester.renderPage()– PageTester.clickLink()
• Selenium 테스트 도입
Web 계층 테스트 코드public class ViewServerTest {PageTester tester;
@Before 1. test setUppublic void setUp() { String appPackage = "com.nkia.cygnus.management.server"; String appName = "development"; tester = new PageTester(appPackage, appName, "src/main/webapp", TestAppModule.class);}
@Testpublic void testExistServer() { ServerService serverService = tester.getService(ServerService.class); 2. MockServerServic
when(serverService.getServerById(1)).thenReturn(newServer()); 3. Mock 동작 정의
Document doc = tester.renderPage("server/ViewServer/1"); 4. 대상 페이지 렌더링 assertThat(doc.toString(), containsString("View Server - testserver123456")); 4. 정상 여부 판정}
Web 계층 테스트 코드// link test@Testpublic void testDeleteNotExistServer() { ServerService serverService = tester.getService(ServerService.class); 1. Mock 객체 when(serverService.findServersAll()).thenReturn(newServerList()); 2. Mock 동작 정의 when(serverService.getServerById(1)).thenReturn(null);
Document doc = tester.renderPage("server/ServerPage"); 3. page rendering Element delete = doc.getElementById("delete"); 4. link 객체 얻기 assertThat(delete, notNullValue()); 5. link 존재 검증
Document linkDoc = tester.clickLink(delete); 5. link 클릭 assertThat(linkDoc.toString(), containsString("Server not found. id = 1")); 5. link 동작 검증}
Test 커버리지 보고서
• 소스의 단위 테스트 커버리지 측정• Cobertura 활용• 테스트 커버리지 기준을 만들고 일정 수준이
되어야 만 릴리즈 할 수 있는 정책 가능– http://cms.nkia.net:8088/projects/cygnus/c
ygnus-management-server/cobertura/index.html
• 클래스 복잡도 측정 - McCabe's cyclomatic complexity)– http://blog.wisedog.net/110