Jasypt: มาเข้ารหัส property value ใน properties file กัน

ปกติแล้วเวลาทำ app ขึ้นมาเราก็ต้องมีการเก็บข้อมูลจำพวก username, password ไว้ในไฟล์ properties ประมาณนี้

jdbc.driverClassName=org.h2.Driver
jdbc.url=jdbc:h2:tcp://localhost/~/appdb
jdbc.username=admin
jdbc.password=password

ซึ่งมันดูโหดมากเพราะเรากำลังเอา password ไปโชว์หร่าบน text ไฟล์กันโต้งๆ ดังนั้นถ้าข้อมูลประเภทที่ควรเป็นความลับก็ควรจะเข้ารหัสซักนิดเพื่อความปลอดภัยในชีวิตและทรัพย์สิน

สมมุติโจทย์

สมมุติโจทย์ให้ app เรา(ของเดิมนั้นแหละ) ต้องอ่านข้อมูล JDBC จาก [root]/cfg/jdbc.properties โดยให้ข้อมูลมี่เป็น password ต้องเข้ารหัสให้ไม่สามารถรู้ได้ว่า password คืออะไร

Code เดิมๆ

ถ้าเป็นแบบเดิมๆเราก็จะมี jdbc.properties เหมือนด้านบนและก็เขียน JdbcProperties ขึ้นมาเพื่ออ่าน properties ขึ้นมาประมาณนี้

