Angular ว่าด้วยเรื่อง Build

 bb

Working Software คือ เป้าหมาย

สำหรับการพัฒนา Software มันจะมีขั้นตอนในการแปลง source code และ resources ต่างๆให้ออกมาเป็น “ของ” ที่พร้อมที่จะ deploy เป็นระบบที่ทำงานได้จริง (Working Software) เราเรียกกระบวนการนี้ว่า การ “build” ซึ่งไอ้การ build แต่ละครั้งเราก็มีเหตุผลที่ต่างกันไปเช่น เพื่อทดสอบระหว่างการพัฒนา เพื่อให้ QA ทดสอบ UAT หรือ Go live ใช้งานจริงๆ

สำหรับ Angular เรานิยมใช้ Angular CLI เพื่อช่วยทั้งสร้างโปรเจค สร้าง source code และแน่นอนรวมทั้ง build ของด้วย เราจะไปดูว่ามันจะช่วยเรา build ได้แบบไหน ยังไงบ้าง 🙂

แล้ว Angular CLI มี build แบบไหนบ้าง ?

ปกติเราจะ build ตาม environment ต่างๆกันไปตามที่ออกแบบ เช่นอาจจะแบ่งเป็น alpha, beta, prod แต่ถ้าจะให้แยกตามลักษณะ output ที่ออกมาจะแยกออกเป็น 2 แบบคือแบบ dev กับ prod การทดลองนี้จะลอง ng new ngdemo โง่ๆขึ้นมาตัวนึง แล้วมาลอง build ดูกันว่าจะได้ผลกันยังไง

Build สำหรับการพัฒนาระบบ

ng buld —dev

Screen Shot 2560-06-19 at 11.29.53 PM

Screen Shot 2560-06-19 at 11.32.53 PM

เวลาเรา build ด้วย option นี้ เราจะได้ประโยชน์คือเราจะได้ source mapping มาด้วย (สังเกตุขนาดไฟล์ vendor.bundle.js ที่ได้มาหลัก MB เลย) ดังนั้นเวลาเราพัฒนาแล้วเกิดเจอ error อะไรมันก็ยังกลับจุดทีมีปัญหาได้ว่ามาจากตรงไหนไฟล์ไหนนั้นเอง

Build สำหรับใช้งานจริง

ng build —prod

Screen Shot 2560-06-19 at 11.34.59 PM

Screen Shot 2560-06-19 at 11.35.26 PM

สิ่งแรกที่อยากให้สังเกตคือจะเห็นว่าขนาดไฟล์ลดขนาดไปพอควร โดยเฉพาะ vendor.xxx.bundle.js ลดลงจากหลัก MB ไปหลัก 3xx kB เพราะการ build ด้วย option นี้จะตัดสวนของ source mapping ออกไปนั้นเอง

อย่างที่สองที่อยากชวนดูคือ ชื่อไฟล์ มันจะมีตัวอักษรแปลกๆแปะมาระหว่างชื่อไฟล์ ตัวอักษรแปลกๆพวกนี้คือ hashing ที่ได้จากไฟล์นั้นๆ ถ้าไม่มีการแก้ไขในไฟล์ hash ที่ได้ก็จะได้เลขเดิมเสมอ ซึ่งมันจะช่วยในเรื่อง browser caching นั้นเอง

ต่อมาที่น่าสนใจคือ styles.bundle.js ถูก extract ออกมากลายมาเป็น styles.xxx.bundle.css แทน ก็เพื่อใช้แสดงผลตามปกติของ css ที่มันควรจะเป็น

นอกจากนี้เวลา build แบบ prod นี้ยังมีเรื่องการ Ahead-of-Time Compilation อีก ดังนั้นเวลาเราต้องการ build ของสำหรับใช้งานจริงควรจะใช้ option นี้

ตารางเปรียบเทียบ –dev กับ –prod

Flag --dev --prod
--aot false true
--environment dev prod
--output-hashing media all
--sourcemaps true false
--extract-css false true

สรุป

ก็น่าจะเห็นภาพมาขึ้นสำหรับเรื่องการ build โปรเจคทั้ง 2 แบบด้วย ng build ของ Angular CLI เหมือนเดิมครับ ถ้าใครมีเทคนิคแจ่มๆเอามาแบ่งกันได้นะครับ สวัสดีครับ (-/\-)

