http://download.macromedia.com/pub/labs/jseclipse/autoinstall/
저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1
Eclipse IDE for Java EE Developers

http://www.eclipse.org/downloads/packages/eclipse-ide-java-ee-developers/ganymedesr1

여기가서 걍 EE버전용 보고

우측에 링크되어 있는 해당 OS용 WINDOWS용은 링크

받으면 된다.

귀찮게 업데이트고 뭐고 할 필요도 없다.


동적 웹 프로젝트를 생성 하고 이클립스 자체에서 server.xml을 설정하고 간단히 웹에서 확인하고자 하면

이클립스01

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

저렇게 선택을 하고

 

image

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1
이클립스 3.3, 3.4 버전 이상을 지원한다.
update 주소:http://jora.luenasoft.de/updatesite
사이트 홈페이지:
클릭

아래는 features의 내용을 간단히 끌어온 것

헐 짤리네 된장


Connections View Anzeige und Auswahl der konfigurierten Connections.
Database Explorer Erlaubt das Browsen durch die Objekte der Datenbank.
Detail Browser Anzeige von Detailinformationen zu einem Datenbankobjekt.
SQL Worksheet Absetzen beliebiger SQL-Statements.
SQL Script Editor Standard-Editor für die Datei-Extension sql.
SQL History Historie der ausgeführten SQL-Statements.
SQL Monitor Log aller abgesetzten SQL-Statements.
DBMS-Output View Anzeige aller Ausgaben über DBMS_OUTPUT.
Stored Procedure Editor Bearbeiten von Stored Procedures, Views und Triggern.
PL/SQL Editor Stored Procedure Editor für die Datei-Extension pls.
Outline View Anzeige der logischen Struktur eines Datenbankobjekts.
Sessions View Monitor für die Anzeige aller Sessions auf der Datenbank.
Parameters View Übersicht über die Datenbank- und Session-Parameter.
Connections View
Im Connections View werden alle eingerichteten Connections dargestellt. Hier erfolgt das Anmelden an die Datenbank und das Einrichten der Connections.

Außerdem zeigt der Connections View alle mit den Connections verbundenen Editoren an (SQL Worksheet, Stored Procedure Editor, Detail Browser, SQL Script Editor).

Über das Kontextmenü der Connection kann ein Filter für die anzuzeigenden Schemas definiert werden. Die Einstellungen werden gespeichert und bei späteren Verbindungen wieder benutzt.

 
 
 
Mit dem New Connection Dialog können Verbindungen zu allen Oracle Datenbanken ab Version 8.1.6 hergestellt werden. Über die Option Use custom JDBC connect string kann ein eigener JDBC Connection String angegeben werden, mit dem dann auch Connections für ein Oracle Real Application Cluster (RAC) eingerichtet werden können.

Ebenso werden Proxy User unterstützt. Der Benutzername muss dazu in der Form "proxy_user[proxied_user]" wie bei SQL-Plus angegeben werden.

Für alle Verbindungen können die Passwörter verschlüsselt gespeichert und über die Option Auto logon after startup kann die Verbindung zur Datenbank sofort beim Start von Eclipse aufgebaut werden.

Database Explorer
Mit dem Database Explorer kann komfortabel durch die in der Datenbank definierten Objekte navigiert werden. Die angezeigten Datenbankobjekte gehören immer zur momentan aktiven Connection. Diese wird entweder über den Connections View ausgewählt oder bei Aktivierung eines SQL-Editors oder Detail Browsers automatisch gesetzt.

Der Database Explorer stellt auch die Standardfunktionen für das jeweilige Datenbankobjekt zur Verfügung (z.B. löschen, umbenennen, bearbeiten). Bei Auswahl eines Datenbankobjekts werden dessen Eigenschaften im Detail Browser der Connection angezeigt. Ist der Detail Browser nicht geöffnet oder unsichtbar, so kann er durch Doppelklick auf das ausgewählte Datenbankobjekt geöffnet werden.

Tables

  • rename
  • analyzea
  • truncate
  • drop

Views

  • rename
  • compile
  • edit
  • drop

Stored Procedures

  • Procedures, Functions, Packages, Java Sources
  • compile
  • edit
  • drop

Sequences

  • rename
  • drop

Synonyms

  • Public und Private Synonyme
  • drop

Database Links

  • drop

Triggers

  • rename
  • compile
  • edit
  • enable, disable
  • drop
Die Objektlisten lassen sich nach Schema und Objektnamen filtern. Standardmäßig werden die Objekte des Benutzerschemas angezeigt. Beim filtern nach Objektnamen lassen sich Wildcards verwenden.

Für alle Datenbankobjekte lassen sich Bookmarks definieren, die in einer eigenen Sektion angezeigt werden und die damit den schnellen Zugriff auf häufig benutzte Datenbankobjekte ermöglichen.

Detail Browser

Der Detail Browser zeigt übersichtlich alle Eigenschaften eines Datenbankobjekts an und erlaubt deren Änderung. Die zur Verfügung gestellten Funktionen richten sich nach dem jeweiligen Datenbankobjekt:

Tables

  • Columns (hinzufügen, löschen)
  • Comments (ändern)
  • Data Sheet (anzeigen, ändern, löschen, hinzufügen, filtern und sortieren, commit und rollback)
  • Indexes (hinzufügen, löschen, umbenennen, analyze)
  • Constraints (hinzufügen, löschen, aktivieren, deaktivieren)
  • Trigger (löschen, aktivieren, deaktivieren, anlegen, umbenennen, compile, bearbeiten über Stored Procedure Editor)
  • Grants (hinzufügen, löschen)
  • Used By
  • Synonyms (hinzufügen, löschen)
  • SQL-Script

Views

  • Columns
  • Comments (ändern)
  • Source (bearbeiten über Stored Procedure Editor)
  • Data Sheet (filtern, sortieren)
  • Trigger (löschen, aktivieren, deaktivieren, anlegen und bearbeiten über Stored Procedure Editor)
  • Grants (hinzufügen, löschen)
  • Used Objects
  • Used By
  • Synonyms (hinzufügen, löschen)

Stored Procedures

  • Source (bearbeiten über Stored Procedure Editor)
  • Used Objects
  • Used By
  • Grants (hinzufügen, löschen)
  • Synonyms (hinzufügen, löschen)

Sequences

  • Eigenschaften
  • Used By
  • Grants (hinzufügen, löschen)
  • Synonyms (hinzufügen, löschen)

Database Links

  • Eigenschaften
  • SQL-Script

Triggers

Synonyme

  • Eigenschaften
  • SQL-Script
SQL Worksheet und SQL Script Editor

Die Funktionen beider Editoren sind nahezu identisch. Das SQL Worksheet dient der einmaligen Verarbeitung von SQL-Statements während einer Datenbanksitzung und der SQL Script Editor der Entwicklung und Speicherung von SQL-Scripten. Er ist standardmäßig mit Dateien die die Extension "sql" besitzen verbunden.

In beiden Editoren können beliebige Datenbank-Statements ausgeführt werden. Das Ergebnis von SELECT-Statements wird dabei tabellarisch angezeigt.

Es lassen sich mehrere Befehle markieren und zusammen als Script ausführen (hierbei erfolgt keine Anzeige der Ergebnisse von SELECT-Statements).

Für die schnelle Analyse von SQL-Anweisungen ist die Anzeige des Ausführungsplans (EXPLAIN PLAN) integriert. Mit diesem kann die Ausführung des Statements nachvollzogen und z.B. Maßnahmen für eine Optimierung abgeleitet werden.

Über die Toolbar ist ein einfacher Wechsel zwischen allen offenen Connections möglich. Der Inhalt des Editors bleibt beim Wechsel zwischen den Connections erhalten und kann damit komfortabel in mehreren Connections ausgeführt werden.

Alle ausgeführten Anweisungen lassen sich im SQL Monitor protokollieren.

SQL History

Die SQL History zeigt die letzten 100 in einem SQL-Editor ausgeführten SQL-Statements an. Die Statements werden nach Connections gruppiert und lassen sich über die Eingabe eines Suchtextes filtern.

Außerdem lassen sich Einträge in der History verankern (pinnen) damit sie nicht daraus entfernt werden, wenn Platz zum eintragen neuer Statements benötigt wird.

Die Einträge der History lassen sich die Zwischenablage kopieren um sie z.B. im SQL Worksheet erneut ausführen zu können.

SQL Monitor

Der SQL Monitor zeigt alle von jOra ausgeführten SQL Statements an. Dies betrifft sowohl die eingegebenen Anweisungen als auch die internen Anweisungen.

Zusätzlich zum Statement wird dessen Ausführungszeit protokolliert. Aufgetretene Fehler werden als Kommentar hinter dem Statement ausgegeben.

Die Anzeige lässt sich in eine Datei exportieren und kann somit auch als Log einer Session oder als SQL-Script benutzt werden.

DBMS-Output View
Im DBMS-Output View werden alle Ausgaben über das Package DBMS_OUTPUT angezeigt.

Die Anzeige lässt sich bearbeiten und in eine Datei exportieren.

Stored Procedure Editor und PL/SQL Editor

Der Stored Procedure Editor und PL/SQL Editor erlauben das anlegen und ändern von Stored Procedures, Views und Triggern. Sie bieten dabei viele Funktionen die man heute von einer modernen Entwicklungsumgebung erwartet:

  • Syntaxhighlighting
  • automatisches Einrücken
  • Querverweise auf andere Datenbankobjekte und innere Funktionen/Prozeduren
  • Code Templates
  • Dokumentationsunterstützung
  • komfortable Bearbeitungsfunktionen

Der Stored Procedure Editor lädt die Objekte direkt aus der Datenbank und speichert sie auch dort wieder ab.

Der PL/SQL Editor hingegen ist mit Dateien der Endung pls verknüpft. Er lässt sich einer Datenbank-Connection zuordnen und erlaubt damit neben der normalen Dateibearbeitung auch das abspeichern direkt in die Datenbank.

Beide Editoren unterstützen den Outline View.

Outline View

Der Outline View zeigt bei einem aktiven Stored Procedure Editor die logische Struktur des PL/SQL-Quellcodes an.

Ein Klick auf eine Prozedur im Outline View setzt den Cursor im zugehörigen Stored Procedure Editor sofort an die zugehörige Stelle.

Bei der Navigation im Stored Procedure Editor oder bei Änderungen am Quelltext wird die Anzeige im Outline View sofort aktualisiert.

Bei aktivem Detail Browser zeigt der Outline View alle wichtigen Details des angezeigten Datenbankobjekts an und erlaubt ebenfalls die schnelle Navigation.

Sessions View

Der Sessions View zeigt alle Datenbank-Sessions und deren Zustand an. Die Anzeige wird fortlaufend aktualisiert und sich ändernde Werte werden hervorgehoben.

Die Anzeige lässt sich filtern, z.B. nach aktiven Sessions oder ohne System-Sessions.

Ebenso lassen sich einzelne Sessions beenden (KILL).

Parameters View
In diesem View werden wichtige Parameter der Datenbank angezeigt:
  • alle Oracle Parameter mit ihrem aktuellen und dem Standardwert
  • NLS Parameter der Datenbank, Instance und Session
저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1

D:\JAVAMODELING14\app\eclipse3.2\eclipse.exe -vm C:\Program Files\Java\jdk1.5.0_10\bin\javaw -data D:\My Documents\Project -vmargs -Xverify:none -Xms128m -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M

 

-vm:java 실행기 위치

-data:workspace위치

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1
www.exadel.com
March 2006





Welcome to the next edition of "Extra from Exadel". Our monthly online newsletter provides the open source community with news about our products, industry trends, and upcoming events.


PRODUCTS AND SERVICES:

JSF + AJAX = VCP
Exadel Announces Visual Component Platform 1.0

Exadel Visual Component Platform is a rich component library for JavaServer Faces (JSF) that includes an advanced framework for easily integrating AJAX along with flexible look-and-feel design capabilities into business application development. This new product allows users to quickly incorporate AJAX and flexible design ("skinnability") right into their JSF applications without having to learn new underlying technologies.

Exadel Visual Component Platform 1.0 extends JSF development environments by offering three key elements:

  • Components, which help companies dramatically improve their time to market and quality to market;
  • AJAX-enabled components, which offer the ability to provide a rich user experience based on Web 2.0;
  • Skin-enabled components, which provide a highly customizable visual environment for building web applications.

To see how easily you can add new functionality to your web applications, try the online demo.

For more information about this exciting technology and to download your free 15-day trial, visit our website.

EXADEL STUDIO 3.5 HAS BEEN RELEASED
Based on Web Tools Project (WTP) 1.0

As a follow-up to the release of Exadel Studio Pro 3.5, Exadel Studio 3.5 is now available. Exadel Studio is a free building tool for Struts and JSF Web applications that seamlessly integrate visual and source-oriented approaches for the rapid development and deployment of Web applications. Upgrade your version now!

OPEN SOURCE CORNER:

ECLIPSE GOES SERVERSIDE
Exadel Submits "Enterprise Component Framework Project" to Eclipse Foundation

"At least one new project, if adopted, has in my opinion the potential to forever change the way server applications are built and deployed." -Edward J. Correia, EclipseSource, March 15.

Here is the project, Executive Editor of BZ Media.s SD Times, Edward J. Correia, was talking about. Last week, Exadel officially announced its proposal to the Eclipse Foundation for an Enterprise Component Framework Project, which would create an end-to-end business application development platform based on Eclipse.

The Enterprise Component Framework's goal is to create a comprehensive component model for enterprise application development. The component model will provide a great deal of flexibility for building enterprise applications from components. In addition, applications would benefit from the component approach by acquiring such feature as adaptability e.g. ability to react on changes in environment at runtime. Finally, the project will create the ability to easily create enterprise application from existing components for development teams and users.

The Proposal for the Enterprise Component Framework can be found at the Eclipse Web site.
The press release can be found at our Web site.
Download the files for the Enterprise Component Framework.

TECHNOLOGY PERSPECTIVES:

OSGI/ECLIPSE COMPONENT PLATFORM
By Alex Antonov, Software Architect, Exadel, Inc.

Lately, there have been articles regarding using Eclipse on the server-side rather than the client-side. It.s a very interesting subject for us, too, and we want to share what we.ve been up to in that area. In our article, we will present a new enterprise component model, based on Eclipse Equinox (OSGI), that we believe can provide substantial help in the development of enterprise applications. Our article describes the rationale and ideas behind this enterprise component platform and briefly describes the Enterprise Component Framework itself.

Read the whole article and let us know if you wish to participate in this project.

TEST-DRIVING THE JSF 1.2 RI IMPLEMENTATION ON TOMCAT
By Sergey Smirnov, Principal Architect of the Exadel Studio Product Line

At this time, the official JSF 1.2 RI implementation has not been released. However, the beta version is currently being distributed with the Glassfish Java EE 5 application server (also a beta). Although the 1.2 RI is a beta, it.s in very good condition and it.s quite ready for test-driving to understand how it might fit the requirements for future projects.

To read the article and download the examples, go to http://blog.exadel.com/?p=23.

IN THE NEWS:

IN BRIEF: EXADEL VCP (VISUAL COMPONENT PLATFORM) EASES DEVELOPMENT COMPLEXITY
By China Martens, IDG News Service, March 20, 2006

infoworld China Martens, talks to Fima Katz, Exadel founder, Chief Executive Officer, and President about Exadel.s latest release of Exadel Visual Component Platform 1.0.

"Many companies are under pressure to upgrade their Web applications to give them the 'cool interface' that Google Maps provides as well as improving functionality," Katz said. "Trying to develop such applications can prove complex and time consuming to a company's in-house development team."

"With VCP, Exadel is attempting to mask the complexity, making it easier for developers to build applications using a combination of components based on JavaServer Faces and components enabled by AJAX (asynchronous JavaScript and Extensible Markup Language) technology." -Quoted from the article.

Read more at http://www.infoworld.com/article/06/03/20/76624_HNexadelvcp_1.html

