Saturday, July 22, 2006

EasyMock

ผมผัดผ่อนกับเรื่อง Unit Testing แบบจริงๆจังมานานแล้ว
ส่วนใหญ่จะทำนิดทำหน่อย ตรงจุดที่ไม่แน่ใจใน algorithm
มา project ตัวปัจจุบันนี้ก็เลยตั้งใจว่า
จะบังคับตัวเองให้เขียน testcase ดีๆให้ได้

ตัว tool ที่เลือกใช้ ก็คือ TestNG
ซึ่งหลังจากเขียนไปได้จำนวนหนึ่ง ก็รู้สึกไม่ค่อยมี feedback มากระตุ้นตัวเองมากพอ
ก็เลยเอา JCoverage มาใช้อีกตัว

ใช้ไปได้สักพัก ก็ยังรู้สึกว่ามีแรงจูงใจยังไม่พออยู่ดี
เนื่องจากเรารู้ตัวเองว่า เรายังหลีกเลี่ยงไม่แตะ source code ส่วนที่สำคัญจำนวนหนึ่งอยู่
เนื่องจากมันเป็น code ส่วนที่ไกล้ชีิดกับ Database
ซึ่งเมื่อทำเสร็จมันต้อง rollback ข้อมูลที่ insert, update ลงไป
เพื่อให้ database อยู่ในสถานะก่อนหน้าที่จะ test
(ซึ่งเดิมที่เคยทำ ผมใช้ DBUnit ช่วยทำในส่วนนี้)

ปัญหาหลักๆก็คงอยู่ตรงความขี้เกียจนี่แหล่ะ ขี้เกียจเตรียมข้อมูล

EasyMock เข้้ามาเป็นพระเอกในจุดนี้
EasyMock ช่วยให้ผมสามารถ test business logic โดยไม่ต้องยุ่งกับ database ได้

เริ่มแรก สมมติเรามี logic การ Login อยู่
public class AuthenService {

IUserDao userDao;

public boolean authenticate(String login, String password)
throws DisableAccountException{

User user = userDao.getByLoginId(login);
if (user.isEnable()) {
if (user.getPassword().equals(password)) {
return true;
}
} else {
throw new DisableAccountException();
}
if (userDao.increaseFailCount() > 3) {
userDao.disableLogin(login);
}
return false;
}

}

โดยเราจะ test logic "ในกรณีที่ login ไม่สำเร็จ 3 ครั้งให้ disable account ของ user"
คัว class ที่เราจะสร้าง Mock ก็คือ IUserDao
public interface IUserDao {
/**
*
@param login
*
@return
*/

public User getByLoginId(String login) ;

/**
*
@return number of fail count
*/

public int increaseFailCount();

/**
*
@param login
*/

public void disableLogin(String login);

}


เริ่มด้วยการเขียน testcase
public class TestLogin {

private User createUser(boolean flag) {
User user = new User();
user.setLogin("pok");
user.setPassword("xxx");
user.setEnable(flag);
return user;
}

@Test
public void testDisableAccount() {

IUserDao dao = createStrictMock(IUserDao.class);

expect(dao.getByLoginId("pok")).andReturn(createUser(true));
expect(dao.increaseFailCount()).andReturn(4);
dao.disableLogin("pok");
expect(dao.getByLoginId("pok")).andReturn(createUser(false));

replay(dao);

AuthenService service = new AuthenService();
service.setUserDao(dao);
try {
service.authenticate("pok", "yyy");
} catch (DisableAccountException de) {
fail("early throw exception");
}
try {
service.authenticate("pok", "yyy");
fail("should not be here.");
} catch (DisableAccountException de) {

}
verify(dao);
}

}

ใน code เราเริ่มด้วยการ สร้าง Mock ของ​ IUserDao
IUserDao dao = createStrictMock(IUserDao.class);

จากนั้นก็ train Mock ว่า เดี๋ยวในตอน test จะมีการเรียกใช้แบบนี้เกิดขึ้นนะ
expect(dao.getByLoginId("pok")).andReturn(createUser(true));
expect(dao.increaseFailCount()).andReturn(4);
dao.disableLogin("pok");
expect(dao.getByLoginId("pok")).andReturn(createUser(false));

จบการ train ด้วยคำสั่ง replay
replay(dao);

ที่เหลือก็เป็นการจำลองการเรียกใช้งาน AuthenService

สุดท้ายจบ testcase ด้วยคำสั่ง
verify(dao);

ซึ่งเป็นการตรวจว่าถ้า dao เราถูกเรียกไม่เรียงลำดับ หรือไม่ครบตามที่เรากำหนดไว้
testcase นั้นก็จะ error

ลองดูผลลัพท์จาก JCoverage หลังจาก test แล้ว



สถานะปัจจุบัน ตอนนี้ผมได้ feel ของการเขียน test กลับมาแล้ว
คือรู้สึกว่าติดและสนุกกับการเขียน Test

Related link from Roti

Friday, July 21, 2006

blog ภาษาอังกฤษ