อ้างอิง : https://github.com/angular/angular-cli/wiki/build

Advertisements

เพิ่มความสนุกในการเขียน Angular ด้วย HMR (Hot Module Replacement)

299673_10151471144320809_1119982299_n

ปัญหาในการพัฒนา Angular

ผมเคยฟัง คุณจั๊ว ในหัวข้อ Hyper Productivity ด้วยสมองผมที่มีรอยหยักจำกัดเลยจำอะไรไม่ค่อยได้มากนัก แต่สิ่งที่จำได้ลางๆ คือ

การทำงานมันจะอยู่ในสภาวะไหลลื่น(Flow) นั้นการตอบสนอง(Feedback loop) ต้องสั้นทันใจ

การพัฒนาระบบก็เหมือนกันถ้าเรารู้ว่าสิ่งที่เราแก้ไขไปมันถูกต้องได้ไวขึ้นมันก็น่าจะทำให้ Productivity ดีขึ้นด้วย กลับมาที่ Angular ปัญหาอย่างนึงที่พบเวลาพัฒนาคือเวลาเราแก้ไขอะไรแม้จะเล็กน้อยมันจะต้อง reload ทั้งหน้า ซึ่งปัญหานี้แหละครับที่เราจะเอาเจ้า HMR มาช่วย

เริ่มที่ CLI

ในบทความนี้จะเป็นการใช้ HMR ร่วมกับเครื่องมือยอดนิยมของ Angular นั้นก็คือ Angular CLI นั้นเอง ดังนั้นเริ่มเลยครับ

ng new hmr-demo

hmr-demo-02

หลังจากนั้นก็แวะไปดื่มกาแฟ เข้าห้องน้ำ ดูหน้ากากนักร้องย้อนหลังไปซักแป๊บ ก็จะได้โปรเจค hmr-demo ที่พร้อมว่าแล้วก็ลอง run ซักดอกนึงก่อนครับว่ามันโอเคไหมด้วย

ng serve

hmr-demo-01
โอเคดูเรียบร้อยดีต่อมาเราจะมาเริ่ม configuration กัน

Environment สำหรับ HMR

ปกติเวลาเราพัฒนาระบบเราก็จะมีการแยก environment เป็นไปตามเงื่อนไขต่างๆกันเเช่นเป็น dev, uat หรือ prod ตัว HMR เราก็จะแยก environment ออกมาเหมือนกัน เพราะถ้าเราไป Hot Module replace กับที่ prod ก็อาจจะไม่เหมาะเท่าไหร่ โอเคเรามาเริ่มกัน เริ่มจากสร้างไฟล์ environment.hmr.ts แล้วเพิ่มส่วนของ hmr ลงไปเป็น true อย่างงี้

hmr-demo-03

ส่วน environment อื่นๆที่ไม่ใช่ hmr เราก็จะ set เป็น false ให้หมดแบบนี้เป็นต้น

hmr-demo-04

ต่อมาส่วนที่เราจะไป set เพิ่มเวลาเราเพิ่ม environment ขึ้นมาก็คือ .angular-cli.json ก็ตามนี้เลย

hmr-demo-05

Install ตัว HMR

ต่อมาก่อนที่เราจะใช้ HMR ได้เราก็ต้อง Install มันก่อนก็จาก

npm install --save-dev @angularclass/hmr

จากนั้นเราก็ไปสร้างไฟล์ที่ hmr.ts เพื่อ configuration ตัว HMR กันก็ตามนี้เลย

// src/hmr.ts
import { NgModuleRef, ApplicationRef } from '@angular/core';
import { createNewHosts } from '@angularclass/hmr';

export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => {
  let ngModule: NgModuleRef<any>;
  module.hot.accept();
  bootstrap().then(mod => ngModule = mod);
  module.hot.dispose(() => {
    let appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);
    let elements = appRef.components.map(c => c.location.nativeElement);
    let makeVisible = createNewHosts(elements);
    ngModule.destroy();
    makeVisible();
  });
};

hmr-demo-06

เสร็จแล้วก็ไป set ใน main.ts อีกนิดเพื่อให้ตัว HMR ขึ้นมาทำงานในกรณีที่เรา run ด้วย hmr environment

// src/main.ts
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { hmrBootstrap } from './hmr';

if (environment.production) {
  enableProdMode();
}

const bootstrap = () => {
  return platformBrowserDynamic().bootstrapModule(AppModule);
};