EXADEL'S COMPONENT FRAMEWORK DOES SOME OF THE AJAX DEV WORK FOR YOU
By Kathleen Richards, a senior editor at Application Development Trends, March 21, 2006

adt "Commercial tool provider Exadel is rolling out a Web component framework for building apps based on JavaServer Faces components. What's new is that the open source IDE takes some of the development work out of creating AJAX-enabled components." -Quoted from the article.

Read more at http://www.adtmag.com/article.aspx?id=18148

EVENTS:

EXADEL CEO FIMA KATZ TO SPEAK AT OPENSOLUTIONS WORLD CONFERENCE
April 3 - 6, 2006, Boston Convention and Exhibition Center, Boston, MA

This presentation explores how organizations can create powerful frameworks that will allow them to build and create re-usable yet custom business components. Attendees will get a broad vision of how next-generation technology can allow developers to build systems from pre-built components that are easy to deploy, develop, customize, and test. This strategic shift in development strategy could fundamentally catapult development team productivity, thus challenging participants to explore their own development strategies.

For more information on this conference, visit website of the event.

2006 JAVAONE CONFERENCE IS AROUND THE CORNER!
May 16 - 19, 2006, Moscone Center, San Francisco, CA

You cannot afford to miss JavaOne this year! Exadel's team of experts will be on-hand to provide answers to your questions concerning developing enterprise web applications while leveraging open source technology. Discover why thousands of developers around the globe have partnered with Exadel to efficiently design, develop and deploy web applications with more flexibility and less complexity. We look forward to seeing you there.

For more information on this conference, visit website of the event.

COMMUNITY:

VOTE FOR NEW FEATURES TO ADD TO EXADEL STUDIO

Are you an Exadel Studio or Exadel Studio Pro user? We want to hear from you! Let us know what features you would like to see in the next release of Exadel Studio. Will it be Portlets support, visual Hibernate mappings, or something else?

Vote on the Exadel forum!

YOUR OPINION IS VALUABLE

If you have questions or comments about Exadel Studio, Exadel Studio Pro, or Exadel Visual Component Platform that you would like to share with us and the Exadel community, visit our forum at http://forum.exadel.com/.










크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1

Easy and Affordable
J2EE IDE as an Eclipse Plugin
  MyEclipse 4.1 GA - New AJAX/Web 2.0 Tools  
  MyEclipse 4.1 GA is now available for immediate download at the MyEclipse site. This is the generally available(stable) release of the MyEclipse Enterprise Workbench 4.1 series and the first Eclipse-based IDE to support Web 2.0/AJAX development through an innovative WebTools 2.0 Tools Platform (W2TP) and uniquely integrates Spring, Hibernate and database technologies into a single intuitive solution. MyEclipse 4.1 new features include:
  • Web 2.0 Tool Platform Features (Windows only, except where noted)
  • UML Improvements
  • Visual Web Designer
    • DnD support
      • Palette to Visual Web Designer
      • Snippets view to Visual Web Designer
    • Editing performance improvements
    • Zoom
  • JSF - MyFaces 1.1 support
  • Spring Development Enhancements
    • Bean Config Editor - improved code assist
    • DnD Java class to create bean definition
    • New Wizards for Spring Bean, DataSource and SessionFactory creation and editing
    • Launch from Outline view
  • Hibernate Development Enhancements
    • New DB Reverse-engineering wizard
      • Creates Hibernate mapping files
      • Updates Hibernate configuration file
      • Customize mapping strategy - select/omit columns, control
      • Generate Data Objects (Java class) with optional abstract superclass
      • Generate Data Access Objects (Java class) for Spring or EJB/JNDI
      • Supports User-defined Velocity templates
    • Auto-detect custom Hibernate dialects
    • New Hibernate Mapping file editor (*.hbm.xml)
    • Added MyEclipse libraries for Hibernate 2 and Hibernate 3
    • DnD Java class to create bean definition
    • DnD table from DatabaseExplorer to Hibernate mapping file
    • New Wizards for Spring Bean, DataSource and SessionFactory creation and editing 
    • Launch from Outline view
  • Spring/Hibernate Integration
    • AddSpringCapabilities wizard recognizes Hibernate projects and configures project for integrated operation
    • AddHibernateCapabilites wizard recognizes Spring projects and configures project for integrated operation
    • Reverse-engineer DB schema directly to Spring Hibernate SessionFactory
    • Code generation for Hibernate DAOs will update Spring bean configuration file 
  •  Database Explorer
    • Improved driver management UI
    • Improved driver configuration dialog
    • Auto DB reconnection for timed out connections
  • New Image Editor
    • Crop, resize, rotate, flip, scale, zoom 
    • Layer support
    • Gradient and color tools
    • 60 image filters and transforms 
    • Formats: GIF, JPG, PNG, BMP, PSD, TIFF, TGA, PICT, PCX and RAS
  • Application Server Connectors
    • Toolbar action supports default server restart action
    • Quick navigation to server configuration details from deployment and application server control services and appserver error dialogs
    • Improved Geronimo 1.0 connector to support exploded deployment (development mode)
    • Improved Oracle 10 connector to support JSP debugging (JSR-045)

Please review our New and Noteworthy and the Release Notes sections for additional details on the new features and enhancements included in this release.

Please refer to the special instructions post if upgrading from a previous MyEclipse release. Also note that you must download Eclipse 3.1.1 to use with the latest release. Make sure that you have JDK 1.5 installed to use the UML Modeler on Linux and 1.4.2_05+ or JDK 1.5 for Windows. Unfortunately, due to an Eclipse bug, our Mac users will not be able to access the UML capabilities.

 
     
  New Milestone for MyEclipse  
  Thanks to our user support, the MyEclipse member community is now 200,000 strong. It seems like a lifetime ago, but MyEclipse was first introduced in May 2003. Since then, the MyEclipse community has been growing steadily and has provided much needed feedback and direction to make the Enterprise workbench the most popular Eclipse based IDE. The following statistics speak for themselves:
  • 1.2 Million downloads
  • Members in 136 countries and
  • 8,000 enterprises around the globe
  • $100,000,000 USD in customer savings

2006 ushers new possibilities for MyEclipse with equally aggressive feature and delivery plans. For users that have been with us for a while, you know what to expect. For new users, you are in for an exciting ride.

 
     
  Eclipse Community Awards  
 

The Eclipse Community Awards are to recognize and thank the individuals and technologies that make Eclipse such a vibrant robust community. There are two categories of awards: 1) awards for individuals, and 2) awards for technologies and products. The awards will be presented at EclipseCon. Nominations are now open for all of the following awards.

  • Individual Awards
    • Top contributor
    • Top committer
    • Top ambassador
  • Technology Awards
    • Best open source RCP application
    • Best commercial RCP application
    • Best open source Eclipse based developer tool
    • Best commercial Eclipse based developer tool
    • Best deployment of Eclipse technology in an enterprise

This is a great opportunity to provide exposure to your Eclipse project and be acknowledged for your contribution. More details are available at the Eclipse web site.

 
     
Featured Training Partners
Now Available as private classes throughout the US by MyEclipse’s Authorized Training Provider!

610. 206. 0101 (voice)
610 . 206. 0102 (fax)
partner@learnquest.com

With nearly twenty years experience providing training for the IT professional, now offers training on MyEclipse and Eclipse J2EE Development including Java, J2EE, Struts, JSF and more. LearnQuest delivers MyEclipse hands-on tool training as well as comprehensive Custom Enterprise Java Reskilling Bootcamps which incorporate the MyEclipse IDE.



Now Available Throughout Europe
By Dimensic Software
München - Germany
Phone +49 (0)89 6935 9470
Fax +49 (0)89 1488 280675
info@myeclipse-training.com

Offering extensive and customized training programs for Eclipse and MyEclipse




Java-learning.com is a training partner of MyEclipse specialized on
distance learning. Offering a wide range of quality training including
professional development with Websphere products, Hibernate, Struts
and EJB development for JBoss using MyEclispe. All trainings are held
remote using a powerful elearning environment. Available languages are
English, German and French.

Read more about the concept and prices at http://www.java-learning.com

Testimonials
I can just tell you that what you and many other have built in Eclipse and MyEclipse is THE most impressive thing I have seen in all my years in IT. The capabilites and functionality and support are literally unbelieveable.
I have been a user for two years now and I never cease to be amazed at MyEclipse! I really don't know what I'd do without it. Spectacular job and support
WHAT A FANTASTIC PRODUCT You have!!!!!. I keep running into features that I haven't explored yet (you keep adding them before I get totally accustomed to having all previous features! )

I'd just like to say myEclipse and eclipse in general is amazing. Having done j2ee projects in the passed and spent hours and hours debugging struts and jsp code, I realize now how simple this could have been!
I searched around and found something that has me dancing with glee, and strangely enough opening up my wallet...Instead of buying a one-off copy that works forever but will never get better, I subscribed to some software that continually improves and uses Eclipse as it's basis and that's cool to me
Using MyEclipse since last December I came to the conclusion that it is a wonderful completion of Eclipse. MyEclipse makes my daily work as a software developer so efficient and pleasant
Please rate us at the
Eclipse Plugin Central



EclipseCon 2006
Mark your calendar.
The third annual EclipseCon conference will be held March 20th through 23rd at the Santa Clara Convention Center, CA, USA. Registration is open now through February 14th, 2005. Register early and get a significant discount. Make sure to looks us up while attending the conference.
 
True Virtual Servers with full root access, perfect for development, testing, and as production environment for your Java applications. Take advantage of the current specials at http://www.rosehosting.com/virtserv-spec.html.
RoseHosting is a leading provider of unique and cost effective hosting solutions and a synonym for high quality and reliability. Some of the services offered are: Shared Hosting, Managed and Unmanaged Virtual > Servers (VPS/VDS) and Dedicated Servers, SSL Certificates, Domains, Merchant Accounts, and more...
pnTresMailer by Tresware.com

 나만의 스타일, 나만의 인터넷   하나포스 http://www.hanafos.com
   
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1

다음과 같이 원격으로 디버깅을 있습니다:

    • Start eclipse. 이클립스를 실행합니다.
    • Perspective->Open->Java.
    • "Create a new Java Project" 눌러서 새로운 프로젝트를 만드세요
    • 만약 프로그램소스가 있으면 프로젝트에 임포트하세요 그래야지 디버거가 디버깅중에 소스를 보여 줍니다.
    • 다른 컴퓨터에 있는 자바 프로그램을 실행하세요. 명령어 줄에 프로그램 디버그가 가능하도록 다음을 추가하세요:

-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=n,suspend=n,address=8000 -Djava.compiler=NONE

:여러분이 사용할 포트 주소로 바꿀 있습니다.

    • 여러분이 만든 자바 프로젝트를 선택하세요.
    • Run->Debug...
    • 원격 자바프로그램을 선택하고 "New" 누르세요.
    • 원격 자바프로그램의 컴퓨터이름과 포트주소를 넣고 "Finish" 누르세요.
    • 디버거가 원격 프로그램으로 접속됩니다.

: 설정은 자바 프로그램이 디버거가 연결될 때까지 실행을 기다리는 설정입니다. 시작 중에 디버그가 필요 없으면 이렇게 명령어 줄을 수정하세요."server=n" to "server=y" 웹서버나 비슷한 프로그램 디버그에 매우 유용합니다.

: 방법은 여러분의 컴퓨터에서 디버그 때도 사용 있습니다.

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1

TDD를 이용한 게시판 프로그램

 

실제 게시판 프로그램을 만들어 보면서 TDD를 진행해 보자. 게시판 프로그램을 진행하기 위해 다음과 같은 테이블을 구성한다. 여기서는 간단하게 하기 위해 답변형 게시판보다는 일반형 게시판을 만들어 보자.

 

게시판 테이블 생성 스크립트(mySQL)

CREATE TABLE TEST_BOARD (

    BOARD_ID      INT          NOT NULL,          /* 게시글 ID(일련번호)          */

    TITLE         VARCHAR(255) ,                  /* 제목                         */

    CONTENTS      TEXT         ,                  /* 내용                         */

    WRITE_NAME    VARCHAR( 20) ,                  /* 사용자성명                   */

    PRIMARY KEY(BOARD_ID)

)

 