เมื่อวันก่อนมีชาวต่างประเทศคนหนึ่งเขา comment ใน
http://pphetra.blogspot.com/2006/03/rails-s-date-widget-plugin.html#comments
อยากให้แปลเรื่องที่ถูกถึง Date picker ใน Rails
ให้เป็นภาษาอังกฤษหน่อย

ส่วนพรุ่งนี้ บริษัทแฟนอยากให้เข้าไปช่วย
brief เรื่อง java ให้กับ programmer ชาวจีินให้หน่อย

ได้เวลาฝึกพูดกับฝึกเขียนแล้ว

Related link from Roti

Tuesday, July 18, 2006

เดินทาง

ได้ link มาจาก BioLawcom
เรื่องนี้ไม่อ่านไม่ได้
chittawiwat.pdf

ผมใช้เวลาเดินทางจากเชียงใหม่เมื่อวันที่ 16 พฤษจิกายน 2548 ไปถึงเกาะสมุย เมื่อวันที่ 24 มกราคม 2549 ใช้เวลาทั้งหมด 66 วัน
การเดินทางในครั้งนี้ผมใช้เงื่อนไขหนึ่งคือ จะไม่เดินไปหาคนที่รู้จัก หรือถ้าคนรู้จักอยู่ที่ไหนก็จะหลีกไปใช้ทางอื่น
สองคือ จะไม่มีสตางค์ซึ่งเป็นสัญลักษณ์ของอำนาจติดตัวไปด้วย

Related link from Roti

Sunday, July 16, 2006

Dojo DateWidget in Thai

Dojo Widget ออกแบบมาดี
มีความเป็น object อยู่สูง
แถมยังมีลักษณะเป็น MVC ด้วย
ทำให้เราสามารถ customize ส่วน Presentation ได้โดยไม่ต้องยุ่งกับ Model

ในการทำ ThaiDateWidget
เราสนใจแค่การ override
function ที่ทำหน้าที่แสดงผล เดือน กับ ปี
dojo.provide("dojo.widget.html.ThaiDatePicker");
dojo.require("dojo.widget.html.DatePicker");


dojo.widget.defineWidget(
"dojo.widget.html.ThaiDatePicker",
dojo.widget.html.DatePicker,
{
thaimonths : ['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน',
'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม',
'กันยายน', 'ตุลาคม', 'พฤษจิกายน', 'ธันวาคม'],

setYearLabels: function(year) {
this.previousYearLabelNode.innerHTML = year - 1+543;
this.currentYearLabelNode.innerHTML = year+543;
this.nextYearLabelNode.innerHTML = year + 1+543;
},
setMonthLabel: function(monthIndex) {
this.monthLabelNode.innerHTML = this.thaimonths[monthIndex];
}
}

);

เวลาใช้งาน
<html>
<head>
<script type="text/javascript" src="./js/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.html.DatePicker");
dojo.require("dojo.widget.html.ThaiDatePicker");
</script>
</head>
<body>

<span dojoType="DatePicker" widgetId='test'></span>
<br/>
<span dojoType="ThaiDatePicker" widgetId='test2'</span>
</body>
</html>

ผลลัพท์ออกมาหน้าตาดังนี้

Related link from Roti

Better Builds with Maven

Better Builds with Maven
คือ Ebook ที่ผมใช้เป็นคู่มือในการใช้ Maven2
(เคยให้น้องที่บริษัทอ่าน ตอนให้อ่านก็บอกว่าอ่านสบายๆ
น้องเขาบอกว่า 200 กว่าหน้านี่นะ อ่านสบาย)

หลังจากใช้ maven2 มา 6 อาทิตย์
ได้ประสบการณ์รวมๆ มาดังนี้

  • ถ้าใช้ในองค์กร ก็ควรจะใช้ร่วมกับ Maven proxy
  • maven2 ยังไม่เหมาะกับ Project ที่ใช้ Cutting Edge library เท่าไร
    เพราะว่ามันจะไม่มี repository ให้ใช้
    ทำให้เราต้องเสียเวลาสร้าง repository เอง
  • กรณีที่ Project เรามีหลาย Module
    แต่วิธีการ assign งานของเรา เป็นแบบ End-to-End
    คือทำตั้งแต่ UI ยัน Model
    การจัดการ Project ใน IDE ที่เราใช้ จะซับซ้อนขึ้นทันตาเห็น
  • บาง Package ที่ pack อยู่ใน repository ยังไม่ได้มาตรฐาน
    เช่น สมมติเราใช้ Tapestry4.02
    แล้วเราต้องการใช้ Library X ซึ่ง dependency กับ Tapestry อยู่
    แต่ใน packaging ของมันกลับระบุให้มัน dependency กับ Tapestry4.0
    ผลลัพท์ก็คือ เราก็จะ dependency กับทั้ง 4.0 และ 4.02
    (ถ้า Library X pack ไว้ถูกต้อง มันควรจะต้องระบุว่ามัน depend กับ 4.0+)
  • ใช้งานร่วมกับ CruiseControl ได้โดยไม่มีปัญหา
  • feature Transitive Dependencies ช่วยให้ชีวิตง่ายขึ้นเยอะ

Related link from Roti