if (environment.hmr) {
  if (module['hot']) {
    hmrBootstrap(module, bootstrap);
  } else {
    console.error('HMR is not enabled for webpack-dev-server!');
    console.log('Are you using the --hmr flag for ng serve?');
  }
} else {
  bootstrap();
}

hmr-demo-07
เมื่อเรียบร้อยดีแล้วก็ลองเลยครับ

ng serve --environment=hmr --hmr

ซึ่งถ้าตัว HMR มันทำงานก็จะขึ้นมาบอกประมาณนี้ Hot Module Replacement (HMR) is enabled for the dev server.
hmr-demo-08

พอเราแก้ไข code เช่นแก้ title เป็น Angular Developers Thailand ก็จะเปลี่ยนโดยไม่ต้อง reload ใหม่ทั้งหมด
hmr-demo-09

สรุป

ต้องออกตัวว่ายังไม่เคยลองใช้ในโปรเจคตัวเอง(แต่จะเอาไปลองเดี๋ยวนี้แหละ) แต่มีโปรเจคข้างๆของ คุณเน็ต กูโค้ดใช้อยู่ (ซึ่งก็เป็นคนเอาตัว HMR มาขายผม) แต่ดูแล้วตัว HMR จะทำให้การพัฒนาระบบด้วย Angular รวดเร็วและสนุกขึ้น ถ้าใครลองใช้กันแล้วชอบหรือติดอะไรยังมาแชร์กันได้นะครับ (-/\-)

จบปิ๊ง

เนื้อหาต้นฉบับ: Tutorial: Enable HMR in angular-cli apps

เขียนชวาแบบไม่ขัดจังหวะด้วย 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 ได้ ถือว่าน่าสนใจพอที่จะลองไปเล่นดูครับ 😀

Geeky Academy — ตอนที่ 1 Git 4 Geeks

สรุปโดยรวม

  • สร้างจาก requirements 3 อย่าง คือ ต้อง distributed, เร็ว, ทำงานได้ถูกต้อง

  • distributed คือสามารถทำงานแบบใครอยาก commit ก็ commit ใครอยาก branch ก็ branch ไม่ depend ซึ่งกันละกัน

  • พอทำเสร็จอยาก public ก็ค่อย push ขึ้น share repository(เช่น GitHub, Bitbucket เป็นต้น)

  • ใครอยากทำต่อก็ clone มาได้แต่ถ้าจะ push กลับ เจ้าของ repo ต้องอนุมัติก่อน

  • ถ้าเจ้าของไม่อนุมัติแต่เราเห็นว่าของเราก็โอเคนะ ก็ fork ออกมา เราก็จะเป็นเจ้าของเอง(Open source concept)

ประเด็นที่มันแตกต่างกับ centralizeก อย่างชัดเจนคือ

  • ทำงานกับเครื่องตัวเองก่อนได้ commit แตก branch ไปได้เรื่อยๆ ไม่ impact ใคร

  • ก่อนเอาไป public แต่มี command ช่วยตกแต่งว่าจะเอาเฉพาะบาง commit ขึ้น หรือแก้ message ให้ meaningful ก่อนได้

  • ไม่เหมือน centralized ที่ทุก commit จะแสดงหมด ช่วยให้เวลาไล่ change ไม่ต้องเสียเวลาดู message ที่ไม่จำเป็น

Slide

Git 4 Geeks@dean4j


เรื่องมันมีอยู่ว่าเมื่อวานคุณบีเมลมาถามว่า "อาทิตย์ที่แล้วเรียนเป็นไงบ้างอะ มา share บ้างดิ อยากรู้" เลยถือโอกาสนั่งทบทวนอีกที่ว่าจับใจความเรื่องที่เรียนมาว่าได้อะไรไปบ้าง เลยอยากแชร์ให้เพื่อนๆกับ Trainer Salah Chalermthai อ่านดูว่าที่เข้าใจนี้ตรงกันไหมนะครับ

อีกอย่างอยากให้เพื่อนลองเขียนต่อมาให้อ่านบ้างว่าจับอะไรได้ตรงไหนกันบ้างจะได้แชร์ๆกันเนาะ

ปล. ถ้าอันไหนเข้าใจผิดแย้งมาได้เลยนะครับ…

tumit @ Geeky Academy page — July 3 at 4:28pm

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