TDD는 테스트 프로그램을 먼저 만들기 때문에 게시판 프로그램을 작성하기 전에 어떻게 테스트를 실행할 것인지에 대해서 생각해보자.(XP-eXtreamming Program에서는 이러한 테스트 시나리오의 수립을 고객이 하도록 하고 있다. 고객이 자신이 구축될 시스템에 대해 고객이 실제로 필요로 하는 기능이 구현되었는지에 대한 검증을 하는 것이기 때문에 개발자들이 대신해 줄 수는 없는 것이기 때문이다. 개발자는 이 테스트 사항을 프로그램으로 자동화하고 이 테스트를 통과하도록 프로그램을 개발하면 된다.

게시판의 전체 테스트 시나리오 구성은 다음과 같을 것이다.

 

사용자 Action

기능

테스트 사항

게시판 메뉴 클릭

게시판 이동

특정 게시판 메뉴 클릭 시 해당 게시판의 목록 조회화면으로 이동 여부

 

게시판 목록 조회

해당 게시판의 목록이 화면에 나타나는지 여부

(전체 게시글의 건수, 페이지수, 1페이지에 10개의 게시글 조회-번호, 제목, 작성자)

Write 버튼 클릭

게시글 등록 화면

게시글 등록화면으로 이동 여부

게시글 작성 후 저장 버튼 클릭

게시글 등록

사용자가 입력한 게시글이 등록되고 게시판 목록 조회화면으로 이동하고 등록된 글이 게시판에 나타난다.

목록에서 특정 게시물을 선택한 후 클릭한다.

게시글 상세조회

선택된 게시글의 세부사항을 조회하여 상세조회 화면에 나타내는지 여부

상세조회한 화면에서 삭제버튼 클릭

게시글 삭제

선택된 게시글이 삭제되고 목록조회 화면으로 이동한다.

 

위의 시나리오에서 각 항목은 별도의 프로그램의 단위 테스트에 해당하고 각 항목을 통합하여 하나의 테스트를 수행하는 경우 통합테스트가 된다. 여기서 제시하는 예제는 각각의 기능을 구현하기 위해 각 단위 테스트에 해당하는 테스트 프로그램을 만들고 이들을 통합하여 하나의 테스트를 수행하는 테스트 프로그램을 만들었다.

이중에서 화면의 이동과 관련된 부분의 View Layer에 해당하는 부분이기 때문에 이 부분은 뒤에서 cactus를 이용하여 테스트 하기로 하고 먼저 시스템의 Business Layer 부분을 테스트 해보기로 하자. 앞에서 설명했듯이 테스트 프로그램은 각각의 Layer 별로 테스트 프로그램을 작성한다고 했다.

 

☞ 게시판 프로그램 상세조회 개발 및 테스트

 

개발 순서는 상세조회, 삭제, 등록, 기타 검색 기능 순서로 개발하였다. 이유는 다른 수정, 조회와 같은 기능을 테스트하기 위해서는 테스트용 데이터가 등록되어야 하고 테스트가 완료된 후에는 테스트용으로 입력된 데이터는 삭제되어야 하기 때문이다. 또한 등록 기능을 테스트하기 위해서는 DB에 정상적으로 등록되었는지 확인하기 위해서 등록된 데이터를 조회하여 등록시킨 값과 조회된 값이 일치하는지 검증을 해보아야 하기 때문이다.

(켄트벡은 각각의 Test들간에 dependency가 없어야 한다고 말하고 있다. 이 말의 의미는 필자의 경우처럼 각각의 테스트 프로그램들간의 의존성 없이 독립적으로 테스트 수행이 가능해야 한다는 것을 의미한다. 여기에서 제시하는 예제의 경우 상세조회 테스트가 실패하는 경우 입력 기능에 대한 테스트는 할 수 없게 된다.

필자가 이렇게 구성한 이유는 대부분의 프로젝트에서는 DB에 정보를 등록하고 조회하는 기능이 많은데 등록/수정/삭제 등의 기능을 테스트하기 위해서는 어쩔 수 없이 DB의 데이터를 조회하는 기능과 의존성을 가질 수 밖에 없었기 때문이다.

DB의 실제 정보를 조회하는 공통 기능을 만든 다음 이 기능을 이용하여 SQL문을 직접 이용하여 데이터를 조회한 다음 검증하는 방법도 가능할 것이다. 다른 방법에 대해 알거나 다른 방안이 있으면 필자에게도 알려주기 바란다.)

물론 상세 조회 기능을 테스트하기 위해서도 테스트 데이터가 필요한데 이때에는 개발자가 직접 DB에 값을 입력하고 테스트해보는 수밖에 없다(아니면 Mock Object를 이용할 수 도 있다).

 

 

먼저 앞의 eclipse 설치 시 수행한 Tomcat 프로젝트를 생성한다. 예제의 경우 프로젝트 명은 tdd로 하였다. 패키지 구성은 테스트 클래스가 위치하는 패키지는 test 라는 별도의 패키지를 만들고 이 패키지 아래로 각각의 서브 시스템에 해당하는 패키지를 만들어 해당 서브 시스템의 테스트 클래스를 위치시킨다.

 

 

위의 화면에서는 게시판(board), 사용자 관리(user) 두개의 서브 시스템이 존재하고 각각의 기능에 대한 테스트용으로 tdd.test.board, tdd.test.user 패키지를 만들었다.

 

앞에서는 JUnit에서 제공하는 도구를 사용하였는데 매번 클래스를 수정하고 다시 테스트 도구로 이동하여 테스트를 수행하는 것 역시 번거로운 작업이다. 앞장에서 열심히 eclipse에 대해 설명한 이유가 이러한 테스트 사항도 eclipse 상에서 수행하기 위해서다. 이제 eclipse를 이용하여 테스트 프로그램을 수행시켜 보자.

Eclipse 상에서 프로젝트를 생성한 다음 클래스 패스 설정화면 앞에서 다운로드 받은 junit.jar 파일을 추가해준다.

 

 

추가로 SQL관련 처리를 위해 사용하고 있는 DBMS의 JDBC드라이버는 WEB-INF/lib 폴더로 import 하고 build path에 추가한다. (import 방법은 2장에서 설명).

 

테스트 클래스의 명명규칙은 Test라고 postfix를 사용한다. 게시판 상세조회 기능에 대한 테스트 클래스는 BoardFindByKeyTest가 된다.

클래스를 생성하기 위해 New → Class를 선택하여 Super Class 에 TestCase를 추가해준다.

 

 

먼저 BoardFindByKeyTest 클래스에서 테스트를 수행할 메소드를 작성한다. 메소드 명은 testBoardFIndByKey()로 한다.

게시판의 모든 기능은 BoardService 클래스가 담당하도록 구성을 할 것이기 때문에 다음과 같이 테스트 프로그램이 작성될 것이다.

 

BoardFindByKeyTest.java

package tdd.test.board;

 

import junit.framework.Test;

import junit.framework.TestCase;

import junit.framework.TestSuite;

import tdd.board.Board;

import tdd.board.BoardService;

import tdd.common.DomainKey;

 

public class BoardFindByKeyTest extends TestCase

{

public void testBoardFindByKey() throws Exception

{ 

       DomainKey key = new DomainKey(new Integer(1234));   

       BoardService service = new BoardService();          

       Board board = service.findBoardByKey(key);                        

       assertNotNull(board);           

       assertEquals(board.getKey(), key);           

       assertEquals(board.getTitle(), "TEST_TITLE");       

       assertEquals(board.getContents(), "TEST_CONTENTS");        

       assertEquals(board.getWriteName(), "TEST_USER_NAME");      

}               

 

    public BoardFindByKeyTest(String name)

    {

super(name);

    }

public static Test suite()

{

       return new TestSuite(BoardFindByKeyTest.class);

}

public static void main(String[] args)

{

     junit.textui.TestRunner.run(suite());

}        

}

 

여기까지 작성하면 컴파일 에러가 발생하게 된다. 지금까지 작성된 클래스는 아무것도 없기 때문에 Board, BoardService와 같은 클래스를 만들어야만 최소한 컴파일이라도 가능하다.

이제 Board, BoardService 클래스를 만들어 보자.

 

Board.java

public class Board extends DomainObject

{

String title;

       String contents;

       String writeName;

 

       getter…

        setter…

}

 

BoardService.java

public class BoardService

{

       public Board findBoardByKey(DomainKey key)

       {

             return null;

       }

}

 

(DomainKey, Board 클래스는 첨부 참고)

 

Board, BoardService 클래스를 만들면 이제 BoardFindByKey 클래스를 컴파일하면 에러는 발생하지 않는다. 컴파일은 통과하였기 때문에 이제 테스트 프로그램을 실행시켜 본다.

 

JUnit 결과는 당연히 에러일 것이다. 에러가 발생한 부분은 검색한 Board 객체의 NULL 여부를 확인하는 다음 부분에서 테스트를 통과하지 못했다.

 

           assertNotNull(board);

 

 

BoardService에서 findBoardByKey() 메소드에서 null 값을 반환했기 때문에 당연히 NotNull 체크에서 테스트를 통과하지 못했을 것이다.

이제 컴파일은 통과하지만 실제 구현해야 할 기능이 테스트를 통과하지 못했기 때문에 테스트를 통과하도록 기능을 구현해보자.

BoardService의 findBoardByKey() 메소드는 파라미터로 받은 Key 값에 해당하는 게시판 정보를 DB에서 load하여 Board 객체를 생성한 다음 반환하는 기능을 수행할 것이다. 따라서 소스는 다음과 같이 될 것이다(전체소스는 별첨 참고).

 

BoardService.java

public class BoardService

{

       public Board findBoardByKey(DomainKey key) throws Exception

       {

             return (Board)Board.findByKey(key);

       }

}

 

위의 BoardService 역시 컴파일 시 에러가 발생할 것이다. Board 클래스에 findByKey() 라는 메소드가 없기 때문이다. Board에 다음과 같이 findByKey() 메소드를 추가하고 다시 컴파일 해 본다.

 

Board.java

public class Board

{

//멤버변수

//setter, getter

 

public static Board findByKey(DomainKey key) throws Exception

{

       String selectSql = "SELECT BOARD_ID, TITLE, CONTENTS, WRITE_NAME \n" +

                       "  FROM TEST_BOARD \n" +

                       " WHERE BOARD_ID = ?";        

            

       Connection conn = null;

       PreparedStatement pstmt = null;

       ResultSet rs = null;

            

       Board result = null;

       try

       {

             conn = DBUtil.getConnection();

             pstmt = conn.prepareStatement(selectSql);

            

             for(int i = 0 ; i < key.getFieldSize(); i++)

             {

                    pstmt.setObject(i + 1, key.getField(i));

             }

                   

             rs = pstmt.executeQuery();

            

             if(!rs.next()) throw new ObjectNotFoundException

("게시글에 대한 정보가 존재하지 않습니다.");      

                   

             result = load(key, rs);

       }

       catch(Exception e)

       {

             throw e;

       }

       finally

       {

             DBUtil.closeConnection(conn, pstmt, rs);

       }

      

       return result;

} 

      

protected static Board load(DomainKey key, ResultSet rs) throws Exception

{

       Board board = new Board(key);

       board.setTitle(rs.getString("TITLE"));

       board.setContents(rs.getString("CONTENTS"));

       board.setWriteName(rs.getString("WRITE_NAME"));

       return board;

} 

}

 

(기능 구현 부분에 대한 구성은 되도록 간단하게 구성하였다. DB에 대한 처리가 Board 객체에 같이 포함되어 있는 것은 잘못된 구조라고 지적하는 독자도 있겠지만 여기서는 이러한 실제 구현에 대한 사항을 설명하는 글이 아니고 테스트를 어떻게 구성할 것인가에 대한 글이기 때문에 구체적인 구현에 대한 아키텍처적인 사항은 별도의 문서를 참조하기 바란다.)

 

이제 하나의 게시글에 대한 정보를 가져오는 기능이 완전히 구현되었다고 생각되기 때문에 다시 이전에 작성한 테스트 프로그램을 수행시켜 본다. 정상적으로 작동한다면 JUnit 화면에서 Green Bar를 볼 수 있을 것이고, 정상적으로 동작하지 않는다면 Red bar가 나타날 것이다.

 

 

필자의 경우 Red Bar가 나타났는데 test_board라는 테이블을 생성하지 않았기 때문에 발생한 에러임을 알 수 있다.

이렇게 Exception관련된 사항도 JUnit에서 결과로 보여주고 있지만 코드상에서 특별한 Excetpion이 발생했을 때에 대한 처리를 할 경우 테스트 클래스에 별도로 Exception에 대한 처리 로직을 삽입하면 된다. 이때 주의해야 할 사항은 Exception이 발생했을 때 테스트를 위한 코드는 실행하지 않고 바로 catch 절로 이동하게 되기 때문에 테스트는 통과한 것으로 나타나게 된다. 이럴 경우에는 catch 절에 강제적으로 테스트가 실패했다는 것을 나타내기 위해 다음과 같이 fail() 메소드를 강제로 호출하여 테스트가 실패하도록 처리하면 된다.

 

try

{

    //테스트 코드

}

catch(Exception e)

{

e.printStackTrace();

fail();

}

 

JUnit에서 Green Bar가 나타날 때 까지 소스의 잘못된 부분에 대한 수정을 계속 진행한다.

이러한 과정을 반복적으로 거치는 동안 JUnit에서 Green Bar가 나타나게 되면 게시판 상세조회에 대한 비즈니스 로직은 완성이 된다. 물론 “Refactor to remove duplication 단계도 거쳐야 하겠지만

이제 테스트 프로그램이 있기 때문에 내부적으로 Board 클래스 등을 변경하거나, 테이블의 schema를 변경할 경우에 이 테스트 프로그램을 통해 수정된 사항이 정상적인 기능을 수행하는지 대해서는 자동으로 테스트를 할 수 있다.

 

☞ 게시판 삭제 테스트

 

상세 조회 다음으로 작성하는 테스트는 삭제 테스트이다. 삭제의 경우 삭제할 객체를 생성하여 삭제 기능을 호출하고, 삭제 기능이 정상적으로 동작하는지에 대한 검증은 삭제 처리 건수(return 값)가 1이거나 앞에서 테스트가 완료된(이미 검증된 기능) findByKey() 기능을 이용하여 삭제한 게시글의 키 값으로 findByKey() 기능을 호출하여 조회된 결과가 NULL이거나 ObjectNotFoundException을 발생시키면 정상적으로 테스트를 통과한 것으로 본다.

 

BoardDeleteTest.java

public class BoardDeleteTest extends TestCase

{

public void testBoardDelete()

{

       DomainKey key = new DomainKey(new Integer(1234));

BoardService service = new BoardService();

        Board board = new Board(key);

 

   int delCount = service.deleteBoard(board);

         

   assertEquals(1, delCount);             

       try

       {

             Board deletedBoard = service.findBoardByKey(key);

             assertNull(deletedBoard);

       }                  

       catch(ObjectNotFoundException oe)

       {

             assertTrue(true);

       }

}

      

public BoardDeleteTest(String name)

    {

super(name);

    }

public static Test suite()

{

       return new TestSuite(BoardDeleteTest.class); 

}

      

public static void main(String[] args)

{

       junit.textui.TestRunner.run(suite());

} 

}

 

수정/삭제의 경우 잘못 개발될 경우 전체 데이터에 대한 수정/삭제가 발생하기 때문에 가능한 프로그램에서 수정/삭제 결과에 대한 건수를 검증하고 의도대로 수정/삭제된 경우만 commit()을 처리하도록 구성하고 테스트에서도 이 부분에 대한 검증을 하는 것이 좋다.

 

BoardService.java

public class BoardService

{

public int deleteBoard(Board board) throws Exception

{

   if(board == null) throw new Exception("삭제할 게시정보가 없습니다.");

         

   return board.delete();

}

}

 

Board.java

public class Board

{

//멤버변수

//setter, getter

public int delete() throws Exception

{        

   String deleteSql = "DELETE FROM TEST_BOARD WHERE BOARD_ID = ?";      

         

   Connection conn = null;

   PreparedStatement pstmt = null;

   try

   {

          conn = DBUtil.getConnection();

          pstmt = conn.prepareStatement(deleteSql);

                

          pstmt.setObject(1, key.getField(0));

         

          return pstmt.executeUpdate();

   }

   catch(Exception e)

   {

          throw e;

   }

   finally

   {

          DBUtil.closeConnection(conn, pstmt);

   }

}

}

 

여기서 다시 한번 고려해 볼 사항은 앞의 findByKey() 기능에 대한 테스트 프로그램에서 검색조건에 맞는 게시글이 없을 경우에 대한 처리를 테스트 했느냐 하는 것이다. 앞의 예제에서는 이러한 사항을 무시하고 진행을 하였다. 따라서 지금 작성하는 삭제 기능에 대한 테스트의 검증은 findByKey() 기능에서 데이터가 없을 경우에 대한 테스트를 다시 해야지만 테스트 진행이 가능하게 된다. 따라서 앞의 findByKey()를 테스트하는 BoardFindByKey 클래스에 다음 메소드를 추가하여 정상적으로 작동하는지 확인하다.

 

BoardFindByKeyTest.java

public class BoardFindByKeyTest

{

public void testBoardNotFound()

{

DomainKey key = new DomainKey(new Integer(5678));

            

   BoardService service = new BoardService();

            

       try

       {

         Board board = service.findBoardByKey(key);

            

         assertNull(board);

       }

       catch(ObjectNotFoundException oe)

       {

         assertTrue(true);

       }

}

 

//기존 코드

}

 

 

이렇게 테스트 프로그램에서 모든 테스트 Case에 대해 테스트 코드가 작성되지 않은 경우 해당 테스트 프로그램을 통해 검증된 프로그램이 운영환경으로 release되었을 때에는 반드시 문제가 발생하게 되어 있다. 따라서 개발자는 얼마나 견고한 테스트 Case를 도출하여 테스트 프로그램을 만드느냐에 따라 시스템의 신뢰성이 좌우된다 할 수 있다.

위의 testBoardNotFound() 테스트 메소드에 대한 또 하나의 이슈는 이 테스트 코드를 BoardFindByKeyTest 클래스에 두어야 하는지 BoardDeleteTest 클래스에 두어야 하는지에 대한 사항이다. 앞에서 각각의 테스트 사이에는 dependency가 존재하지 않아야 한다고 하였다. 그러면 BoardFindByKeyTest 클래스에 이 메소드를 위치하게 되면 BoardDeleteTest는 BoardFindByKeyTest 클래스에 대한 검증이 모두 통과되었다는 가정하에서만 테스트가 가능할 수 있다. 그러면 BoardDeleteTest에 위치를 시켜야 하나? 이러한 부분에 대해서는 앞에서도 밝혔듯이 필자도 아직 해당을 찾지 못했다. 필자의 경우 BoardFindByKeyTest 클래스에 위치시켰다.

 

이제 삭제 기능에 대한 테스트를 실행하여 Green Bar 나타나는지 확인한다.

 

☞ 게시판 등록 테스트

 

등록의 경우 등록할 데이터에 해당하는 키 값이 이미 DB에 존재하게 되면 테스트 수행 시 반드시 키 중복 SQLException이 발생하게 된다, 물론 이러한 키 값이 중복되었을 때 어떻게 처리 할 것인지에 대한 테스트라면 이렇게 수행하는 것이 맞겠지만 일반적인 등록 테스트의 경우는 테스트로 생성한 데이터가 이미 존재하면 테스트를 수행하기 전에 삭제 처리를 하고 등록 테스트를 수행한다.

또한 테스트가 완료 되었을 때에는 테스트용으로 입력된 데이터는 삭제해준다.

 

등록 기능의 테스트 순서는 다음과 같다.

 

1. setUp()에서 테스트할 데이터를 삭제

(삭제하지 않을 경우 입력시 Key Duplication Error 발생)

2. testXXXInsert 메소드에서 입력 기능을 수행

        3. 검증은 앞에서 테스트한 상세조회 기능을 이용해서 검증

             4. tearDown()에서 테스트 데이터 삭제

 

BoardInsertTest.java

public class BoardInsertTest extends TestCase

{

Board newBoard;

      

public void setUp() throws Exception

{

       DomainKey key = new DomainKey(new Integer(1234));

       //등록할 객체를 생성

       newBoard = new Board(key);

       newBoard.setTitle("TEST_TITLE");

       newBoard.setContents("TEST_CONTENTS");

       newBoard.setWriteName("TEST_USER_NAME");

                   

       //이미 존재하고 있으면 삭제

       BoardService service = new BoardService();

       service.deleteBoard(newBoard);

}

      

public void testBoardInsert() throws Exception

{        

       BoardService boardService = new BoardService();

            

       boardService.insertBoard(newBoard);          

            

       Board board = boardService.findBoardByKey(newBoard.getKey());

                   

       assertNotNull(board);

       assertEquals(board, newBoard);

       assertEquals(board.getTitle(), "TEST_TITLE");

       assertEquals(board.getContents(), "TEST_CONTENTS");

       assertEquals(board.getWriteName(), "TEST_USER_NAME");             

}

      

public void tearDown() throws Exception

{

       BoardService boardService = new BoardService();

            

       boardService.deleteBoard(newBoard);    

}

 

public BoardInsertTest(String name)

    {

super(name);

    }

public static Test suite()

{

       return new TestSuite(BoardInsertTest.class); 

}

      

public static void main(String[] args)

{

       junit.textui.TestRunner.run(suite());

}

}

 

BoardService.java

public class BoardService

{

       public void insertBoard(Board board) throws Exception

       {

             if(board.getKey() == null)

             {

                    board.setKey(Board.makeKey());

             }

             board.insert();

       }     

}

 

Board.java

public class Board

{

//멤버변수

//setter, getter

public void insert() throws Exception

{

       String insertSql = "INSERT INTO TEST_BOARD \n" +

                       " (BOARD_ID, TITLE, CONTENTS, WRITE_NAME ) \n" +

                       " VALUES (?, ?, ?, ?)" ;

            

       Connection conn = null;

       PreparedStatement pstmt = null;

       try

       {

             conn = DBUtil.getConnection();

             pstmt = conn.prepareStatement(insertSql);

 

             int colIndex = 1;

                                       

             pstmt.setObject(colIndex++, key.getField(0));

             pstmt.setObject(colIndex++, getTitle());

             pstmt.setObject(colIndex++, getContents());

              pstmt.setObject(colIndex++, getWriteName());

                   

             pstmt.executeUpdate();

       }

       catch(Exception e)

       {

             throw e;

       }

       finally

       {

             DBUtil.closeConnection(conn, pstmt);

       }

}

 

public static DomainKey makeKey() throws Exception

{

       String selectSql =  "SELECT MAX(BOARD_ID) FROM T_BOARD";   

            

       Connection conn = null;

       PreparedStatement pstmt = null;

       ResultSet rs = null;

            

       Board result = null;

       try

     {

             conn = DBUtil.getConnection();

             pstmt = conn.prepareStatement(selectSql);

                   

             rs = pstmt.executeQuery();

      

             if(!rs.next())      return new DomainKey(new Integer(1));

          else   return new DomainKey(new Integer(rs.getInt(1)));             }

       catch(Exception e)

       {

             throw e;

       }

       finally

       {

             DBUtil.closeConnection(conn, pstmt, rs);

       }

} 

}

 

여기까지 만든 다음 테스트 프로그램을 수행하여 테스트를 통과하는지 검증한다. 위의 경우 테스트는 다음 화면과 같이 실패하게 된다.

 

테스트 실패의 이유는 자바의 equals() 메소드의 특징 때문에 발생하게 된다. 위의 화면에서 보는 것처럼 테스트가 실패한 곳은 테스트 코드의

 

assertEquals(board, newBoard);

 

부분이다. 여기서 assertEquals() 라는 assert 메소드를 사용하였는데 여기서 두개의 객체를 비교하는 방법은 내부적으로 해당 객체의 equals() 메소드를 호출하여 처리한다. 따라서 이 assertEquals() 메소드를 이용할 경우 비교의 대상이 되는 객체에 equals() 메소드를 정의해야만 정상적으로 이 assertEquals() 메소드가 동작하게 된다. 따라서 위의 예제에서는 Board 클래스에 equals() 메소드를 다음과 같이 구현해 주어야 한다.

 

Board.java

public class Board

{

public boolean equals(Object obj)

{

       if(!(obj instanceof Board))  return false;

       return getKey().equals(((Board)obj).getKey());

}

}

 

☞ 게시판 목록 테스트

 

목록조회 기능에 대한 테스트의 경우 다음과 같은 순서로 진행할 수 있다.

 

1. setUp()에서 반복문을 수행하여 테스트 데이터 등록

  페이지 이동 테스트가 가능할 정도의 데이터를 입력

2. test() 메소드에서 첫페이지, 중간 페이지 조회에 대해 건수, 페이지수, 조회된 데이터에 대한 검증

3. tearDown() 에서 반복문을 수행하여 테스트 데이터 삭제

 

BoardFindByKeywordTest.java

package tdd.test.board;

 

import java.util.List;

 

import junit.framework.Test;

import junit.framework.TestCase;

import junit.framework.TestSuite;

import tdd.board.Board;

import tdd.board.BoardService;

import tdd.common.DomainKey;

import tdd.common.PageList;

 

public class BoardFindByKeywordTest extends TestCase

{

public void setUp() throws Exception

{

       BoardService boardService = new BoardService();

 

       Board newBoard;

       for(int i = 0 ; i < 25 ; i++)

       {

             DomainKey key = new DomainKey(new Integer(1234 + i));

            

             newBoard = new Board(key);

             newBoard.setTitle("TEST_TITLE" + i);

             newBoard.setContents("TEST_CONTENTS" + i);

             newBoard.setWriteName("TEST_USER_NAME" + i);

                   

             boardService.insertBoard(newBoard);          

       }

}

public void testBoardList() throws Exception

{

       //사용자 목록 조회는 페이지 단위로 이동하게 구현

       BoardService service = new BoardService();

              

       PageList pageList = service.findBoardByKeyword("TEST_TITLE", "TITLE", 2);

 

       assertNotNull(pageList);

       assertEquals(pageList.getPage(), 2);

       assertEquals(pageList.getTotalSize(), 25);

       assertEquals(pageList.getTotalPage(), 3);

            

       List dataList = pageList.getData();

       assertEquals(dataList.size(), 10);

 

       pageList = service.findBoardByKeyword("TEST_TITLE", "TITLE", 3);

       dataList = pageList.getData();

       assertEquals(dataList.size(), 5);

}

      

public void tearDown() throws Exception

{

       BoardService boardService = new BoardService();

       for(int i = 0 ; i < 25 ; i++)

       {

             DomainKey key = new DomainKey(new Integer(1234 + i));

      

             boardService.deleteBoard(new Board(key));           

       }

}

 

public BoardFindByKeywordTest(String name)

    {

super(name);

    }

public static Test suite()

{

       return new TestSuite(BoardFindByKeywordTest.class); 

}

      

public static void main(String[] args)

{

     junit.textui.TestRunner.run(suite());

}

}

 

BoardService.java

public class BoardService

{

public PageList findBoardByKeyword(String keyword, String findType, int page)

throws Exception

{

   List data = Board.findByKeyword(keyword, findType, page);

   int totalCount = Board.countByKeyword(keyword, findType);

 

   PageList pageList = new PageList(page, totalCount, data);

         

   return pageList;

} 

}

 

Board.java

public class Board

{

//멤버변수

//setter, getter

public static List findByKeyword(String keyword, String findType, int page)

throws Exception

{ 

       String selectSql = "SELECT BOARD_ID, TITLE, CONTENTS, WRITE_NAME \n" +

                       "  FROM TEST_BOARD \n" ;

 

       int paramCount = 0;

       if("WRITER".equals(findType))

       {

             selectSql += "WHERE WRITE_NAME LIKE ? \n";

             paramCount++;

       }

       else if("TITLE".equals(findType))

       {

             selectSql += "WHERE TITLE LIKE ? \n";

             paramCount++;

       }     

                          

       selectSql += "ORDER BY BOARD_ID DESC \n";

       selectSql += "LIMIT ? , ?";

 

       Connection conn = null;

       PreparedStatement pstmt = null;

       ResultSet rs = null;

            

       try

       {

       conn = DBUtil.getConnection();

         pstmt = conn.prepareStatement(selectSql);

                   

         int colIndex = 1;

                   

         if(paramCount >= 1)

         {

             pstmt.setString(colIndex++, "%" + keyword + "%");

         }

            

         pstmt.setInt(colIndex++, PageList.getStartRowNum(page));

         pstmt.setInt(colIndex++, PageList.getCountPerPage());

            

         rs = pstmt.executeQuery();

                   

         List result = new ArrayList();

            

         while(rs.next())

         {

             result.add(load(new DomainKey(rs.getString("BOARD_ID")), rs));

         }

         return result;

       }

       catch(Exception e)

       {

             throw e;

       }

       finally

       {

             DBUtil.closeConnection(conn, pstmt, rs);

       }

}

 

public static int countByKeyword(String keyword, String findType)

throws Exception

{ 

       String selectSql = "SELECT COUNT(*) \n" +

                       "  FROM TEST_BOARD \n" ;

 

       int paramCount = 0;

       if("WRITER".equals(findType))

       {

             selectSql += "WHERE WRITE_NAME LIKE ? \n";

             paramCount++;

       }

       else if("TITLE".equals(findType))

       {

             selectSql += "WHERE TITLE LIKE ? \n";

             paramCount++;

       }     

      

       Connection conn = null;

       PreparedStatement pstmt = null;

       ResultSet rs = null;

      

       try

       {

             conn = DBUtil.getConnection();

             pstmt = conn.prepareStatement(selectSql);

            

             if(paramCount >= 1)

             {

                    pstmt.setString(1, "%" + keyword + "%");

             }

            

             rs = pstmt.executeQuery();

             rs.next();

             return rs.getInt(1);

       }

       catch(Exception e)

       {

             throw e;

       }

       finally

       {

             DBUtil.closeConnection(conn, pstmt, rs);

       }

}

}

 

대부분의 웹 시스템에서 목록 조회의 기능은 페이지 이동을 포함하고 있으며, 이 기능은 개발자들이 테스트하기 귀찮고 번거로운 항목 중에 하나이다. 따라서 위와 같이 테스트 프로그램에서 동일한 검색 조건으로 다른 페이지를 조회하여 실제 페이지에 나타나는 데이터의 건수, 전체 건수, 전체 페이지 수 등을 검증하는 방법으로 테스트를 자동화 할 수 있다.

위의 테스트 프로그램에는 빠져 있지만 조회된 목록의 실제 데이터까지 확인하도록 하는 것이 더 정확한 테스트 프로그램이 될 것이다.

 

지금까지 게시판의 입력/삭제/목록조회/상세조회 기능에 대한 테스트 코드를 작성해 보았다. Enterprise 시스템 대부분의 기능이 위와 같은 입력/수정/삭제/목록조회/상세조회 형태로 구성되어 있기 때문에 이들을 응용하면 전체 시스템에 대해 적용도 가능할 것이라 생각된다.

위에서의 테스트는 각각의 단위 기능에 대한 테스트이다. 하지만 게시판 등록 테스트 프로그램을 보면 게시판의 등록 → 등록된 데이터의 검증 → 데이터의 삭제 등과 같은 통합 테스트적인 요소까지 포함하고 있는 것을 볼 수 있다.

이러한 단순히 CRUD성의 테스트가 아닌 시스템의 기능적인 통합 테스트가 필요한 경우 별도의 TestCase로 작성하여 구성할 수도 있을 것이다. 위의 예제의 경우 BoardFindByKeywordTest 클래스의 구현 내용을 자세히 보면 이러한 통합 테스트와 관련된 사항이 모두 포함되어 있음을 알 수 있다. 물론 화면과 연결되어 있는 부분(사용자의 메뉴 선택, 저장 버튼 클릭 등)에 대한 테스트는 처리가 되어 있지 않지만 게시판의 기능에 대한 전체적인 통합 테스트는 표현이 되어 있다.

 

☞ 전체를 한번에 수행시키기

 

이번에는 별도로 작성해서 수행한 테스트를 한번에 수행할 수 있도록 구성해 보자. 한번에 수행 가능하도록 구성하는 이유는 테스트를 통과하여 운영환경으로 이관되어 있는 프로그램이 운영 중에 사용자의 요청 또는 버그로 인해 수정사항이 발생할 경우 특정 부분을 수정한 다음 다시 테스트를 수행해야 하는데 이때 수정된 부분만 테스트를 수행하는 것이 아니라 수정된 부분으로 인해 다른 부분에서 오류가 발생하지 않는지(Side Effect)를 검증하기 위해 전체 테스트 프로그램을 수행시켜 보아야 하기 때문이다.

side effect는 개발자가 운영중인 시스템의 소스를 수정하기 꺼려 하는 가장 큰 이유중의 하나이다. 하지만 TDD를 이용할 경우 자동화된 테스트 프로그램이 이미 만들어져 있기 때문에 프로그램의 한 부분이 수정되었다고 해도 전체 테스트를 수행해보고 모두 테스트를 통과한 경우에만 release 하면 된다.

이러한 이유 때문에 각각의 테스트를 통합하는 작업이 필요한데 eclipse의 기능을 이용하는 방법과 JUnit의 TestCase 클래스를 이용하는 방법이 있다.

먼저 eclipse를 이용한 경우 메뉴의 Run → Run 을 선택한 다음 JUnit을 선택하고 Keep JUnit running after a test run when debugging을 선택한 후 실행을 시키면 된다.

 

 

eclipse를 이용하는 경우 전체 테스트 프로그램이 모두 수행되기 때문에 필요에 따라 하나의 서브 시스템 또는 하의 레벨의 시스템 단위로만 테스트할 경우가 필요하게 된다. 이때에 TestCase 클래스를 이용해 이미 작성된 테스트 클래스를 이용하여 또 다른 테스트 클래스를 작성해 주면 된다.

예제에서는 Board의 기능 전체를 테스트하는 프로그램의 BoardTest 클래스를 만들었다.

 

BoardTest.java

package tdd.test.board;

 

import junit.framework.Test;

import junit.framework.TestCase;

import junit.framework.TestSuite;

 

public class BoardTest extends TestCase

{

public BoardInsertTest(String name)

    {

super(name);

    }

 

       public static Test suite()

       {

             TestSuite suite = new TestSuite();

            

             suite.addTestSuite(BoardInsertTest.class);

             suite.addTestSuite(BoardDeleteTest.class);

             suite.addTestSuite(BoardFindByKeyTest.class);

             suite.addTestSuite(BoardFindByKeywordTest.class);

 

             return suite;

       }

      

       public static void main(String[] args)

       {

             junit.textui.TestRunner.run(suite());

       }     

}

 

 

테스트를 수행하기 전에 이전에 작성한 BoardFindByKey, BoardDeleteTest 클래스의 setUp(), tearDown() 부분에 테스트할 데이터를 등록하는 부분을 추가해주어야 한다. 이유는 최초 이들에 대한 테스트를 수행할 때 Insert 기능이 구현되지 않아 직접 DB에 테스트 데이터를 등록했던 것을 기억할 것이다. 이제 Insert 기능이 구현되고 테스트까지 통과했기 때문에 전부 자동으로 테스트할 수 있도록 하기 위해서다.

다음 화면은 BoardTest 클래스를 수행시킨 결과이다.

 

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1

Cactus를 이용한 Http 테스트

 

앞에서 JUnit을 이용하여 비즈니스 Layer에 대한 테스트를 진행하였다. 하지만 JUnit은 EJB, Servlet 등과 같이 Container 상에서 작동하는 클래스들에 대한 테스트는 어렵다. Servlet, JSP, Session Bean Entity Bean 등은 반드시 해당 Container가 있어야지만 수행이 가능하고 이러한 환경하에서만 테스트가 가능하다.

이렇게 서버 측에서 운영되는 프로그램에 대한 테스트를 지원하는 것이 apache의 cactus이다.

 

실제 개발자들이 테스트하기 어렵게 여기는 부분도 웹브라우저에서의 사용자의 Action에 따라 시스템의 각 기능이 정상적으로 동작하는지에 대한 테스트이다.

 

Http와 관련된 사항에 대한 테스트를 수행하는 원리는 테스트를 위한 framework(여기서는 cactus)에서 HttpRequest를 강제적으로 생성하여 Container로 해당 request를 전송 시켜 테스트를 수행한다. 이때 HttpRequest에 테스트할 request parameter를 설정할 수 있으며,  수행된 결과인 HttpResponse에 대한 검증이 가능하다.

더 나아가 JSP 페이지에서 생성된 HTML 코드에 대한 검증도 가능하기 때문에 전체 웹 시스템에 대해 비즈니스 로직뿐만 아니라 화면 부분까지 자동화된 테스트가 가능하게 된다.

 

cactus 다운로드 및 설치

 

cactus는 apache의 http://jakarta.apache.org/cactus/index.html에서 자세한 정보를 얻을 수 있다. cactus는 명령행으로 실행시키는 모드와 eclipse plugin 두가지 모드를 지원하고 있으나 현재 eclipse plugin은 정식으로 release 된 상태는 아니다.

cactus의 eclipse plugin 관련 문서는 다음 url에서 찾아 볼 수 있다.

 

http://jakarta.apache.org/cactus/integration/eclipse/index.html

 

eclipse plugin의 다운로드는 nightly build에서 다운로드 가능한데 현재 cactus는 계속 진행 중인 프로젝트 이기 때문에 계속해서 버전이 바뀌고 있어 최신 버전을 다운로드 받으면 된다. http://cvs.apache.org/builds/jakarta-cactus/nightly/ 페이지에서 최근의 파일을 다운로드 받아 설치하면 된다.

위의 페이지에는 ant를 지원하는 모드와 eclipse를 지원하는 모두 같이 있다. 여기서 모두 다운로드 받은 다음 jakarta-cactus-eclipse-runner-<build version>.zip 파일과 jakarta-cactus-eclipse-webapp-<build version>.zip 두 파일의 압축을 해제한 다음 eclipse의 pulgin 디렉토리에 복사하면 설치는 끝난다.

나머지 한 파일은 jakarta-cactus-13-1-<build version>.zip 파일은 cactus 테스트 프로그램 컴파일 시 필요한 파일들이 있기 때문에 일단 적당한 디렉토리에 압축을 푼 상태로 둔다.

cactus가 제대로 설치 되었는지 여부는 eclipse에서 Windows → Preferences 메뉴에서 아래 화면과 같이 Cactus에 대한 설정이 나타나면 제대로 설치가 된 것이다.

 

 

Cactus 설정에서 사용하고 있는 서버 Container의 Home 경로를 지정해 준다. 필자의 경우 Tomcat를 사용하였기 때문에 Tomcat 부분에 Tomcat의 Home 디렉토리를 설정해 주었다.

 

이제 프로젝트로 돌아와서 프로젝트의 CLASSPATH에 이 cactus를 추가하여야 한다. 이때 조금 주의해야 할 사항이 있는데 cactus에서 사용하고 있는 junit의 버전과 CLASSPATH상에 있는 junit의 버전이 틀릴 경우 컴파일 시 에러가 발생할 수 있다. 이유는 cactus의 여러 클래스들은 junit의 클래스를 상속 받아 사용하고 있는데 이들에 super클래스의 생성자 등에서 버전에 따라 틀릴 경우 제대로 컴파일이 되지 않는다. 앞의 예제에서는 JUnit의 junit.jar 파일을 CLASSPATH 상에 설정하여 컴파일 하였는데 cactus를 이용하게 되면 cactus 다운로드시 다운로드 받은 junit.jar 파일을 이용하여야 한다. 이 파일은 cactus의 eclipse plugin에 있는 것이 아니라 파일은 jakarta-cactus-13-1-<build version>.zip 압축을 해제한 디렉토리 아래에 lib 디렉토리에 존재한다.

 

 

추가로 common-logging-1.0.jar 파일도 추가로 jar 파일에 등록한다.

(위의 cactus 설치 사항은 2003.07월 기준으로 작성된 것이기 때문에 그 동안의 변경 사항에 대해서는 cactus 사이트에서 참고를 하고 설치 사항이 변경된 것이 있는지 확인학 설치하기 바란다.)

 

cactus 동작원리

 

JUnit은 setUp(), testXXX(), tearDown() 순으로 동작하여 초기 값 설정 등을 setUp() 메소드에서 처리하게 하였지만 Http관련 테스트를 수행하기 위해서는 Session의 설정, HttpRequest 에 파라미터 추가 등의 작업을 해주어야지만 원하는 테스트를 수행할 수 있다.

Cactus는 이러한 HTTP의 특성을 테스트에서 처리하기 위해 다음과 같은 순서로 동작하게 된다.

 

beginXXX() 메소드의 XXX는 테스트할 메소드의 명칭을 의미한다. 예를 들어 testBoardList()라는 테스트 메소드가 있다면 startBoardList() 가 될 것이다.

beginXXX(), endXXX() 메소드의 파라미터로 WebRequest, WebResponse가 넘겨져 오는데 이 파라미터를 이용하여 Request 정보를 설정한다.

다음은 cactus doc 문서를 번역한 것인데 요약해서 설명하면 Client Side에서 개발자가 작성한 테스트 클래스의 객체를 생성하여 beginXXX() 메소드를 수행한 다음 cactus에서 제공하는 proxy 서버에 Http 접속을 하여 beginXXX()에서 설정한 값들을 proxy로 전송한다. proxy에서는 테스트 클래스를 객체화하여 setUp, testXXX, tearDown 메소드를 수행하고 결과를 다시 Request, Response에 담아 Client Side로 보내는 방식으로 테스트가 수행된다.

1.       JUnit은 YYYTestCase의 runTest() 메소드를 호출한다.

runTest() 메소드는 beginXXX(WebRequest) 메소드가 있으면 이 메소드를 호출한다. 이 beginXXX(WebRequest) 메소드는 Server Container가 아인 독립적인 clinet side(별도의 JVM)에서 수행된다. beginXXX() 메소드에는 WebRequest아는 파라미터가 전돨되는데 이것은 HTTP Header, Parameter 등을 설정하여 step2에서 proxy로 전송하는데 사용되어 진다.

2.       YYYTestCase.runTest() 메소드는 Redirector proxy에 HTTP 접속을 한다. beginXXX() 메소드에서 설정된 모든 파라미터는 이 HTTP request에 담겨 전송된다.

3.       Redirector proxy는 테스트 클래스에 대해 server side 에서 proxy같이 작동한다. 이것은 테스트 클래스가 두 번 객체화 되는 것을 말하는데 한번은 Junit 의 Test Runner에 의해 client side에서, 한번은 이 Redirector proxy에 의해 server side에서 발생한다. client side의 instance는 beginXXX()와 endXXX()을 수행하는데 사용되고 server side의 instance는 testXXX() 메소드를 수행하는데 사용된다.

4.       테스트 클래스의 setUp(), testXXX(), tearDown() 순서로 수행된다. 이들 메소드는 reflection(자바 API중 메소드 명, 멤버변수 등과 같은 클래스 정보를 가져오는 API)을 이용하여 Redirector proxy에 의해 호출된다. 물론 setUp(), tearDown()는 optional 이다.

5.       testXXX() 메소드는 server side의 테스트를 수행하는 코드를 호출하게 되는데 여기에는 테스트를 수행하고 JUnit의 assert API를 이용하여 수행결과를 검증한다.

6.       테스트가 실패하면 testXXX() 메소드는 exception 을 발생시킨다.

7.       exception이 발생하면 Redirector proxy는 이 exception에 대한 정보를 client side로 보낸다. 이 exception에 대한 정보는 JUnit에 의해 console에 나타난다.

8.       예외가 발생하지 않은 경우 YYYTestCase.runTest() 메소드는 endXXX(org.apache.cactus.WebResponse) 또는 endXXX(com.meterware.httpunit.WebResponse)- HttpUnit을 이용하는 경우-를 찾아 있는 경우 이를 수행한다. 이 단계에서 cactus나 JUnit의 assert API를 이용하여 response의 HTTP header, cookie, output stream 의 값을 검증할 수 있다.

cactus 를 이용한 Servlet 테스트(Front Controller)

 

Cactus는 Servlet, JSP, Filter, TagLib. EJB에 대해 테스트 기능을 지원한다. 여기서는 Servlet에 대한 테스트에 대해서 알아보기로 하자. Cactus의 환경설정, 테스트 작성방법, 테스트 수행방법만 알면 나머지에 대해서는 동일한 방법에 대해 수행하면 된다.

요즘 J2EE를 이용하여 웹 시스템을 구축할 때 Servlet을 사용하는 경우는 거의 드물다. 일반적인 화면의 경우 대부분 JSP로 처리하기 때문이다. 하지만 예제의 경우 JSP Model2 구조로 되어 있기 때문에 Front Controller를 사용하여 모든 사용자의 요청은 한곳에 받고 각각의 사용자의 요청에 따른 처리는 Action 단위로 클래스를 구성하여 처리하도록 하였다.

이때 이 Front Controller 부분이 Servlet으로 구성되어 있다(Action이라는 용어 대신 Command라는 용어를 사용하였다. 구조에 대한 자세한 설명은 필자의 글 JSP Model2를 이용한 웹 시스템 구축 사례를 참조하기 바란다.).

이러한 구조에서 웹 Layer에서 가장 먼저 만드는 것이 Front Controller라 할 수 있다. 일반적으로 이 Front Controller는 전체 시스템에 하나 또는 몇 개만 존재하기 때문에 한번 작성되면 거의 수정사항이 발생하지 않는다.

필자의 경우 테스트를 다음과 같은 단위로 작성하였다. Front Controller의 기능을 테스트하기 위한 Tester를 구성한 다음 정상적으로 원하는 Command를 생성시키고, 처리 결과를 제대로 forward 시키는지 여부에 대해 테스트를 수행하였다. 그리고 각각의 Command에 대해 하나의 Tester 클래스를 만들었다. 이렇게 구성하다 보니 Tester 클래스가 너무 많이 생겼다. 하지만 개발 후 지속적으로 관리(유지보수)를 하기 위해서는 특정 단위로 나누어져 있어야지만 편리하기 때문에 이러한 방식을 택했다.

 

앞에서 JUnit을 이용하여 테스트 프로그램을 작성할 때 TestCase를 상속받아 테스트 프로그램을 작성하였다. Cactus에서 Servlet 테스트 프로그램을 만들기 위해서는 ServletTestCase 클래스를 상속받아 구현하면 된다.

비즈니스 레이어 측의 테스트를 수행하기 위해 패키지로 tdd.test.board 라는 패키지를 만들었는데 웹 계층을 테스트하기 위해 tdd.test.board.web 이라는 패키지를 만들어 여기에 웹 Layer관련 테스트 클래스들이 위치하도록 하였다.

 

 

Front Controller는 HttpRequest로부터 사용사의 처리 요청을 받아 특정 Action(Command) 객체를 생성시켜 이 객체를 수행 시키고 결과로 받은 forward 정보를 이용하여 결과 페이지로 forward 시키는 기능을 수행한다. 이러한 기능에 대한 테스트 코드를 먼저 작성한다.

 

TestCommandServletTest.java

package tdd.test.board.web;

 

import java.io.IOException;

 

import junit.framework.Test;

import junit.framework.TestSuite;

 

import org.apache.cactus.ServletTestCase;

import org.apache.cactus.WebRequest;

import org.apache.cactus.WebResponse;

 

import tdd.board.BoardServlet;

 

public class BoardServletTest extends ServletTestCase

{

public BoardServletTest(String name)

{

     super(name);

}

////////////////////////////////////////////////////////////////////////////

//Command 대한 처리 부분 테스트

public void beginGetCommand(WebRequest theRequest)

{

     theRequest.addParameter("command", "tdd.board.command.BoardDummyCommand");

}

public void testGetCommand()

{

       BoardServlet servlet = new BoardServlet();

       String command = servlet.getCommand(request);

            

       assertEquals("tdd.board.command.BoardDummyCommand", command);

}

////////////////////////////////////////////////////////////////////////////

 

////////////////////////////////////////////////////////////////////////////

//Dispatcher 기능에 대한 부분만 테스트 

public void testRequestDispatcherForward() throws Exception

{

       BoardServlet servlet = new BoardServlet();

       servlet.init(config);

       servlet.forwardNextUrl(request, response, "/jsp/test.jsp");

}

 

public void endRequestDispatcherForward(WebResponse theResponse)

throws IOException

{

       assertTrue("처리결과 This is Test 라는 문장이 있어야함[" +

theResponse.getText() + "]" ,

theResponse.getText().indexOf("This is Test") >= 0);

}

////////////////////////////////////////////////////////////////////////////

 

////////////////////////////////////////////////////////////////////////////

//Servlet service 대한 처리 테스트

public void beginService(WebRequest theRequest)

{

       theRequest.addParameter("command", "tdd.board.command.BoardDummyCommand");

}

public void testService() throws Exception

{

       BoardServlet servlet = new BoardServlet();

       servlet.init(config);

       servlet.service(request, response);

       assertNull(request.getAttribute("javax.servlet.jsp.jspException"));

       assertEquals("Dummy Command Message",

 (String)request.getAttribute("message"));

}

public void endService(WebResponse theResponse)

{

       //Forward 결과 JSP 페이지 결과 조회

       assertTrue("처리결과 Dummy Command Message 라는 문장이 있어야함[" +

theResponse.getText() + "]",

 theResponse.getText().indexOf("Dummy Command Message") >= 0);

}

////////////////////////////////////////////////////////////////////////////

      

public static Test suite()

{

       return new TestSuite(BoardServletTest.class);

}

public static void main(String[] args)

{

       junit.textui.TestRunner.run(suite());

} 

}

 

생성자와 main() 메소드, suite() 메소드는 앞의 JUnit에서 작성한 것과 동일한 방법으로 작성하면 된다.

위의 테스트 코드는 크게 3가지 기능을 테스트 한다. 첫번째 나타나는 testGetCommand()는 request에서 넘어온 command 파라미터를 제대로 가져오는 지에 대한 테스트이다.

소스를 보면 알겠지만 이 testGetCommand()메소드르 수행하기 전에 request에 command 파리미터를 설정하기 위해 beginGetCommand() 메소드에서 이 메소드의 파라미터로 받은 WeqRequest 객체에 addParameter() 메소드를 이용하여 request의 파라미터 정보를 추가하고 있다.

이러한 방법으로 사용자가 입력한 값, 프로그램을 실행시키는데 필요한 파라미터 정보를 테스트 코드내에 추가할 수 있다.

두 번째 테스트 testRequestDispatcherForward()는 Front Controller 기능중의 하나인 처리결과 적절한 페이지로 이동시켜주는 forward 기능에 대한 테스트이다. 이 테스트의 경우 처리된 결과 정상적으로 원하는 페이지로 forward 되었는지에 대한 검증을 해야 한다. Forward에 대한 검증은 서버측에서 처리가 완료되고 클라이언트로 전송된 response를 이용하여 개발자가 원하는 페이지로 이동했는지를 검증한다. Cactus에서 클라이언트 단에서의 response에 대한 검증은 endXXX() 메소드에서 수행하면 되는데 예제의 경우 endRequestDispatcherForward() 메소드에서 수행하였다.

예제는 test.jsp 페이지로 forward 시키고 있는데 test.jsp 페이지는 다음과 같이 테스트 코드가 삽입되어 있는데 이 테스트 코드와 response의 값이 일치하는지 확인하는 방법으로 검증을 한다.

 

test.jsp

<%@ page contentType="text/html; charset=EUC-KR" %>

This is Test Page

 

 

세 번째 테스트는 Servlet의 실제 기능을 수행시키는 테스트이다. 따라서 테스트를 수행하기 전에 request와 관련된 파라미터를 설정하고 테스트 수행 시에는 servlet의 service() 메소드를 호출하여 servlet을 수행시키고 수행 결과 response에 대한 검증을 한다.

Exception이 발생했는지에 대한 검증, Action(Command)에서 처리 후 결과값을 request에 제대로 설정했는지에 대한 검증 등을 수행하게 된다.

 

BoardDummyCommand.java

package tdd.board.command;

 

import tdd.common.Command;

 

public class BoardDummyCommand extends Command

{

 

protected String doExecute() throws Exception

    {

       request.setAttribute("message", "Dummy Command Message");

        return "/jsp/dummy.jsp";

    }

}

 

 

dummy.jsp

<%@ page contentType="text/html; charset=EUC-KR" %>

<%@ page import="java.util.*" %>

<%

       Enumeration enum = request.getAttributeNames();

      

    while(enum.hasMoreElements())

    {

        out.println("" + request.getAttribute((String)enum.nextElement()));

    }

%>

 

 

여기까지 작성한 다음 Cactus를 이용하여 테스트를 수행해 본다.

 

테스트를 수행하면 Green Bar가 나타나야 하는데 위에서 보는 것처럼 테스트가 실패했다는 메시지가 나타난다. 이유는 위의 화면에서 보는 것처럼 cactus의 proxy 서버가 forward되는 페이지를 찾지 못해서 테스트가 실패하였다. 필자도 여러 가지 옵션을 바꾸어봐도 cactus의 proxy내에서 forward될 때 어떤 context 정보를 이용하여 forward 시키는지 찾을 수 가 없었다.

이 문제에 대해서 필자의 경우 Tomcat을 이용하여 테스트를 수행하였다. Cactus에 대한 테스트를 tomcat 상에서 수행할 수 있는데 다음과 같은 방법으로 tomcat을 설정한 다음 테스트를 하면 된다.

 

1.       [TOMCAT_HOME]/common/lib에 관련 파일 복사

cactus.jar

commons-httpclient.jar

commons-logging.jar

junit.jar

aspectjrt.jar

 

2. [TOMCAT_HOME]/conf/web.xml 파일에 servet 등록

<servlet>

    <servlet-name>ServletRedirector</servlet-name>

    <servlet-class>org.apache.cactus.server.ServletTestRedirector</servlet-class>

    <init-param>

      <param-name>param1</param-name>

      <param-value>value1 used for testing</param-value>

    </init-param>

</servlet>

 

<servlet>

    <servlet-name>ServletTestRunner</servlet-name>

    <servlet-class>org.apache.cactus.server.runner.ServletTestRunner</servlet-class>

</servlet>

 

 

<servlet-mapping>

    <servlet-name>ServletRedirector</servlet-name>

    <url-pattern>/ServletRedirector</url-pattern>

</servlet-mapping>

 

<servlet-mapping>

    <servlet-name>ServletTestRunner</servlet-name>

    <url-pattern>/ServletTestRunner</url-pattern>

</servlet-mapping>

 

3. Tomcat restart 후 다음과 같이 url 입력

http://localhost:8080/tdd/ServletTestRunner?suite=tdd.test.board.web.BoardServletTest

 

위와 같이 수행하면 다음과 같은 테스트 결과 화면을 볼 수 있다.

 

테스트가 실패한 경우는 다음과 같이 실패한 부분과 이유에 대해 표시된다.

 

 

eclipse상에서 모든 테스트가 진행되면 개발자에게 편리하겠지만 아직 필자도 이 부분은 해결을 하지 못했다. 이 부분에 대해 해결 방안이 있으면 필자의 메일로 보내주면 이 글을 버전업하면서 바로 적용하도록 하겠다.

(이렇게 Tomcat 상에서 테스트를 수행하게 되면 뒤에서 설명하고 있는 cactus를 이용하여 request를 전송할 때 한글 인코딩 문제로 인해 한글 깨짐 현상이 발생하게 되는데 Tomcat의 Filter 기능을 이용하면 이러한 문제도 해결할 수 있다.)

 

☞ 게시판 목록 조회 웹 Layer 테스트

 

게시판 목록 조회, 등록 등과 같은 기능에 대한 구현은 각각의 기능에 대한 Action(Command) 클래스를 만들어 해당 기능을 처리하기 위한 request의 파라미터를 이용하여 비즈니스 layer에서 구축한 Service 클래스에 각각의 기능을 요청하도록 구현할 것이다. 따라서 이들 기능에 대한 테스트는 이들 Action(Command) 클래스를 생성하여 명령을 수행하는 메소드를 호출하고 처리결과를 검증하는 방법으로 테스트 프로그램을 작성한다.

먼저 게시판 목록 조회에 대한 Command를 만들기 위해 이 Command를 테스트하는 프로그램부터 먼저 만들자. 클래스의 이름은 BoardListViewCommandTest라고 하고 ServletTestCase로부터 상속을 받는다.

 

BoardListViewCommandTest.java

package tdd.test.board.web;

 

import java.util.List;

 

import junit.framework.Test;

import junit.framework.TestSuite;

 

import org.apache.cactus.ServletTestCase;

import org.apache.cactus.WebRequest;

import org.apache.cactus.WebResponse;

 

import tdd.board.Board;

import tdd.board.BoardService;

import tdd.common.Command;

import tdd.common.CommandFactory;

import tdd.common.DomainKey;

import tdd.common.PageList;

 

public class BoardListViewCommandTest extends ServletTestCase

{

public BoardListViewCommandTest(String name)

{

   super(name);

}

 

public static Test suite()

{

       return new TestSuite (BoardListViewCommandTest.class);

}

            

public static void main (String[] args)

{

   junit.textui.TestRunner.run(suite());

} 

}

 

다음으로 어떻게 테스트를 할 것인지 생각해보자. 테스트를 어떻게 진행하느냐가 곧 프로그램의 기능에 대한 생각이 때문에 이 부분은 사용자의 요구사항에 맞게 작성되어야 할 것이다. 먼저 사용자가 검색조건(게시자, 제목, 내용)을 선택하고 키워드를 입력한 후 검색 버튼을 클릭하게 되면 해당 검색조건에 맞는 목록이 나타나는지를 테스트해보아야 할 것이다. 이러한 스토리를 테스트하기 위해서는 사용자의 요청을 가상으로 만들어야 하는데 beginXXX() 메소드에서 이 기능을 수행하면 된다.

beginXXX() 메소드에서 XXX 부분은 우리가 수행할 실제 테스트 메소드의 이름과 동일해야 하기 때문에 먼저 테스트를 수행할 메소드의 이름을 testBoardList() 라고 정하자. 그러면 beginBoardList() 메소드를 다음과 같이 만들어 Request에 대한 설정을 해주면 될 것이다.

 

public void beginBoardList(WebRequest theRequest)

{

theRequest.addParameter("findType", "TITLE");

theRequest.addParameter("keyword", "TEST");

theRequest.addParameter("command", “tdd.board.command.BoardListViewCommand");

}     

 

beginXXX() 메소드의 파라미터는 WebRequest라는 cactus에서 지원하는 HttpRequest에 상응하는 객체를 받게 되는데 이 객체를 이용하여 Request에 대한 설정을 할 수 있다. 예제에서는 parameter에 대한 설정만 있지만 cookie, session에 대한 설정도 가능하다.

스토리에서 검색조건과 키워드가 나왔기 때문에 파라미터에 이 두 가지 값을 임의로 설정한다.  마지막에 있는 command 파라미터는 Command 객체를 생성할 CommandFactory 클래스에 어떤 Command를 생성해야 하는지에 대한 정보를 전달하기 위해 사용되는 파라미터이다. Front Controller를 이용할 경우 일반적으로 이러한 command, cmd 등과 같은 파라미터명을 이용하여 Front Controller에 어떤 명령을 수행해야 하는지에 대한 정보를 전달하게 된다.

 

다음은 실제 테스트를 수행하는 testBoardList() 메소드를 만들어 보자.

public void testBoardList() throws Exception

{

Command command = CommandFactory.getCommand(request.getParameter("command"));

String url = command.execute(request, response);

            

PageList pageList = (PageList)request.getAttribute("boardList");

assertNotNull(pageList);

assertEquals(pageList.getPage(), 1);

assertEquals(pageList.getTotalSize(), 25);

assertEquals(pageList.getTotalPage(), 3);

            

List dataList = pageList.getData();

assertEquals(dataList.size(), 10);

      

Board board = null;

for(int i = 0 ; i < dataList.size() ; i++)

{

board = (Board)dataList.get(i);

                    assertTrue(board.getTitle().indexOf(request.getParameter("keyword")) >= 0);

}

            

assertNotNull(url);

assertEquals(url, "/jsp/board/BoardListView.jsp");

}

 

CommandFactory에서 생성된 Command 객체의 execute() 메소드를 실행시켜 게시판 목록 조회 기능을 호출하게 되는데 이때 사용되는 파라미터인 request, response는 cactus의 ServletTestCase의 멤버변수로 cactus 테스트 프레임워크 자체에서 테스트 수행 시 자동으로 HttpRequest, HttpResponse에 대한 객체를 생성시키게 된다. 테스트 코드를 만드는 개발자의 입장에서는 별도의 Container의 기동 없이 테스트를 수행할 수 있게 된다.

JSP Model2에서의 동작원리가 Servlet에서 Command를 수행시키면 Command에서는 특정 기능을 수행한 다음 수행결과를 HttpRequest의 request에 다시 담아 보내는 형태로 구현하게 되는데, 이러한 동작원리를 이해해야지만 테스트 코드의 작성도 가능하다.

BoardListViewCommand에서 처리 결과는 게시판 목록 정보이기 때문에 이 정보는 PageList라는 Helper객체를 이용하여 결과를 반환시키도록 구성하면 위의 코드처럼 request.getAttribute() 메소드를 이용하여 PageList 객체를 가져온 다음 우리가 원하는 값들을 조회했는지 검증하면 된다.

여기서도 물론 이 테스트를 수행하기 전에 setUp() 메소드에서 테스트할 데이터를 등록하고 tearDown() 메소드에서 테스트 데이터를 삭제하는 로직을 넣어 준다. 아니면 미리 DB에 이러한 테스트 데이터를 미리 넣어 둘 수도 있다.

public void setUp() throws Exception

{

       BoardService boardService = new BoardService();

       Board newBoard;

       for(int i = 0 ; i < 25 ; i++)

       {

             DomainKey key = new DomainKey(new Integer(1234 + i));

                   

             newBoard = new Board(key);

             newBoard.setTitle("TEST_TITLE" + i);

             newBoard.setContents("TEST_CONTENTS" + i);

             newBoard.setWriteName("TEST_USER_NAME" + i);

                   

             boardService.insertBoard(newBoard);          

       }

}

 

public void tearDown() throws Exception

{

       BoardService boardService = new BoardService();

       for(int i = 0 ; i < 25 ; i++)

       {

             DomainKey key = new DomainKey(new Integer(1234 + i));

            

             boardService.deleteBoard(new Board(key));           

       }

}

 

마지막으로 해당 기능 수행 후 이동해야 할 forward에 대한 정보가 제대로 넘어 왔는지에 대한 검증도 해야 한다.

테스트 결과는 물론 red bar가 나타난다. 게시판 목록 조회 기능을 처리하는 Command인 BoardListViewCommand를 만들지 않았기 때문에 아래 Console에서와 같이 java.lang.ClassNotFoundException: jaso.board.command.BoardListViewCommand 같은 Exception 발생하게 된다.

 

TDD 순서를 다시 한번 보자.

      

1. 테스트 코드 작성

             2. 컴파일 에러 해결

             3. 테스트 실패

             4. 코드 수정

5. 테스트 통과

6. 중복 제거

 

테스트 코드가 컴파일 에러가 났기 때문에 컴파일 에러가 나지 않도록 이제 개발자는 테스트를 통과하는 BoardListViewCommand 클래스를 만들면 된다.

BoardListViewCommand 만들면 컴파일을 통과하게 되지만 테스트를 수행시키면 red bar 나타나게 된다. 그러면 이제 BoardListViewCommand 클래스의 기능을 구현하여 테스트를 통과하도록 하게 만들면 된다. 다음은 BoardListViewCommand 클래스의 구현 예이다.

 

BoardListViewCommand.java

package tdd.board.command;

 

import tdd.board.BoardService;

import tdd.common.Command;

import tdd.common.PageList;

 

public class BoardListViewCommand extends Command

{

protected String doExecute() throws Exception

    {

       BoardService service = new BoardService();

            

       String findType = request.getParameter("findType");

       String keyword = request.getParameter("keyword");

       String page = request.getParameter("page");

       if(page == null)    page = "1";

            

       PageList boardList = service.findBoardByKeyword(

keyword, findType, Integer.parseInt(page));

             

       request.setAttribute("boardList", boardList);

                   

        return "/jsp/board/BoardListView.jsp";

    }

}

 

게시판 목록 조회는 검색 기능만 있는 것이 아니라 페이지 이동 기능도 존재하게 된다, 이제 페이지 이동 기능을 테스트 해보면 페이지 이동 시에는 사용자는 이동할 페이지를 선택하게 된다. 하지만 내부적으로는 이전에 입력한 검색조건에 대한 정보도 같이 Request로 전송되어 오는데 이러한 사항을 스토리로 구성하여 테스트 코드를 작성하면 된다.

 

☞ 게시판 등록 Layer 테스트

 

게시판 등록 기능의 웹 Layer에 대한 테스트는 게시판 목록 조회보다는 조금 더 복잡하다.웹 시스템에서 게시판 등록의 경우 일반적으로 다음과 같은 흐름으로 진행된다.

 

             등록 화면 → DB 등록 처리 → 목록조회화면

 

JSP Model2에서는 등록처리를 하는 Command(Action) 클래스에서 실제 등록 처리를 하고 forward 정보를 front controller에게로 반환 할 때 자신의 forward 정보를 반환하는 것이 아니라 목록 조회화면 Command를 수행시키고 이 Command 처리 후 forward 정보를 반환하게 된다.

 

 

필자의 경우 다음과 같은 방법으로 테스트를 수행했다.

 

1. 입력 데이터를 HttpRequest 파라미터에 설정(beginXXX)

2. InsertCommand 수행(testXXX)

3. forward 결과가 ListCommand forward 결과와 동일한지 검증(testXXX)

4. ListCommand 수행후 검증해야 할 사항을 검증(testXXX)

5. 입력한 데이터 삭제를 위해 입력한 데이터 조회(tearDown)

6. 조회된 결과가 입력한 값과 일치하는지 검증(tearDown)

 

BoardInsertCommandTest.java

/*

 * Created on 2003-09-13

 *

 * To change the template for this generated file go to

 * Window>Preferences>Java>Code Generation>Code and Comments

 */

package tdd.test.board.web;

 

import java.util.List;

 

import junit.framework.Test;

import junit.framework.TestSuite;

 

import org.apache.cactus.ServletTestCase;

import org.apache.cactus.WebRequest;

import org.apache.cactus.WebResponse;

 

import tdd.board.Board;

import tdd.board.BoardService;

import tdd.common.Command;

import tdd.common.CommandFactory;

import tdd.common.PageList;

 

public class BoardInsertCommandTest extends ServletTestCase

{

public void beginBoardInsert(WebRequest theRequest) throws Exception

{

     theRequest.addParameter("TITLE", "INSERT_TEST11");

       theRequest.addParameter("CONTENTS", "INSERT_TEST_CONTENTS");

            

       theRequest.addParameter("command", "tdd.board.command.BoardInsertCommand");

}

 

public void setUp()

{

}

            

public void testBoardInsert() throws Exception

{

       session.setAttribute("USERID", "babokim");

       session.setAttribute("USERNAME", "김형준");

 

       Command command = CommandFactory.getCommand(request.getParameter("command"));

       String forward = command.execute(request, response);

            

       PageList pageList = (PageList)request.getAttribute("boardList");

       assertNotNull(pageList);

       assertEquals(pageList.getPage(), 1);

            

       List dataList = pageList.getData();

       assertNotNull(dataList);

       assertTrue(dataList.size() >= 1);

      

       assertNotNull(forward);

       assertEquals(forward, "/jsp/board/BoardListView.jsp");

}

 

public void tearDown() throws Exception

{

       BoardService service = new BoardService();

            

       PageList pageList = service.findBoardByKeyword("INSERT_TEST11", "TITLE", 1);

       List data = pageList.getData();

       assertTrue(data.size() == 1);

       Board board = (Board)data.get(0);

       service.deleteBoard(board);                  

}

            

public void endBoardInsert(WebResponse theResponse)

{

}

/////////////////////////////////////////////////////////////////////////////

 

public BoardInsertCommandTest(String name)

{

     super(name);

}

 

public static Test suite()

{

       return new TestSuite (BoardInsertCommandTest.class);

}

            

public static void main (String[] args)

{

       junit.textui.TestRunner.run(suite());

} 

}

 

위의 예제 코드에서도 알 수 있듯이 웹 layer에 대한 테스트 수행 시 실제 DB에 값이 저장되었는지에 대한 검증은 할 필요가 없다. 이유는 앞에서 만든 비즈니스 Layer에서의 테스트 프로그램에서 검증을 했기 때문이다. 웹 layer에서는 원하는 비즈니스 layer의 기능이 호출되었는지 처리결과 request, response 값이 의도한 결과대로 나타나는지에 대한 검증만 하면 된다.

게시판의 경우 게시판의 키 값이 DB에 저장되면서 자동으로 생성되기 때문에 입력된 데이터를 삭제하기 위해 등록된 정보를 찾아야 하는데 이 부분이 어려웠다. 게시판의 경우 게시판 제목으로 글을 찾는 기능이 있어 이를 이용하여 등록 시 특이한 제목으로 등록하고 이 제목을 이용하여 찾은 다음 제대로 입력되었는지 확인하고 삭제 처리하였다. 하지만 이런 기능이 없는 경우 테스트하기가 어려워 질 수 있는데 이런 사항은 개별적으로 해결해야 할 것이다.

 

BoardInsertCommand.java

package tdd.board.command;

 

import tdd.board.Board;

import tdd.board.BoardService;

import tdd.common.Command;

 

/**

 * @author babokim

 *

 * To change the template for this generated type comment go to

 * Window>Preferences>Java>Code Generation>Code and Comments

 */

public class BoardInsertCommand extends Command

{

protected String doExecute() throws Exception

    {

       String title = request.getParameter("TITLE");

       String contents = request.getParameter("CONTENTS");

      

       Board board = new Board();

      

       board.setTitle(title);

       board.setContents(contents);

       board.setWriteName((String)session.getAttribute("USERNAME"));

      

       BoardService service = new BoardService();

      

       service.insertBoard(board);

      

       return (new BoardListViewCommand()).execute(request, response);

    }

}

 

◆ 켄트벡이 주장하는 TDD(서문 중에서)

 

번역하기가 너무 어려워 원문 그대로 올립니다.

 

Test-Driven Development By Example

 

Clean code that works, in Ron Jeffries pithy phrase. The goal is clean code that works, and for a whole bunch of reasons:

l        Clean code that works is a predictable way to develop. You know when you are finished, without having to worry about a long bug trail.

l        Clean code that works gives you a chance to learn all the lessons that the code has to teach you. If you only ever slap together the first thing you think of, you never have time to think of a second, better, thing.

l        Clean code that works improves the lives of users of our software.

l        Clean code that works lets your teammates count on you, and you on them.

l        Writing clean code that works feels good.

 

But how do you get to clean code that works? Many forces drive you away from clean code, and even code that works. Without taking too much counsel of our fears, heres what we dodrive development with automated tests, a style of development called Test-Driven Development (TDD for short). In Test-Driven Development, you:

l        Write new code only if you first have a failing automated test.

l        Eliminate duplication.

 

Two simple rules, but they generate complex individual and group behavior. Some

of the technical implications are:

l        You must design organically, with running code providing feedback between decisions

l        You must write your own tests, since you cant wait twenty times a day for someone else to write a test

l        Your development environment must provide rapid response to small changes

l        Your designs must consist of many highly cohesive, loosely coupled components, just to make testing easy

The two rules imply an order to the tasks of programming:

 

1.       Redwrite a little test that doesnt work, perhaps doesnt even compile at first

2.       Greenmake the test work quickly, committing whatever sins necessary in the process

3.       Refactoreliminate all the duplication created in just getting the test to work

 

Red/green/refactor. The TDDs mantra.

Assuming for the moment that such a style is possible, it might be possible to dramatically reduce the defect density of code and make the subject of work crystal clear to all involved. If so, writing only code demanded by failing tests also has social implications:

l        If the defect density can be reduced enough, QA can shift from reactive to proactive work

l        If the number of nasty surprises can be reduced enough, project managers can estimate accurately enough to involve real customers in daily development

l        If the topics of technical conversations can be made clear enough, programmers can work in minute-by-minute collaboration instead of daily or weekly collaboration

l        Again, if the defect density can be reduced enough, we can have shippable software with new functionality every day, leading to new business relationships with customers

 

So, the concept is simple, but whats my motivation? Why would a programmer take on the additional work of writing automated tests? Why would a programmer work in tiny little steps when their mind is capable of great soaring swoops of design? Courage.

 


 

◆ 필자의 의문사항

 

앞에서 밝혔듯이 이 컬럼은 TDD에 대한 개념적인 이해만 가지고 있는 필자가 실제 pilot 프로젝트 성격으로 진행한 것이기 때문에 틀린 부분도 많이 있고 필자 역시 의문점이 많다. 여기서는 필자의 의문점을 정리하여 이 부분에 대해 서로의 의견을 나누는 기회를 마련하였으면 한다.

 

☞ 테스트 파일 단위

 

필자의 경우 테스트 단위를 Service의 하나의 기능 단위로 만든 다음 각각의 파일 전체를 한번에 수행시킬 수 있는 TestSuit를 작성하는 방법으로 하였는데 이렇게 구성할 경우 테스트 파일이 너무 많이 생기게 된다.

하지만 Service 별로 하나만 만들 경우에도 하나의 Test 프로그램에 너무 많은 테스트 케이스가 존재하게 되어 Test의 관리가 어려워지는데 적당한 테스트 단위를 어디까지 정해야 하는지에 대해 좀 더 논의가 되어야 할 것 같다.

이 부분은 테스트 프로그램의 관리 방안과도 연결되는 문제인데 어차피 향 후 유지보수 시에도 지속적으로 이 테스트 프로그램을 이용하기 위해서는 체계적인 관리가 필요하다 할 수 있다. 이렇게 관리 포인트가 추가되게 되면 개발자/유지보수자의 입장에서는 또 다른 업무가 추가되는 것이라서 어떻게 받아들일지?

 

☞ 테스트 데이터의 생성

              

목록 조회, 페이지의 이동 등과 같은 사항을 테스트 하기 위해서는 많은 양의 데이터가 필요한데 이들 데이터에 대한 입력 방안

 

☞ 웹 레이어와 비즈니스 레이어 테스트 사이에 발생하는 코드의 중복

 

테스트를 작성하다 보면 테스트를 위한 setup(), tearDown()과 같은 준비 단계에서는 거의 유사한 코드가 중복되어 생성되게 된다. 이렇게 작성하는 것이 옳은 것인지 아니면 또 다른 방법이 있는지?

 

☞ 웹 계층과 비니지스 계층의 개발을 병렬로 개발할 경우의 테스트 방안

 

MVC 모델을 이용하게 되면 비즈니스 Layer와 웹 Layer를 병행 개발할 수 있는데, 이럴 경우 웹 Layer의 개발자에게는 아직까지 비즈니스 Layer에 대한 구현이 완료되지 않았기 때문에 테스트를 수행해 볼 수 없다. 물론 Mock Object 기법과 같은 Dummy 비즈니스 기능을 수행하는 클래스를 만들고 진행하며 되지만 이 또한 overhead가 아닌가 하는 생각이 든다.

 

cactus를 이용하는 경우 한글 처리 문제

 

앞에서도 잠깐 언급 했지만 cactus를 이용할 경우 한글 문제와 forward에 대한 문제는 해결해야 할 것 같다.

 

       
4. 글을 마치며.

 

이번 글의 목적은 eclipse가 아니라 TDD(Test-Driven Development)에 있다고 할 수 있다. TDD를 하기 위해 조금이나마 개발자들의 수고를 덜기 위해 eclipse를 사용했던 것이다. 하지만 개발자들이 얼마나 많이 이 TDD를 사용하게 될지는 필자 역시 의문이다.

TDD를 설명 받은 대부분의 개발자들이 모두 비현실적이고 이상적인 이론이라는 것이다. 필자 역시 처음에는 그러한 생각을 가지고 시작했지만 실제 몇 개의 테스트 코드를 작성해보고 테스트를 수행하고 기존에 만든 코드에 새로운 기능을 추가하고 테스트를 수행하는 절차를 거치면서 테스트 프로그램의 막강한 위력에 실감을 하였다.

물론 개발 납기에도 맞추기 어려운 상황에서 테스트 코드까지 만들어야 한다는 부담감이 있을 수는 있다. 하지만 실제 프로젝트에서 어떻게 진행되고 있는지 한번 보자.  개발자들은 자신의 프로젝트의 일정에 맞추어 자신이 맞은 프로그램 목록 상의 기능을 개발한다. 문론 정해지니 일정이 있기 때문에 간단한 단위 테스트 정도만 수행하고 다음 프로그램으로 넘어간다. 이렇게 2 ~ 4개월 정도의 코딩이 끝난 다음 프로젝트 막바지에 전체적인 통합 테스트를 수행하게 되면 많은 예상하지 못한 프로그램 오류가 나타나게 된다. 그러면 개발자들은 이 오류를 수정하기 위해 프로그램을 수정하고 또 테스트를 하게 된다. 프로그램을 수정할 때에도 2 ~ 4개월 전에 작성한 프로그램이기 때문에 제대로 생각도 나지 않고 실제 어떻게 테스트를  해야 하는지 모를 경우도 많다. 납기는 맞추어야 하기 때문에 밤을 새워가며 간신히 통합 테스트를 통과했다 하더라도 개발자의 마음속에는 여전히 불안한 마음을 지울 수 없게 된다. 대부분이 이렇지 않은가? 이것을 해결할 수 있는 다른 방법이 있다면 최소한 한번정도는 시도해볼 수 있지 않을까 하는 것이 필자의 주장이다.

이제 국내 프로젝트도 소스 코드 자체의 품질에 관심을 가져야 한다. 언제까지 개발자들의 양심(?)에만 의존할 수는 없다. 개발자의 의지만으로는 TDD는 절대 수행 할 수 없다라는 것이 필자가 생각이다. 물론 앞서나가는 개발자라면 자신이 개발한 소스 부분에 대해서만이라도 TDD를 할 수 있겠지만 이러한 개발자 하나의 노력만으로 어렵다. 프로젝트 관리의 요소로 이 TDD를 도입하여 개발자들을 교육시키고, 테스트 기법에 대해 연구하는 노력이 필요하다 할 수 있다.

 

프로젝트의 가장 중요한 산출물은 설계 문서가 아닌 제대로 운영되는 소스(Clean code that work)이다.

 

마지막으로 이 글에서 작성한 내용은 필자의 개인적인 의견으로 작성되었으며 필자 역시 전체적으로 이해하지 못한 상태에서 파일럿 프로젝트 성격으로 진행된 사항이기 때문에 실제 프로젝트 진행시 또는 개념적으로 잘못 작성되었을 수도 있다는 것을 다시 한번 밝히면서 작성된 글 중 잘못된 부분이나 더 좋은 방안을 가지고 있는 개발자가 있으면 반드시 연락을 주시기 바랍니다. 저 역시 혼자서 학습을 진행하다 보니 너무 어려운 점이 많네요. ^^

 

 

Reference

 

[1] Test-Driven Development By Example

Kent Beck , 2002

 

[2] http://www.eclipse.org」

             eclipse 홈페이지

 

[3] http://www.junit.org」

             JUnit 홈페이지

 

[4] http://www7b.software.ibm.com/wsdd/partners/merant/」

             PVCS에 대한 eclipse 적용 관련 페이지

 

[5] http://www.jlab.net/」

             한국 eclipse 사용자 모임

 

[6] http://xper.org/wiki/xp/

             한국 eXtreme Programming 사용자 모임

 

[7] JSP Model2를 이용한 웹사이트 구축 사례」

             김형준, 2003

 

[8] http://www.javajigi.net/

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1

3. TDD(Test Driven Development) 적용 예

 

프로그램을 개발(코딩)할 때 개발자들이 어떤 작업에서 가장 많은 시간을 소비할까? 개발 환경 구성, 프로그램의 구상, 소스코드의 타이핑, 테스트 및 디버깅 등과 같은 여러 작업이 있는데 이중에서 필자의 경우 테스트 및 디버깅에 가장 많은 시간을 소비하는 것 같다. 하지만 이 테스트 및 디버깅 과정은 필자가 가장 싫어 하는 과정이기도 하다.

하나의 프로그램을 테스트하기 위해서는 모든 예외사항에 대한 데이터를 직접 입력해야 하고 실제 데이터베이스에 정확하게 반영되는지도 확인해야 하기 때문이다. 이렇게 테스트를 수행한다고 해도 프로그램을 Release한 다음에는 여전히 마음 한 구석에는 버그에 대한 불안한 마음이 있다.

해당 기능에서 다루는 데이터가 돈과 관련이 있거나, 비즈니스 수행에 치명적인 데이터인 경우 이러한 불안감에 불면으로 고생하는 개발자들도 있을 것이다.

필자의 경우 7 ~ 8년 동안 이러한 불안 속에서 생활을 계속해왔다고 할 수 있다. 그러면 개발자들은 이런 버그에 대한 불안을 개발직에 종사하는 그 순간까지 계속 가지고 가야 하는가? 필자의 경우도 이러한 불안감으로 많은 스트레스를 받아 왔지만 해결하려고 하는 노력은 거의 없었다.

이번 장에서는 이러한 테스트와 관련되어 요즘(?, 이미 오래 전부터 있었고 실제 사용되어지던 내용들이다. 필자와 같이 노력을 기울이지 않은 개발자들에게는 요즘에서야 알게 되겠지만 말이다.) 주목 받고 있는 TDD(Test-Driven Development)에 대해 알아보고 eclipse를 이용하여 쉽게 TDD를 적용하는 방법에 대해 소개하고자 한다.

TDD 는 말 그대로 테스트 프로그램을 위주로 개발을 수행하는 기법이다. 간단하게 요약하자면 프로그램을 개발하기 전에 개발할 프로그램을 테스트하는 프로그램을 먼저 만든 다음 이 테스트 프로그램이 컴파일이 되고 테스트가 모두 통과하도록 실제 프로그램을 개발해 나가는 기법이다.

이렇게 테스트 프로그램을 먼저 만드는 장점은 테스트 수행 자체가 자동(프로그램)으로 이루어지기 때문에 한번 작성된 테스트 프로그램을 이용하여 지속적인 테스트가 가능하다는 것과 이러한 테스트를 기반으로 하여 신뢰성이 놓은 시스템을 구축할 수 있다는 것이다.

또 다른 장점은 기존의 테스트 프로그램이 없는 시스템의 경우 유지보수 시 특정 부분에 대해 변경이 가해졌을 때 시스템 전체에 대해 수작업으로 다시 테스트를 수행해야 하는 번거러움이 있고 테스트가 완료되었다 하더라도 어디 부분에서 side effect가 발생할 지 예측을 할 수 없다.

하지만 테스트 프로그램을 미리 만들어 놓고 이 테스트 프로그램을 모두 통과한 프로그램을 release하고 운영 중이라고 하면 이때 만들어진 테스트 프로그램은 해당 어플리케이션의 기능 테스트를 만족시켰기 때문에 정상적으로 운영이 될 것이다.

여기까지 설명하면 대부분의 개발자들은 아니 개발 납기 맞추기에도 일정이 촉박한데 거기다가 테스트 프로그램까지 만들라고 하다니! 너무 비현실적인 내용이야!! 라고 말하는 개발자들이 많다. 필자의 경우도 그러했다. 하지만 곰곰히 생각해 보면 앞에서 필자가 가장 많은 시간이 걸리는 부분이 테스트 및 디버깅 부분이라고 했다. 이 부분의 시간을 줄이게 되면 그만큼의 시간을 버는 것이 될 것이다. 몰론 초기에 익숙하지 않은 개발 기법에 적응하는데 있어 개발 생산성의 저하는 모든 기술에 있어 학습의 과정에서는 어쩔 수 없는 것이다.

조금의 시간을 더 투자해서 좀 더 안정적인 코드가 나와 개발자의 불안함을 조금이나마 해소 시켜 줄 수 있으면 선택해볼 만도 하다는 것이 필자의 생각이다.

 

필자 역시 이 TDD에 대해서 익숙하지 않고 실제 프로젝트에 적용해본 경험도 없다. 지금 필자의 상태는 이 TDD를 프로젝트에 어떻게 적용을 할 수 있는지 고민하고 스터디를 하고 있는 중이다. 물론 관련 세미나도 참석을 하고 여러 서적을 참고하고 있지만 구체적으로 프로젝트에 어떻게 적용했다라고 하는 사례가 없기 때문에 여전히 고전을 면치 못하고 있다.

가장 좋은 방법은 TDD를 적용한 프로젝트에 참여하는 방법이겠지만 현실적으로 필자가 근무하는 곳에서는 아직 이러한 사항을 적용하기에는 시기상조라고 생각하고 있다.

이번 장의 목적은 필자가 스터디하면서 진행한 사항을 정리하고 제대로 방향을 잡아가고 있는지에 대해 이 글을 읽는 독자들로부터 자문을 구하기 위해서라고 생각하면 된다.

따라서, 이번 장에 나타나는 내용은 순수하게 필자의 생각만을 위주로 작성하는 글이기 때문에 글을 읽는 독자들은 이런 방법도 있구나라는 정도의 참고 수준 정도로만 인식해 주었으면 한다. 또한 잘못된 방향이나 설명이 있으면 즉시 알려주면 반영하도록 하겠다.

이 글의 대부분은 켄트벡이 쓴 Test-Driven Development By Example 라는 글을 내용을 참고로 하여 작성하였다. TDD에 대해 좀 더 깊은 내용을 알고 싶으면 이 글을 참고하기 바란다.


JUnit을 이용한 테스트 프로그램

 

그러면 eclipse를 이용하여 TDD를 적용하는 방법을 살펴보자. 먼저 JUnit를 이용하여 기본적인 테스트 프로그램을 작성해본 다음 cactus를 이용하여 Http 처리관련 테스트 프로그램을 만들어 보자.

JUnit은 테스트 프로그램에 대한 프레임워크를 제공하고 GUI 테스트 도구를 지원한다. eclipse를 이용하지 않고도 www.junit.org에서 다운로드 받아 사용할 수 있다.

먼저 간단하게 JUnit을 이용하여 간단한 테스트 프로그램을 만들어 본 다음 eclipse에서 사용하는 방법에 대해 알아보자.

 

JUnit을 이용하여 테스트 프로그램을 만들고 테스트하는 것은 JUnit 자체가 프레임워크로 구성되어 있기 때문에 이 프레임워크를 이용하여 정해진 규칙대로만 코드를 만들면 된다.

먼저 다운로드 받은 JUnit 파일을 적당한 디렉토리에 압축을 해제한다. 압축을 해제한 다음 DOS창에서 junit.jar 파일을 클래스패스에 설정하고 다음과 같은 명령을 이용하여 JUnit 테스트 GUI 도구를 실행시켜 본다.

 

 

 

다음과 같은 프로그램이 수행된다.

 

여기까지 성공했으면 JUnit의 설치는 끝났다.

이제 테스트 프로그램을 작성해보자. TDD는 다음과 같은 순서로 진행을 하면 된다. 이 순서는 켄트벡이 제시한 순서이다.

 

1. Quickly add a test(테스트 프로그램을 작성한다.)

2. Run all tests and see the new one fail

(모든 테스트 프로그램을 수행시키고 테스트에 실패한 부분을 확인한다.)

3. Make a little change(소스를 추가하거나 변경한다.)

4. Run all tests and see them all succeed

(다시 모든 테스트를 수행시키고 모두 테스트를 통과했는데 확인한다.)

5. Refactor to remove duplication(중복을 제거하기 위해 Refactoring 한다.)

 

켄트벡이 주장하는 것 중 가장 중요한 것은 먼저 테스트 프로그램을 만들라는 것이다. 그리고 가장 간단한 부분부터 먼저 테스트 프로그램을 작성하고 이 테스트가 통과하도록 프로그램을 만들라고 말하고 있다.

여기서는 예제로 두 Money를 더하기 하는 기능에 대한 프로그램을 TDD를 이용하여 만들어 보자. 지금은 TDD에 대한 흐름을 알기 위해서기 때문에 아주 간단한 예제를 선택했다.

이 예제는 켄트벡이 제시한 예제인데 켄드벡의 글을 보면 아주 자세한 예제 및 설명이 나와 있다.

먼저 테스트 프로그램을 만든다.

 

AddTest.java

import junit.framework.Test;

import junit.framework.TestCase;

import junit.framework.TestSuite;

 

public class AddTest extends TestCase

{

public AddTest(String name)

        {

             super(name);

       }

   

public void testAdd()

{

Money fiveMoney = new Money(5);

fiveMoney.add(new Money(10));

assertEquals(15, fiveMoney.getAmount());

}    

}

 

테스트 프로그램은 JUnit에서 제공하는 TestCase 클래스를 상속 받아 구현해 주면 된다.

테스트 로직을 구현할 때는 메소드 명을 testXXX()와 같은 형태로 작성하면 JUnit에서 자동으로 해당 메소드를 수행시키고 테스트 결과를 보여주게 된다. 위의 예제에서는 testAdd() 메소드가 테스트 대상이 된다. 하나의 테스트 클래스에는 여러 개의 테스트 메소드 작성이 가능하다.

testXXX() 메소드 내에서는 테스트 대상이 되는 기능을 수행시키고, 그 수행 결과가 개발자가 원하는 결과가 나타나는지 검증을 하는 로직을 작성해주면 된다.

위의 예제에서는 테스트 하고자 하는 기능은 두 Money의 합을 구하는 기능이기 때문에 fiveMoney.add() 가 수행되었다.

검증을 위한 부분은 두 Money의 합의 결과가 제대로 수행되었는지 확인하기 위해 assertEquals() API를 이용하여 값을 비교하여 결과가 15인 경우 테스트를 통과한 것으로 인정하고 15가 아닌 경우 테스트를 실패 처리한다.

JUnit을 이용하여 테스트 결과를 검증하는 방법은 assertEquals, assertNotNull, assertNull, assertNotEquals, assertTrue 등과 같은 검증용 메소드를 이용하여 검증한다.

 

 

DOS 창에서 위의 화면과 같이 클래스 패스를 설정해 주고 컴파일을 수행하게 되면 당연히 에러가 발생할 것이다. 에러의 원인은 아직까지 실제 우리가 구현하고자 하는 Money 클래스를 만들지 않았기 때문이다. TDD는 이렇게 Money 클래스를 먼저 만드는 것이 아니라 Money클래스를 테스트하는 AddTest 클래스부터 먼저 만들고 이 AddTest클래스가 정상적으로 동작하도록 하는 순서로 개발을 진행하게 된다.

 

Money.java

public class Money

{

    int amount;

   

    public Money(int amount)

    {

        this.amount = amount;

    }

}

 

다시 컴파일을 해본다. 처음에는 Money 클래스가 없다는 에러가 나타났는데 지금은 다른 에러가 나타났다.

 

AddTest.java:15: cannot resolve symbol

symbol  : method add  (Money)

location: class Money

        fiveMoney.add(new Money(10));

                 ^

AddTest.java:16: cannot resolve symbol

symbol  : method getAmount  ()

location: class Money

        assertEquals(15, fiveMoney.getAmount());

                                  ^

2 errors

 

이 두개의 에러를 잡기 위해 Money 클래스에 add() 메소드와 getAmount() 메소드를 추가한다. 아직까지 구체적인 기능에 대한 구현은 생각하지 않았기 때문에 메소드 정의만 한다.

 

Money.java

public class Money

{

    int amount;

   

    public Money(int amount)

    {

        this.amount = amount;

    }

   

    public void add(Money money)

    {

    }

   

    public int getAmount()

    {

    }

}

 

이제 컴파일 하면 다음과 같이 에러가 하나만 나타난다.

 

.\Money.java:15: missing return statement

    {

    ^

1 error

 

getAmount() 메소드에서는 int type을 반환해야 하는데 반환하는 값이 없기 때문에 발생한 에러이다. 여기에 return 0; 이라고 수정하고 다시 컴파일 해보자.

 

Money.java

public class Money

{

    int amount;

   

    public Money(int amount)

    {

        this.amount = amount;

    }

   

    public void add(Money money)

    {

    }

   

    public int getAmount()

    {

        return 0;

    }

}

 

예상대로 컴파일은 통과하였다. 그러면 테스트가 성공하였는지를 JUnit을 이용하여 수행시켜 보자.

위의 테스트 프로그램의 경우 main() 메소드가 없기 때문에 앞에서 본 JUnit에서 제공하는 GUI 도구를 이용하여 테스트를 수행할 수 있다. JUnit 설치 시 수행시킨 JUnit GUI 도구를 실행시킨 다음 Test Class name 오른쪽에 있는 “…” 버튼을 클릭하여 테스트 할 클래스를 선택한다. 이 “…” 버튼을 클릭하게 되면 TestCase를 상속받은 모든 테스트 클래스가 목록에 나타나게 된다. 여기서는 AddTest를 선택한다.

 

 

Run 버튼을 클릭하여 테스트를 수행시킨다.

 

지금까지 진행된 상태에서 테스트를 수행하게 되면 테스트가 실패했다는 표시로 빨간색 바가 나타나고 화면의 가운데 부분에 어느 부분에서 실패했는지가 나타난다. 여기서는 15라는 값이 나타나야 결과는 0 값이 나왔기 때문에 테스트가 실패하였다.

 

           Junit.framework.AssertionFailedError:expected:<15>but was:<0>

 

그러면 이제 다시 소스로 돌아와서 소스를 적절하게 수정하여 테스트를 통과하도록 만든다. 가장 간단하게 테스트를 통과하도록 하는 방법은 getAmount() 메소드에서 15를 반환하도록 수정 하면 된다.

 

Money.java

public class Money

{

    int amount;

   

    public Money(int amount)

    {

        this.amount = amount;

    }

   

    public void add(Money money)

    {

    }

   

    public int getAmount()

    {

        return 15;

    }

}

 

다시 JUnit의 Run 버튼을 클릭해보면 녹색 바가 나타나면서 테스트가 통과했다는 메시지가 나타난다. 테스트를 통과했기 때문에 우리가 원하는 Money 클래스가 완성된 것일까? 지금의 Money 클래스는 너무 간단해서 누가 봐도 완전하게 구성된 것이 아니라 테스트를 통과하기 위해 억지로 끼워 맞추었다는 것을 알 수 있다. 그러면 여기서 어떻게 진행해야 하나.

해답은 켄트벡이 제시한 방법에서 마지막 단계인 Refactor to remove duplication에 있다. 테스트 코드와 실제 프로그램 사이의 중복을 제거하는 것이다. 여기서는 15라는 숫자가 중복으로 나타난다. 이 15라는 숫자를 제거하고 Money 멤버 변수인 amount를 반환하도록 Money 클래스를 수정하게 되면 다시 테스트를 통과하지 못할 것이다.

그러면 다시 코드의 수정 → 테스트 실패 → 테스트 성공 → 중복 제거의 단계를 거치면서 Money 클래스를 완성하게 된다. 다음은 완성된 Money 클래스이다.

 

Money.java(최종본)

public class Money

{

    int amount;

   

    public Money(int amount)

    {

        this.amount = amount;

    }

   

    public void add(Money money)

    {

        amount += money.getAmount();

    }

   

    public int getAmount()

    {

        return amount;

    }

}

 

Test 수행을 JUnit GUI 도구를 이용하지 않고 일반 명령행 상태에서도 수행할 수 있는데 이렇게 수행할 경우 테스트 프로그램에 main() 메소드 추가하고 다음과 같이 작성하면 된다.

 

public static Test suite()

{

       return new TestSuite(AddTest.class);

}

           

public static void main(String[] args)

{

       junit.textui.TestRunner.run(suite());

}

 

 

지금까지 아주 간단한 TDD의 예제를 해보았다. 좀더 상세한 설명의 다른 TDD관련 서적 및 켄트벡의 문서를 참조하기 바란다.

 

대부분의 JUnit에 대한 설명은 이정도 수준으로 밖에 나와 있지 않다. 필자 역시 여기까지 해 본 다음 실제 어플리케이션에 적용하기가 무척 난감하였다. 개발자 입장에서 수백페이지의 설명보다는 단 한 줄의 소스로 이해할 수 있는 경우가 많은데 아직까지 국내 TDD 또는 해외의 각종 TDD 관련 문서에도 실제 프로젝트에 적용한 예제가 없어(적어도 필자가 찾아 본 경우는) TDD를 시작하는 개발자가 이론적인 설명만 가지고 많은 시행착오를 겪고 있는 것이다.

여기에 필자 나름대로 적용한 방법에 대해 살펴보고 실제 코드를 통해 알아보기로 하자.

 

◇ 각각의 Layer별로 테스트 프로그램을 작성한다.

Data Layer, Business Layer, View Layer 등에 대해 각각의 테스트 프로그램을 작성한다.

◇ 비즈니스 Layer의 테스트의 구성은 하나의 Service 단위를 폴더로 만들고 각각의 기능 단위로 테스트 클래스를 만든다.

◇ 이들 Service 단위를 전체로 묶는 테스트 Unit을 작성하여 나중에 일괄 테스트 시 사용한다.

DB 처리가 필요한 테스트의 경우 setUp() 메소드에서 테스트할 데이터의 값에 대한 입력 작업을 수행하고, tearDown()  메소드에서 테스트를 위해 입력한 값을 삭제한다.

(DB와 관련된 사항을 테스트 하는 경우 DB 접속, SQL의 수행 속도 등과 같은 이유 때문에 DB에서 값을 return해 주는 역할을 대신해 주는 Mock Object를 만들어 이를 이용하여 테스트하는 방법도 있지만 실제 프로젝트에서는 DB에 정보가 제대로 저장되는지, DB에 있는 정보를 제대로 가지고 오는지에 대한 사항이 중요하기 때문에 여기서는 직접 DB를 이용하여 테스트를 수행하도록 하였다.)

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1
이전버튼 1 2 이전버튼