package ninja.tumit.launch4jdemo.properties;

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class JdbcProperties {
 
    final static Logger log = LoggerFactory.getLogger(JdbcProperties.class);
    
    private static final String PROPERTIES_PATH = "../cfg";
    private static String PROPERTIES_FILE = "jdbc.properties";    
    private static JdbcProperties instance = null;
    private Properties prop = null;
    
    private JdbcProperties(){
        
        prop = new Properties();
        try {   
            File executorPath = new File(getExecutorPath());            
            String propertiesPath = executorPath.getParentFile().getAbsolutePath();              
            prop.load(new FileInputStream(propertiesPath+"/"+ PROPERTIES_PATH +"/" + PROPERTIES_FILE));          
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static String getExecutorPath() {
        return JdbcProperties.class.getProtectionDomain().getCodeSource().getLocation().getPath();
    }      
    
    public static synchronized JdbcProperties getInstance(){
        if (instance == null)
            instance = new JdbcProperties();
        return instance;
    }

    public String getValue(String key){
        return this.prop.getProperty(key);
    }
}

ในส่วนของ app ก็เรียกมาดูว่าอ่าน properties จาก [root]/cfg/jdbc.properties

package ninja.tumit.launch4jdemo;

import java.io.IOException;

import ninja.tumit.launch4jdemo.properties.JdbcProperties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Hello Launch4j!
 *
 */
public class App 
{
    
    Logger logger = LoggerFactory.getLogger(App.class);

    public void run() {
        try {
            
            String username = JdbcProperties.getInstance().getValue("jdbc.username");
            String password = JdbcProperties.getInstance().getValue("jdbc.password");
            
            logger.info( "JDBC: username={}, password={}",  username, password);
            
            System.in.read();            
        }catch(IOException e) {
            e.printStackTrace();
        }        
    }
        
    public static void main( String[] args ) {
        new App().run();
    }
}
แน่นอนมันควรจะอ่่านได้เรียบร้อยดี

Code แบบเข้ารห้ส

เอาละมาเข้าประเด็นของหัวข้อกัน 😛

ใส่ Jasypt เข้าไปในโปรเจค

แน่นอนอย่างแรกก็ต้องเป็น lib สำหรับช่วยเข้ารหัสก่อนเราใช้ lib ที่ชื่อ Jasypt (อ่านว่าอะไรฟ่ะ) ก่อนอื่ก็เอาเข้าไปใส่ใน pom.xml ก่อน

<dependency>
	<groupId>org.jasypt</groupId>
	<artifactId>jasypt</artifactId>
	<version>1.9.2</version>
</dependency>

เข้ารหัสในไฟล์ properties

ก่อนอื่นเราต้องมีตัวช่วยในการเข้ารหัสเพื่อเอา text ที่เข้ารหัสแล้วไปแปะที่ไฟล์ properties ซึ่งเรา download ได้จากนั้น unzip ซะแล้ว cmd เข้าไปเลย
cmd-jasypt-enc.exe

  • encrypt.bat – executor ตัวช่วย generate password ที่เข้ารหัส
  • input – password เดิมๆที่เราอยากให้เป็นความลับ
  • password – ตัวปั่นให้ password เดิมๆให้อ่านยากๆ
  • —- OUTPUT —-  – นี้แหละ text ที่เราจะเอาไปแปะแทน password เดิม

ว่าแล้วก็เอาไปแปะแทน password เดิมเลย แต่เพิ่ท ENC(xxxx) หน่อยเพื่อเป็นการบอกว่า text ชุดนี้เข้ารหัสนะ

jdbc.driverClassName=org.h2.Driver
jdbc.url=jdbc:h2:tcp://localhost/~/appdb
jdbc.username=admin
jdbc.password=ENC(4pCWOkm2Kwwa1tfM0x9maZMgfMqsdw0v)

เปลี่ยนตัวอ่าน properties

จาก code เดิมเราใช้คลาส Properties ในการโหลดแต่พอเราใช้ Jasypt ก็ต้องใช้ EncryptableProperties + StandardPBEStringEncryptor แทน

package ninja.tumit.launch4jdemo.properties;

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.properties.EncryptableProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class JdbcProperties {
 
    final static Logger log = LoggerFactory.getLogger(JdbcProperties.class);
    
    private static final String PROPERTIES_PATH = "../cfg";
    private static String PROPERTIES_FILE = "jdbc.properties";    
    private static JdbcProperties instance = null;
    private Properties prop = null;
    
    private JdbcProperties(){
        
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        encryptor.setPassword("MAKE_PASS");

        // prop = new Properties();
        prop = new EncryptableProperties(encryptor);        

        try {   
            File executorPath = new File(getExecutorPath());            
            String propertiesPath = executorPath.getParentFile().getAbsolutePath();              
            prop.load(new FileInputStream(propertiesPath+"/"+ PROPERTIES_PATH +"/" + PROPERTIES_FILE));          
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static String getExecutorPath() {
        return JdbcProperties.class.getProtectionDomain().getCodeSource().getLocation().getPath();
    }      
    
    public static synchronized JdbcProperties getInstance(){
        if (instance == null)
            instance = new JdbcProperties();
        return instance;
    }

    public String getValue(String key){
        return this.prop.getProperty(key);
    }
}
  • StandardPBEStringEncryptor – เราจะเอา MAKE_PASS(ตัวช่วยปั่น) ใส่ไปในออปเจคของคลาสนี้
  • EncryptableProperties – เปลี่ยนจาก Properties ธรรมดาเป็นตัวนี้แล้วเอา encryptor หยอดไป

กลับไป run App อีกทีนี้เราก็จะสามารถใช้ properties ที่เข้ารหัสได้เป็นปกติเหมือนไม่เคยเข้ารหัสมาก่อน

buid ซิ buid

อ้าว! ลืมก็อปไฟล์ properties
อ้าว! ลืมก็อปไฟล์ properties
launch4j-enc
ก็อปปี้แล้ว run โอเช

เขียนชวาแบบไม่ขัดจังหวะด้วย DCEVM

DCEVM คืออะไร

The Dynamic Code Evolution Virtual Machine (DCE VM) is a modification of the Java HotSpot(TM) VM that allows unlimited redefinition of loaded classes at runtime

พูดง่ายๆคือเป็นตัวเสริมการทำงานของ (J)VM ให้สามารถโหลดคลาสใหม่ ณ runtime ทำให้เราสามารถ แก้ไข code แล้วไม่ต้องกด compile ใหม่ run ใหม่

ถ้าจะให้เห็นภาพก็ ดูที่ Video นี้เลย

สังเกตุได้ว่าระหว่างที่โปรแกรม run อยู่พอแก้ code ปุ๊บ active ปั๊บ (แจ่ม) เอาละมาลองเริ่มเลย

โหลด DCE VM มาก่อน

ผมโหลด DCE VM จาก http://dcevm.github.io/ (ตัวที่ใช้ลองเป็น Java 7 update 51, build 3)

โหลด JDK ให้ตรงกับ DCE VM

เพื่อความปลอดภัย ผมเลือกใช้ JDK7u51 ตามที่ DCE VM บอกว่า support โหลดได้จาก Java SE 7 Archive Downloads แล้ว install ซะ

ก็อปปี้ JDK ไว้ทำ DEC VM แล้ว install ซะ

เพื่อความสบายใจผมก็อปปี้ jdk ที่ install ไปไว้อีกที่เพื่อใช้ install DEC VM จากนั้นก็ install เลย

dcevm
ก็อปปี้ไว้ D: ซะ เปลี่ยนชื่อซะหน่อย
dcevm-install
install ซะ
2014-11-11 19_24_49-Dynamic Code Evolution VM Installer
เลือก jdk ที่เราก็อปปี้แล้ว install DCEVM เสร็จแล้วจะขึ้นแบบนี้

สร้าง Project แล้วลองเลย

ผมสร้าง Java project ด้วย Maven บน Eclise และ set ให้ project ใช้ dcevm ที่เรา install ไว้

jre

จากนั้นก็ลอกโค้ดตาม Youtube เลย

package com.tumit.dcevmdemo;

/**
 * DCEVM Test!
 *
 */
public class App {
    public static void main(String[] args) throws InterruptedException{
               
        while(true){
            A a = new A();
            System.out.println(a.doStaff());
            Thread.sleep(1000);
        }
        
    }
}

class A {

    private static int x;
    
    public String doStaff() {
        return "A" + x++;
    }
    
}

ลอง run (Ctrl+F11) เลย อ้าว! ไม่เห็นเปลี่ยน

run1
อ้าว! แก้แล้วทำไมไม่โหลดให้ใหม่

ลองไปลองมาก็มา อ๋อ.. ต้อง run แบบ debug mode (F11) เอาใหม่ๆ

run2
แก้จาก A เป็น B แล้ว save อ้าาา ใช้ได้ๆ

สรุป… ก็โอนะ

หลังจากลองคร่าวๆน่าสนใจเลย ผมลอง(เล็กๆ)แก้โปรเจค struts2+spring+hibernate ก็ช่วย reload พวก class กับ application resource ได้ ถือว่าน่าสนใจพอที่จะลองไปเล่นดูครับ 😀

Matchers 1.1

หลังจากที่เรารู้คร่าวๆแล้วว่าการใช้ Matchers นั้นทำประมาณไหน ต่อไปเราจะมาลองดูตัวอย่างกัน โดยจะแบ่งตัว method ที่เป็น Matcher ออกมาเป็น 3 หมวดหมู่ Object, Core และ Logic มาลองดูกัน

หมวด Object

equalTo()
ทำงานเหมือน assertEqual() เลยครับ

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;

public class MyUnitTest {

	@Test
	public void testConcatenate() {
		MyUnit myUnit = new MyUnit();
		String result = myUnit.concatenate("one", "two");

		assertThat("onetwo", equalTo(result));
	}
}


instanceOf()

เป็น Matcher ไว้ Unit test ว่า object นั้นถูกสร้างมาจาก class ที่ระบุหรือเปล่า

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class MyUnitTest {

    @Test
    public void testGetTheObject() {
        MyUnit myUnit = new MyUnit();        
        assertThat(myUnit.getTheObject(),instanceOf(MyUnit.class));
    }
}

nullValue() + notNullValue()
ทำงานเหมือน assertNull() + assertNotNull() เลยครับ

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class MyUnitTest {

    @Test
    public void testGetTheObject() {
        MyUnit myUnit = new MyUnit();

        assertThat(myUnit.getTheObject(),nullValue());
        assertThat(myUnit.getTheObject(),notNullValue());
    }
}

sameInstance()
ทำงานเหมือน assertSame() เช่นกัน

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class MyUnitTest {

    @Test
    public void testGetTheObject() {
        MyUnit myUnit = new MyUnit();

        assertThat(myUnit.getTheObject(), sameInstance(myUnit.getTheObject()));

    }
}

WTF! ทำไม method ทำหน้าที่ซ้ำซ้อนกับ Junit เดิมอย่างงี้ฟ่ะ? ที่เป็นเช่นนั้นเพราะมันมาจาก framework คนละตัวทำให้มีมุมมองที่ต่างกัน ตัว JUnit คิดถึงการเทียบแบบตรงไปตรงมา ส่วน Hamcrest เป็นการคิดถึงการเอาผลลัพท์ไป match ด้วย Matcher ต่างๆ ซึ่งก็มีข้อดีแต่จะยังไงเรามาดูกันต่อ

หมวด Core

any()
คล้าย instanceOf() คือขอให้เป็น object ที่สร้างจาก class ที่ระบุโดยจะมีค่าเป็นไรก็ได้

ตัวอย่าง: ทำ Unit test ว่าค่าที่ส่งกลับมาเป็น String รวมถึง class แม่ก็คือ Object ด้วย

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class MyUnitTest {

    @Test
    public void testGetTheObject() {
        MyUnit myUnit = new MyUnit();

        assertThat(myUnit.getString(), any(String.class));
        assertThat(myUnit.getString(), any(Object.class));

    }
}


is()

ใช้ Unit test แทนได้ทั้ง equalTo() และ instanceOf()

เพื่ออะไร? ซ้ำกับ JUnit ยังพอเข้าใจ แต่ซ้ำซ้อนกับ method ของ framework ตัวเองมันยังไงๆอยู่?

จริงๆแล้ว Hamcrest มีความพยายามที่จะให้ code อ่านรู้เรื่องมากที่สุด(readable) เขาเรียกมันว่าน้ำตาล(Sugar) เรามาดูตัวอย่างเพื่อความเข้าใจมัน

ตัวอย่าง: ทำ Unit test ว่าค่าที่ส่งกลับมาเป็น String อะไรก็ได้

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class MyUnitTest {

    @Test
    public void testGetTheObject() {
        MyUnit myUnit = new MyUnit();
        String it = myUnit.getString();        

        // it is "Hello"        
        assertThat(it, is("Hello"));

        // it is String.class
        assertThat(it, is(String.class));
        
        // it is instance of String.class
        assertThat(it, is(instanceOf(String.class)));
                
        // it is any String.class
        assertThat(it, is(any(String.class)));        
    }
}

ตามโจทย์เราสามารถเขียนได้หลายวิธีตั้งแต่ใช้ instandOf(), any() แต่ is() จะมาช่วยสร้าง code เราให้เป็นประโยค(ภาษาอังกฤษ) เพิ่มขึ้น เช่นตัวอย่างนี้ ถ้าจะให้อ่านเป็นภาษาอังกฤษเพิ่มขึ้นเราก็จะใช้ assertThat(it, is(any(String.class))) = it is any String.class นั้นเอง

เนื่องจากมันเป็นน้ำตาลเท่านั้นโปรแกรมเมอร์บางท่านชอบกาแฟดำไม่ใส่น้ำตาลมากกว่าจะเลือกไม่ใส่ก็ได้ครับ 🙂

describedAs()
ใช้กรณีที่เราอยากใส่รายละเอียดของ Unit test มากเป็นพิเศษ
ตัวอย่าง: ทำ Unit test ว่าค่าที่ส่งกลับมาต้องไม่เท่ากับ null

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class MyUnitTest {

    @Test
    public void testGetUnitName() {
        MyUnit myUnit = new MyUnit();
        assertThat(myUnit.getUnitName(), is(notNullValue()));
    }
    
    @Test
    public void testGetUnitNameByDesc() {
        MyUnit myUnit = new MyUnit();
        assertThat(myUnit.getUnitName(), describedAs("Unit name is not null", is(notNullValue())));
    }    
}

และพอ run ตัว Unit test (บทความนี้ผม run ด้วย maven test)โดยสมมุติว่าได้ค่า getUnitName() ได้ null ทั้งคู่ เรามาดู log กัน

หน้าตา log ตอน FAILURE:

testGetUnitName(MyUnitTest)  Time elapsed: 0.009 sec  <<< FAILURE!
java.lang.AssertionError: 
Expected: is not null
     got: null
...
testGetUnitNameByDesc(MyUnitTest)  Time elapsed: 0.001 sec  <<< FAILURE!
java.lang.AssertionError: 
Expected: Unit name is not null
     got: null
...

สังเกตุว่า testGetUnitName() บอกแค่ is not null แต่ testGetUnitNameBydesc() พ่นรายละเอียดที่เราต้องการออกมาทาง log ให้ด้วย ซึ่งเหมาะกับการ Unit test งานที่ซับซ้อนหรือที่เข้าใจยาก

หมวด Logical

allOf()
ใช้เมื่อเราต้องการ Unit test โดยเทียบกับ Matcher หลายๆตัว โดยเงื่อนไขที่จะผ่านนั้นคือต้อง match กับทุก Matcher จึงจะผ่าน มองเหมือนเอา Matcher มา AND กันนั้นเอง
ตัวอย่าง: ทำ Unit test ว่า getUnitName() ต้องห้ามเป็น null และมี email format ที่ถูกต้อง และเป็นไปตามกฎของการตั้ง user name
ปล.ตัวอย่างนี้สมมุติเราทำ validEmailFormat() กับ validUsernameFormat ขึ้นมาเอง (วิธี Custom Matcher ดูได้จากบทที่แล้ว)

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;

public class MyUnitTest {
    
    @Test
    public void testAllOf(){
    	
        MyUnit myUnit = new MyUnit();
        assertThat(myUnit.getUnitName(), allOf(is(String.class),is(CustomMatcher.validEmailFormat()),is(CustomMatcher.validUsernameFormat())));    	
    }
    
}

หน้าตา log ตอน FAILURE:

testAllOf(MyUnitTest)  Time elapsed: 0.014 sec  <<< FAILURE!
java.lang.AssertionError: 
Expected: (is an instance of java.lang.String and is invalid email format and is username format)
     got: "a#b.com"

anyOf()
คล้าย allOf() ต่างกันตรงที่แค่ match กับ Matcher ตัวใดตัวหนึ่งก็ Unit test ผ่านแล้ว มองเหมือนเอา Matcher มา OR กัน
ตัวอย่าง: ทำ Unit test ว่า getUnitStatus() ต้องเป็น approve หรือ reject อย่างใดอย่างหนึ่ง
ปล.ตัวอย่างนี้สมมุติเราทำ approveStatus() กับ rejectStatusขึ้นมาเอง

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;

public class MyUnitTest {

    @Test
    public void testAnyOf(){
    	
        MyUnit myUnit = new MyUnit();
        assertThat(myUnit.getUnitStatus(), anyOf(is(CustomMatcher.approveStatus()),is(CustomMatcher.rejectStatus())));    	
    }    
    
}

หน้าตา log ตอน FAILURE:

testAnyOf(MyUnitTest)  Time elapsed: 0.014 sec  <<< FAILURE!
java.lang.AssertionError: 
Expected: (is approve status or is reject status)
     got: "new"

not()
ให้ผลตรงข้ามกับ Matchar ที่ส่งเข้ามา
ตัวอย่าง: ทำ Unit test ว่า getUnitStatus() ต้องไม่ใช่ approve
ปล.ย้ำอีกทีว่า approveStatus() ไม่มีใน Hamcrest นะครับเป็น Matcher ที่ Custom เองเพื่อใช้ในตัวอย่าง

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;

public class MyUnitTest {

    @Test
    public void testAnyOf(){
    	
        MyUnit myUnit = new MyUnit();
        assertThat(myUnit.getUnitStatus(), is(not(CustomMatcher.approveStatus())));    	
    }    
    
}

หน้าตา log ตอน FAILURE:

testNot(MyUnitTest)  Time elapsed: 0.009 sec  <<< FAILURE!
java.lang.AssertionError: 
Expected: is not approve status
     got: "approve"

มาถึงตรงนี้เราคงพอเข้าใจคร่าวๆแล้วว่าเราจะเล่นกับตัว assertThat กับ Matcher ยังไง ต่อไปน่าจะแปล Testing for Exceptions By Jakob Jenkov ต่อนะครับ 🙂

ปล. ผู้เขียนอ่านไป แปลไป เขียนไป ผิดถูกยังไงแนะนำมาได้นะครับ

Assert Methods

สำหรับคนที่เคยลองเขียน Unit test มาบ้างแล้ว เคล็ดลับสำคัญของการเขียน Unit test ด้วย JUnit คือ “การเลือกใช้ assert แบบต่างๆที่มีอยู่ใน class org.junit.Assert” ในบทความนี้เราจะลงลึกไปดูซิว่ามี assert อะไรบ้างที่เราสามารถนำมาใช้ให้เหมาะกับการทำ Unit test ของเรา

รายชื่อ assert methods ที่เราจะมาเล่นกัน

  • assertArrayEquals()
  • assertEquals()
  • assertTrue() + assertFalse()
  • assertNull() + assertNotNull()
  • assertSame() + assertNotSame()
  • assertThat()

หลังจากที่เรารู้ว่ามี assert อะไรให้ใช้บ้างแล้ว เราก็มาเจาะดูซิว่าในแต่ละตัวมันทำงานยังไงและเราจะเรียกใช้มันแบบไหน โดยตัวอย่างนี้จะจำลอง class ขึ้นมาหนึ่งตัวชื่อ MyUnit ซึ่งเราไม่สนใจมันว่าใส้มันเขียนยังไง เราจะเน้นว่าถ้ามี method ใน class นี้เรา Unit test มันยังไงบ้างพอ

assertArrayEquals()

สำหรับ method นี้มีไว้สำหรับทดสอบ array 2 ตัวว่า “เท่ากัน” หรือไม่ โดยคำว่า “เท่ากัน” นี้หมายถึง array 2 ตัวนี้ต้องมี “จำนวนสมาชิก” เท่ากัน และ “สมาชิกในแต่ละตำแหน่งต้องมีค่าเท่ากัน” ด้วย

ในการทดสอบว่า “สมาชิกในแต่ละตำแหน่งต้องมีค่าเท่ากัน” หรือไม่นั้น สมาชิกจะถูกทดสอบด้วย method ที่ชื่อว่า equals โดยจะทดสอบตัวต่อตัวในแต่ละตำแหน่งของสมาชิกใน array นั้นหมายความว่าไม่ใช่แค่มีสมาชิกเท่ากันเท่านั้น แต่ลำดับของสมาชิกต้องตรงกันเป๊ะด้วย

ตัวอย่าง: ต้องการทำ Unit test กับ method ชื่อ getTheStringArray() ว่า return ค่าเท่ากับ {“one”, “two”, “three”} ไหม

import org.junit.Test;
import static org.junit.Assert.*;

public class MyUnitTest {

    @Test
    public void testGetTheStringArray() {
        MyUnit myUnit = new MyUnit();

        String[] expectedArray = {"one", "two", "three"};

        String[] resultArray =  myUnit.getTheStringArray();

        assertArrayEquals(expectedArray, resultArray);
    }
}

อธิบาย code:
1. สร้าง array ชื่อ expectedArray
ที่มีข้อมูลตามที่เราคาดหวังไว้คือ {“one”, “two”, “three”}
2. เรียก getTheStringArray() แล้วเก็บค่าใส่ตัวแปรชื่อ resultArray
3. นำตัวแปรทั้งคู่มาเข้า assertArrayEquals(expectedArray, resultArray) เพื่อทดสอบว่าผ่าน Unit test หรือไม่

ซึ่งถ้า array ทั้งคู่มีค่าเท่ากัน process การ Unit test ก็จะผ่านไปได้โดยไม่มีอะไร แต่ถ้าเกิดไม่เท่ากันก็จะมีเกิด exception และหยุดการ Unit test ใน testGetTheStringArray() ไป

assertEquals()

เป็นการ Unit test ระหว่าง object กับ object โดยผ่านการเรียก equals()
ตัวอย่าง: ทำการ Unit test ตัว concatenate() ว่าสามารถต่อ String กันได้ถูกต้องไหม

import org.junit.Test;
import static org.junit.Assert.*;

public class MyUnitTest {

    @Test
    public void testConcatenate() {
        MyUnit myUnit = new MyUnit();

        String result = myUnit.concatenate("one", "two");

        assertEquals("onetwo", result);
    }
}

อธิบาย code:
1. เรียก concatenate(“one”, “two”) ให้ผลลัพท์เก็บไว้ที่ตัวแปร result
2. นำตัวแปรทั้งคู่มาเข้า assertEquals(“onetwo”, result) เพื่อทดสอบว่าผ่าน Unit test หรือไม่

เหมือนกับ assertArray ถ้าทั้งคู่มีค่าเท่ากัน process การ Unit test ก็จะผ่านไปได้ ถ้าไม่ก็เกิด exception จากตัวอย่างนี้เป็นการเปรียบเทียบ object ของ String แต่จริงๆแล้วตัว assertEquals สามารถเปรียบเทียบกับ object ของ class ใดๆก็ได้รวมไปถึงพวก primitive เช่นพวก int หรือ float ก็ทำได้เช่นกัน

assertTrue() + assertFalse()
เป็น method สำหรับทำ Unit test แบบใช้ตัวแปรตัวเดียวว่ามันเป็น true หรือ false

ตัวอย่าง: ทำการ Unit test ตัว getTheBoolean()

import org.junit.Test;
import static org.junit.Assert.*;

public class MyUnitTest {

    @Test
    public void testGetTheBoolean() {
        MyUnit myUnit = new MyUnit();

        assertTrue (myUnit.getTheBoolean());

        assertFalse(myUnit.getTheBoolean());
    }
}

อธิบาย code:
จะเห็นได้ว่าเราส่งผลลัพท์จาก myUnit.getTheBollean() ในตัว assertTrue() และ assertFalse() โดยตรง ซึ่งถ้า getTheBoolean() ได้ค่าเป็น true ตัว assertTrue() ก็จะผ่าน แต่ถ้าเป็น false ตัว assertFalse() ก็จะผ่าน แต่ถ้าดูที่ตัวอย่างนี้จะเห็นว่า Unit test ยังไงก็ไม่ผ่านเพราะมันจะติดที่ assertTrue() หรือ assertFalse() ไม่จุดใดก็จุดนึง

assertNull() + assertNotNull()

เป็น method สำหรับทำ Unit test แบบใช้ตัวแปรตัวเดียวอีกเช่นกัน โดยเราจะสนใจว่ามันเป็น null หรือไม่

ตัวอย่าง: ทำการ Unit test ตัว getTheObject()

import org.junit.Test;
import static org.junit.Assert.*;

public class MyUnitTest {

    @Test
    public void testGetTheObject() {
        MyUnit myUnit = new MyUnit();

        assertNull(myUnit.getTheObject());

        assertNotNull(myUnit.getTheObject());
    }
}

อธิบาย code:
คล้ายกับ assertTrue() + assertFalse() จะเห็นได้ว่าเราส่งผลลัพท์จาก myUnit.getTheObject() ในตัว assertNull() และ assertNotNull() โดยตรง ซึ่งถ้า getTheObject() ได้ค่าเป็น null ตัว assertNull() ก็จะผ่าน แต่ถ้าไม่เท่ากับ null ตัว assertNotNull() ก็จะผ่าน แต่ถ้าดูที่ตัวอย่างนี้จะเห็นว่า Unit test ยังไงก็ไม่ผ่านเพราะมันจะติดที่ assertNull() หรือ assertNotNull() ไม่จุดใดก็จุดนึง

assertSame() + assertNotSame()
สำหรับ method สองตัวนี้มีได้สำหรับทำ Unit test ว่า reference ทั้งสองตัวนี้ชี้ไปที่ object ตัวเดียวกันหรือไม่ ซึ่งมันไม่เหมือนกับ assertEquals() ซึ่งเทียบกันด้วย equals() แต่ method นี้ต้องเป็นตัวเดียวกันจริงๆเท่านั้น

ตัวอย่าง: ทำการ Unit test ตัว getTheSameObject()

import org.junit.Test;
import static org.junit.Assert.*;

public class MyUnitTest {

    @Test
    public void testGetTheSameObject() {
        MyUnit myUnit = new MyUnit();

        assertSame   (myUnit.getTheSameObject(),
                      myUnit.getTheSameObject());

        assertNotSame(myUnit.getTheSameObject(),
                      myUnit.getTheSameObject());
    }
}

อธิบาย code:
เราเรียก myUnit.getTheSameObject() ลงไปตรงๆที่ assertSame() และ assertNotSame() ถ้า reference ทั้งสองชี้ไปยัง object ตัวเดียวกัน assertSame() จะผ่านปกติถ้าไม่ก็จะเกิด exception ส่วน assertNotSame() ก็จะทำงานตรงกันข้าม


assertThat()

เป็น method ที่ใช้เปรียบเทียบระหว่าง object กับ org.hamcrest.Matcher โดย Unit test จะผ่านเมื่อ object นั้นต้อง match กับ Matcher ที่เราเลือก

ตัว Matcher อาจจะต้องใช้พื้นที่อธิบายกว้างซักนิด ดังนั้นเราจะอธิบายมันอีกทีในบทความหน้าครับ

แปลจาก: Assert Methods by Jakob Jenkov

ข้อแตกต่างระหว่าง java กับ javaw

ข้อแตกต่างระหว่าง java กับ javaw

เวลาเราเปิดโปรแกรมที่เป็น Java แล้วเป็น Task Manager เพื่อดู Process บ้างครั้งเราจะเป็นมี Process ชื่อ java.exe บ้าง javaw.exe บ้าง ถ้าไปดูที่ JAVA_HOME/bin ก็จะเห็นว่ามีทั้ง java.exe และ javaw.exe แล้วข้อแตกต่างระหว่างสองตัวนี้คืออะไร?

java vs javaw
จริงๆแล้วทั้ง java.exe และ javaw.exe สามารถ run โปรแกรม Java ใน jar ได้ทั้งคู่ สิ่งที่แตกต่างเพียงอย่างเดียวคือ java.exe จะมี console เวลา execute และจะต้องรอจน Java นั้น run จนเสร็จ console นั้นจึงจะกลับมาใช้ได้ ส่วน javaw.exe ไม่มี console
เราสามารถใช้อันไหนก็ได้ตามสถานะการณ์เช่นควรใช้ javaw เมื่อเราไม่ต้องการให้โชว์ console และถ้ามี error มันจะ popup ข้อความมาทาง dialog

สรุป
ข้อแตกต่างก็คือ java.exe ถ้า run แล้วมี console และ javaw.exe run แล้วจะไม่มี console

ปล. ผมเคยต้อง run JBoss หลาย instance บนเครื่อง test ทำให้งงเรื่อง process มากว่าอันไหนเป็นอันไหน(java.exe ทุกอันเลย) ผมเลยแก้ปัญหาด้วยการ copy java.exe แล้วแก้ชื่อเป็น jb01.exe, jb02.exe … แล้วเวลา run JBoss แต่ละ instance ก็เปลี่ยนจาก java เป็นชื่อที่กำหนด เราก็สามารถดูได้ว่า process ไหนเป็นของ instance ไหนครับ

อ้างอิง: Difference between java and javaw executable commands

Eclipse Tip ตอน TODO บันทึกช่วยจำ กับ Warning message เจ้าปัญหา

กลับมา blog สั้นอีกครั้งหลังจากดองไว้นาน… วันนี้เกิดรู้สึกว่าอยากใช้ Eclipse ให้เต็มประสิทธิภาพมากกว่าทุกวันที่ไว้ขึ้น project กับ compile เท่านั้น วันนี้เลยมี  tip เล็กๆ จดไว้กันลืม

TODO บันทึกช่วยจำ หลายครั้งที่เราเขียน code แบบวางโครงสร้างไว้ก่อนแล้วค่อยเขียน code จริงตาม ใน eclipse มี keyword ที่ไว้ช่วยคือคำว่า TODO ตัวอย่างเช่น

แล้วรายการ TODO ที่เราม mark ไว้ก็จะมาโชว์ที่  Markers ดังนี้

Warning message เจ้าปัญหา เวลาเราสร้าง project ขึ้นมาโดย eclipse แล้วเราเพิ่ม lib แบบอ้างอิงไม่ได้ใส่ไว้ใน project มักจะเกิด message คอยตอนว่ามี classpath ที่จะไม่มีเวลาเอาไปลงจริงๆนะหรือบ้างครั้งเราได้ html มาจากเว็บ design แล้ว validate ตามมาตรฐานไม่ผ่าน(แต่ทำงานได้ถูกต้องแล้ว)

ซึ่งเราสามารถปิด message เหล่านี้ได้โดย unchecked ชนิดของ message ที่เราต้องการให้ไม่ต้องแสดงซะ

มันก็จะเหลือเฉพาะ task ที่เราสนใจแล้ว 🙂

จบ

Log4j กับ Event Viewer

ปกติแล้ว JBoss จะ log ด้วย Log4j ซึ่ง Log4j ก็ใจดีทำ NTEventLogAppender.dll ไว้ให้โดยสามารถทำ Log ได้ภายใต้ Application โดยสามารถกำหนด Source ได้

แต่ ! บางครั้ง(คุณที่คุณรู้ใคร) อยากได้มากกว่านั้นคือ “สร้างแยกออกมาให้กรูหน่อยได้ไหม คือก็จะเอาแบบนี้ IE ยังทำได้เลย”

เอาแล้วไง เดือดร้อนขึ้นมาทันที แต่ทุกปัญหามีทางออก โดยเฉพาะทางที่เป็น Open Source !! คุณ Log4j ให้ code ที่ใช้เขียนทำงานกับ Event Viewer มาด้วย ดังนั้นเราจึงมีหลักง่ายๆคือ “เอา Source code NTEventLogAppender.dll มา build ใหม่ด้วยชื่อ Event ตามที่เราต้องการ”

1. Download ตัว Log4j มาก่อน(ตัวอย่างนี้ใช้ log4j-1.2.16)ซึ่งจะได้มาทั้ง jar และ source code

2. Unzip แล้วหา Source code ส่วนของ NTEventLogAppender.dll คือ ntdll แต่.. Code ที่ใช้ทำ .dll คงไม่ใช่ Java ดูแล้วเป็น .cpp ครับ ดังนั้นต่อมาคือเราต้องต้องไปโหลด Compiler มาก่อน

3. ตัวที่เลือกมาคือ MinGW โหลดมาแล้ว Install ด้วย “Next Technology” เลย อ้อ!! อย่าลืมเลือก g++ ด้วย

4. Set PATH ให้ MinGW ให้เรียบร้อย อ้อ อย่าลืม Set ของ Ant ด้วยนะ

5. ลอง Compile ดูว่ารอดไหม ? อ้อ!! อย่าลืมใส่ log4j.jar ใน Class Path โดยอาจจะทำไฟล์ build.bat อย่างนี้

ant build -Dclasses.dir=D:\Java\apache-log4j-1.2.16\apache-log4j-1.2.16\log4j-1.2.16.jar

6. สมมุติว่า รอด ก็ทำการแก้ไขไฟล์ nteventlog.cpp โดยแก้คำว่า “\\Application” เป็นชื่อที่เราต้องการเช่น “\\JBoss Application Server”(เวอร์ชั่นนี้แก้ 3 จุด)

7. Compile อีกที ถ้าไม่ซนไปแก้อย่างอื่นก็น่าจะรอดตามๆกันมา

8. ต่อมาก็เอา .dll สดๆออกจากเตาไปวางในที่เหมาะที่ควร system32 พร้อมอุ่นด้วย

regsvr32 "c:\windows\system32\NTEventLogAppender.dll"

9. ไปแก้ jboss-log4j.xml เพื่อกำหนด Source ที่เราทำ

10. ก็จะได้ Event Viewer ตามใจเราแล้ว สาธุ..