테스트가능한 소프트웨어설계와 TDD 작성패턴 Testable software design & TDD patterns 한국스프링사용자모임 (KSUG ) 채수원
발표자소개 LG CNS 경영기술교육원기술교육팀전임강사 강의과목디자인패턴 & 리팩터링 분석설계실무 Agile 적용실무 블로그여름으로가는문 blog.doortts.com Comments 내용이나후기에대해서는 Outsider님의후기 (http://blog.outsider.ne.kr/494) t /494) 를참조하시면좀더도움이될겁니다.
흠 Comments 2010 년 7 월 10 일화창한토요일오후이대 ECC
Why are we here?
보다더나은소프트웨어와 보다더나은삶을만들기위해
객체지향기본원칙
OCP SRP ISP Demeter s Law (=hollywood law) IOC Comments 언어도열심히배우고원칙도학습했으니까개발을더잘할수있겠죠?
다음두코드중더나은디자인은? Case.1 class Rental { Movie movie; Rental(Service service) { this.movie = service.getmovie(); Case.2 class Rental tl{ Movie movie; Rental(Movie movie) { this.movie = movie;
(based on my five years of educational experience) Strongly recommended d approach #1 테스트주도개발 Test-Driven Di Development
TDD 관점에서바라봤을때드러나는 안좋은디자인의징후 - 단위테스트케이스작성이어렵다 - 단위테스트케이스가자주깨진다. - 단위테스트케이스실행을위한준비해야할것이많다. - 다른사람의테스트케이스를읽기가어렵다.
강형마이크로디자인
기초점검코스
프로그램을작성하기전에테스트먼저하라! Test the program before you write it. 잘동작하는깔끔한코드 Clean code that works 질문 응답 정제 반복 질응답정반복 Ask Respond Refine Repeat
public class Calculator { public int sum(int a, int b) { return 0; public static void main(string[] args) { Calculator calc = new Calculator(); System.out.println( calc.sum(10, 20) == 30 ); System.out.println( calc.sum(1, 2) == 3 ); System.out.println( calc.sum(-10, 20) == 10 ); System.out.println( calc.sum(0, 0) == 0 ); 모두 true면작성완료! ----- 실행결과----- false false false Comments true 굳이프레임워크를쓰지않아도무방합니다. 업무로직작성전에완성상태를검증해줄수있는코드가존재하기만하면충분합니다.
기본코스
생성자메소드테스트 (constructor method test) public class EmployeeDaoTest { @Test public EmployeeDaoTest { EmployeeDao dao = new EmployeeDao(); asserttrue(dao.isconnected());
동치비교 (equivalence test) @Test public void testequals_case2() { Music musica = new Music("BAD", "Michael"); Music musicb = new Music("BAD", "Michael"); assertequals ( musica, musicb);
동치비교 (equivalence test) 해결책.1 해결책.2 내부상태 ( 보통은필드값 ) 를직접꺼내와서각각비교한다. tostring 을중첩구현해 (override) 놓고, tostring 값으로비교한다. 해결책.3 equals 메소드를중첩구현한다. 해결책.3 Unitils 의 assertreflectionequals 를이용한다.
배열비교 (array test) 해결책.1 JUnit 4 의 assertarrayequals 를이용한다. 해결책.2 Unitils 의 assertreflectionequals 나 assertlenientequals 를이용한다 해결책.3 List 로변환해서비교한다.
배열비교 (array test) @Test public void testarrayequal_notsorted() { String[] arraya = new String[] {"A", "B", "C"; String[] arrayb = new String[] {"B", "A", "C"; Arrays.sort sort (arraya); Arrays.sort (arrayb); assertarrayequals (arraya, arrayb);
몇가지오해
boolean isright(){ return TDD == UnitTest
(Do) All or Noting
단위테스트케이스작성
Skeleton vs Incremental package main; public class Account { public Account(int i) { public int getbalance() { return 0; public void withdraw(){ public void deposit(){
One method one assert? @Test public void testgetbalance() throws Exception { assertequals (10000, account.getbalance()); account = new Account(1000); assertequals (1000, account.getbalance()); account = new Account(0); assertequals (0, account.getbalance());
Anti-pattern private Account account; @Before public void setup(){ account = new Account(10000); @Test public void testwithdraw() throws Exception { account.withdraw(1000); assertequals(9000, account.getbalance());
Anti-pattern public class AccountTest { @Before public void setup(){ @Test public void testdeposit() throws Exception { Account account = new Account(10000); account.deposit(1000); assertequals(11000, account.getbalance()); @Test public void testwithdraw() throws Exception { Account account = new Account(10000); account.withdraw(1000); assertequals(9000, account.getbalance());
단위테스트접근방식
상태기반테스트 입력 methoda dosomething? = 예상결과실제결과
상태기반테스트 @Test public void testfileremove() tfil { FileUtile fileutil = new FileUtil(); fileutil.cleancontents( targetfile ); assertequals( 0, fileutil.size( targetfile )); );
행위기반테스트 Case.1 입력 A methoda methodb dosomething rampon Case.2 입력 B methoda methodb call dosomething rampon
행위기반테스트 @Test public void testgetorderprice () throws Exception { PriceCalculator calculator = new PriceCalculator(); Item item = new Item("LightSavor","Kitchen knife",100000); ICoupon coupon = new Coupon(); assertequals(93000, calculator.getorderprice(item, coupon)); int methodcallcount = ((Coupon)coupon).getIsAppliableCallCount(); assertequals (1, methodcallcount);
행위기반테스트 public class Coupon implements ICoupon { private int isappliablecallcount; @Override public boolean isappliable(item item) { isappliablecallcount++; // 호출되면증가.. public int getisappliablecallcount(){ return this.isappliablecallcount;
행위기반테스트 @Test public void testgetorderprice () throws Exception { PriceCalculator calculator = new PriceCalculator(); Item item = new Item("LightSavor","Kitchen knife",100000); ICoupon mockcoupon = mock(icoupon.class); // mocking 작업 assertequals(93000, calculator.getorderprice(item, mockcoupon)); verify (mockcoupon, times(1)).isappliable(box); i (b );
TDD with Spring 스프링프레임워크의 Unit Test 지원 - 의존관계주입을통한객체생성 - 웹컨테이너없는웹애플리케이션테스트 - 단위테스트지원유틸리티 => Injection 과 Mock
XmlBeanFacotry 로컨텍스트가져오는버전 <?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www http://www.w3.org/2001/xmlschemaw3 org/2001/xmlschema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> beans.xsd <bean id="music" class="main.mp3"> <constructor-arg value="belong to me.mp3"/> </bean> <bean id="musicplayer" class="main.musicplayer"> <property name="music" ref="music"/> </bean> </beans> XmlBeanFactory beanfactory = new XmlBeanFactory( new ClassPathResource("/context-musicplayer /contextmusicplayer.xml xml") );
Unitils 를사용 @RunWith(UnitilsJUnit4TestClassRunner.class) public class UnitilsMusicPlayerTest { @SpringApplicationContext("/context-musicplayer /context-musicplayer.xml xml") private ApplicationContext context;
annotation 및 autowire 를사용해서 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"/context-musicplayer.xml") public class AutowiredMusicPlayerTest { @Autowired MusicPlayer player;
Injection기능만사용할경우 Google Guice( 쥬스 ) 로처리해보면 static Injector injector = Guice.createInjector(new MusicModule()); @Test public void testgetfilename() throws Exception { MusicPlayer player = injector.getinstance(musicplayer.class); assertequals("belong To Me.mp3", player.getfilename());
TDD with Spring servlet test 이름 박성철 사번 5874 아이디 직위 fupfin 회장
TDD with Spring servlet test public class EmployeeSearchServletTest { @Test public void testsearchbyempid() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); // ➊ MockHttpServletResponse response = new MockHttpServletResponse(); // ➋ request.addparameter("empid", "5874"); // ➌ EmployeeSearchServlet searchservlet = new EmployeeSearchServlet(); // ➍ searchservlet.service(request, response); // ➎ Employee employee = (Employee)request.getAttribute( getattribute("employee"); employee // ➏ assertequals (" 박성철 ", employee.getname() ); // ➐ assertequals ("5874", employee.getempid() ); assertequals ("fupfin", employee.getid() ); assertequals (" 회장 ", employee.getposition() ); assertequals("/searchresult.jsp", response.getforwardedurl()); // ➑
발표를마치며
Q&A 감사합니다 doortts@gmail.com
이미지참조 Longing for Summer http://www.flickr.com/photos/68165632@n00/1553091251/ Coupling sketches cropped http://www.flickr.com/photos/49432745@n03/4533798684/ Tagged, finally... http://www.flickr.com/photos/kornrawiee/3189034267/ Vincent Boiteau http://www.flickr.com/photos/2dogs_productions