General

บทความทั่วไป ไม่จัดอยู่กลุ่มใหนเลย

I would like to check in

– Vocabulary

a double room (ห้องนอนคู่ 1 เตียง)a family room (ห้องนอนแบบครอบครัว)a beach front room (ห้องนอนติดชายหาด)
a twin room (ห้องนอนเตียงเดียว 2 เตียง)a triple room (ห้องนอนเตียงเดียว ผสมคู่ เพื่อนอน 3 คน)a single room (ห้องนอนเตียงเดียว นอนคนเดียว)
an interconnection room (ห้องนอนที่มีทางเชื่อมถึงกัน)a suite (ห้องนอนแบบประตูเชื่อมถึงกัน มีพื้นที่ทำงาน)a duplex room (ห้องนอนแบบมีชั้นลอย)

– Language Focus: Useful Hotel Pharses

ReceptionistGuest
…[greeting]… welcome to …[hotel]…
How may i help you?
I’m checking in. The name is …
I would like to check in please. My name is …
May I see your ID, please?
May I have your passport, please?
Yes, here you are.
Of course, here you go.
That’s…[room]…for …[number]… nights?
Example: That’s a twin room for 2 nights.
That’s right.
I’m sorry. I booked for … [2 nights]…
May I have your credit card, please?
Do you have a credit card?
Yes, here you are.
Of course, here you go.
Could you fill out this form, please?
Could you sign here, please?
Yes, sure
Certainly
You room number is …. on the … floor.
You’re in room … on the … floor.
Would you like the bellboy to take your bags for you?
Thank you.

Flutter จัดการ flutter SDK versions ด้วย fvm (Flutter Version Management) EP3

เมื่อไม่กี่วันที่ผ่านมา ทาง google ได้ประกาศ flutter 2 เป็น stable version แล้ว ซึ่งจะสามารถ run ใช้งานที่ platform web ได้แบบสมบูรณ์ ทำให้นักพัฒนาเกิดความอยากได้อยากลองที่จะ upgrade flutter version จากเดิมเวอร์ชั่น 1.22.6 สุดท้าย ขยับไปเป็น 2.0.1 ซึ่งแน่นอนว่า โปรเจคเก่า ๆ ที่พัฒนาไว้แล้ว บางโปรเจคจะไม่สามารถรันที่ flutter version 2 ได้ (เวอร์ชัน 2 จะ deprecated บาง feature จะไม่สามรถทำงานได้ที่ เวอร์ชั่น 2)

สำหรับการที่จะทำการติดตั้ง flutter ทั้ง เวอร์ชัน 1 และ 2 ในเครื่องเดียว พร้อมทั้ง set FLUTTER_HOME เพื่อให้ editor ต่าง ๆ เช่น VSCode ,Android Studio ให้สามารถเข้าถึง path flutter ได้ ก็ไม่สะดวกในการที่จะใช้งาน

FVM (Flutter Version Management: A simple cli to manage Flutter SDK versions.)

cli ที่จะช่วยที่นักพัฒนาเลือกใช้ flutter sdk ได้สะดวก และง่ายยิ่งขึ้นด้วยการ สลับ sdk version ง่าย ๆ ใน command เดียว

คุณสมบัติของ FVM

  • กำหนดค่าและใช้เวอร์ชัน Flutter SDK ต่อโปรเจ็กต์
  • ความสามารถในการติดตั้งและแคช Flutter SDK หลายเวอร์ชัน
  • สลับระหว่างช่องและเวอร์ชันของ Flutter ได้อย่างรวดเร็ว
  • พาธ dynamic SDK สำหรับการสนับสนุนการดีบัก IDE
  • กำหนดค่าเวอร์ชัน FVM พร้อมโปรเจ็กต์เพื่อความสอดคล้องกันระหว่างทีมและสภาพแวดล้อม CI
  • ตั้งค่าเวอร์ชัน flutter sdk global ในโปรเจ็กต์ต่างๆ

ติดตั้ง FVM

  • ติดตั้งผ่าน pub (package manager for the Dart programming language)
$ pub global activate fvm

*** Window OS ให้ทำการ set environment PATH \Users\poolsawat\AppData\Local\Pub\Cache\bin เพื่อให้สามารถเรียก fvm cli แบบ global ได้

  • fvm help เช็ค options ต่างๆ
$ fvm help
Flutter Version Management: A cli to manage Flutter SDK versions.

Usage: fvm <command> [arguments]

Global options:
-h, --help       Print this usage information.
    --verbose    Print verbose output.

Available commands:
  config     Set configuration for FVM
  flutter    Proxies Flutter Commands
  install    Installs Flutter SDK Version
  list       Lists installed Flutter SDK Version
  releases   Lists Flutter SDK releases.
  remove     Removes Flutter SDK Version
  use        Which Flutter SDK Version you would like to use
  version    Prints the currently-installed version of FVM

Run "fvm help <command>" for more information about a command.
  • fvm list แสดงเวอร์ชัน flutter sdk ทั้งหมด
PS C:\WINDOWS\system32> fvm list
Versions path:  \Users\poolsawat\fvm\versions
2.0.1
1.22.6
  • fvm releases แสดง flutter sdk versions ที่สามารถ donwload มาใช้งานได้
$ fvm releases
Feb 27 18  │ v0.1.6
...
Jan 25 21  │ 1.22.6
Mar 3 21   │ 2.0.0
Mar 3 21   │ 2.0.0
Mar 3 21   │ 2.1.0-10.0.pre
Mar 4 21   │ 2.0.1
Mar 4 21   │ 2.0.1
--------------------------------------
Mar 12 21  │ 2.0.2             stable
--------------------------------------
--------------------------------------
Mar 13 21  │ 2.1.0-12.1.pre    dev
--------------------------------------
--------------------------------------
Mar 15 21  │ 2.0.2             beta
--------------------------------------
  • fvm install 2.0.2 ติดตั้ง flutter sdk เวอร์ชัน 2.0.2
$ fvm install 2.0.2
Installing version: 2.0.2
Cloning into '\Users\poolsawat\fvm\versions\2.0.2'...
remote: Enumerating objects: 286632, done.
remote: Total 286632 (delta 0), reused 0 (delta 0), pack-reused 286632
Receiving objects: 100% (286632/286632), 131.73 MiB | 9.98 MiB/s, done.
Resolving deltas: 100% (220826/220826), done.
Checking out files: 100% (5202/5202), done.
Version installed: 2.0.2
Checking Dart SDK version...
Downloading Dart SDK from Flutter engine ...
Building flutter tool...
Running pub upgrade...
Flutter 2.0.2 • channel unknown • unknown source
Framework • revision 8962f6dc68 (4 days ago) • 2021-03-11 13:22:20 -0800
Engine • revision 5d8bf811b3
Tools • Dart 2.12.1
  • fvm use 2.0.2 เลือกใช้งาน flutter sdk เวอร์ชัน 2.0.2
$ fvm use 2.0.2
Project now uses Flutter: 2.0.2
  • fvm remove 2.0.2 ลบ flutter sdk เวอร์ชัน 2.0.2
$ fvm remove 2.0.1
Removing 2.0.1
  • fvm config –cache-path D:\newpath\fvm\versions แก้ไข path cache ใหม่ กรณีจะย้ายจาก drive c ไป drive อื่น
$ fvm config --cache-path D:\newpath\fvm\versions
  • fvm flutter run เพื่อ รัน code ด้วย flutter sdk ที่เลือก
$ fvm flutter run

ตั้งค่าการใช้งานบน IDE VSCode

ให้เปิด settings.json (Ctrl + Shift + P)

"dart.flutterSdkPaths": [
     "/Users/poolsawat/fvm/versions"
]

Flutter สร้าง Flutter Project แรก เริ่มต้นอย่างไร พร้อมทดสอบรันบน Emulator devices EP2

พาทุกคนมาเริ่มสร้าง Flutter Project แรกมีขั้นตอนอย่างไร บทความนี้จะพาทุกคนมาเริ่มทำการ create flutter starter project ที่ทาง flutter สร้างระบบ counter ง่าย ๆ มาให้ได้ลองเล่นกัน ด้วยเครื่องมือ VSCode ที่เป็นเครื่องมือที่กำลังได้รับความนิยมอยู่ในตอนนี้ มาเริ่มกันเลย

เริ่มสร้าง Counter Flutter Project ง่าย ๆ

  • เปิด Visual Studio Code
  • เปิด Command Pallette (Ctrl + Shift + P) และพิมพ์ “flutter ” เลือก “Flutter: New Application Project”
  • จะมีการให้เลือกที่เก็บ Project จากนั้น ระบบจะให้ตั้งชื่อ Project ก็ตั้งตามที่ต้องการ ตัวอย่างจะตั้ง “counter_app” รอสักครู่…
  • เปิด emulators ด้วยคำสั่ง “‘flutter emulators –launch <emulator id>” (<emulator id> คือ id emulator ที่เราสร้างไว้)
  • สั่งรันโค๊ดตัวอย่างที่ flutter มีให้ด้วยคำสั่ง “flutter run” หรือสั่ง run ผ่านไฟล์ lib/main.dart ด้วยปุ่ม Run

Flutter run key commands.
r Hot reload.
R Hot restart.
h Repeat this help message.
d Detach (terminate “flutter run” but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
An Observatory debugger and profiler on sdk gphone x86 arm is available at: http://127.0.0.1:53236/Vgi_F0zG2PU=/

สรุปท้ายบทความ

ขั้นตอนต่าง ๆ จากบทความนี้เป็นการทดสอบสร้างบท environment ของผู้เขียนที่ได้ทำการตั้งค่า flutter sdk , dart sdk, android sdk เรียบร้อยแล้ว หากเพื่อน ๆ คนใดยังไม่ได้ติดตั้งโปรแกรม หรือไลบารี่เหล่านี้ให้ทำการติดตั้งให้เรียบร้อย จากบทความนี้นะครับ Flutter เริ่มต้น Setup Flutter เตรียมตัวก่อนเขียนโค๊ด EP1

Spring Boot แยก environments dev ,staging ,prod ด้วย spring profiles EP3

เมื่อการพัฒนาระบบ ได้มาถึงจุดที่ต้องทำการแยก environments ที่แตกต่างกัน จากตัวอย่างที่เห็นได้ชัด ๆ เช่น endpoint ของ API ที่ dev ก็จะใช้เป็น endpoint เพื่อสำหรับ dev เท่านั้น แต่เมื่ออยากที่จะ deploy เพื่อใช้งานบน production ก็จะมี endpoint ของ API เวอร์ชั่นที่เป็น ของ production จริงและความต้องการแบบนี้ สำหรับ project ที่ build ด้วย spring boot จะแก้ปัญหาได้อย่างไร มาทำความรู้จัก spring profiles กัน เพื่อช่วยให้ระบบสามารถแยก constant variables ตาม environment ต้องทำอย่างไร

ทดลองสร้าง Simple Project เพื่อพิสูจน์ความต้องการในครั้งนี้

โดยปกติ spring boot เมื่อสร้าง project ด้วย spring initialize เช็คโครงสร้าง project จะสังเกตุเห็นไฟล์ src/main/resources/application.properties ถ้าเราต้องการที่จะแยก environment dev , staging ,prod ก็ต้องจัดการไฟล์นี้ใหม่

  • เพิ่มไฟล์ตาม environment ที่ต้องการ ตัวอย่างข้างล่างนี้จะทำตัวอย่าง แยก dev ,prod เพื่อใช้เป็นตัวอย่างง่าย ๆ โดยจะทำการ copy file application.properties มาตั้งชื่อใหม่ให้เป็นตามแต่ละ environment แบบนี้
    • dev: src/main/resources/application-dev.properties
    • prod: src/main/resources/application-prod.properties
  • เพิ่มเติมเนื้อหาข้างในไฟล์ application-dev.properties และ application-prod.properties
#file application-dev.properties
server.port=8090
api.endpoint="dev.poolsawat.com"
#file application-prod.properties
server.port=9090
api.endpoint="prod.poolsawat.com"

โดยกำหนดให้

server.port ของ dev start ที่ port 8090 , prod start ที่ port 9090

api.endpoint ของ dev ใช้ endpoint ที่ “dev.poolsawat.com” , prod จะใช้ที่ “prod.poolsawat.com”

  • แก้ไขไฟล์ Application.java (ไฟล์ สำหรับใช้ boot SpringApplication.run(PoolsawatApplication.class, args); )
package com.poolsawat.starter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class PoolsawatApplication implements CommandLineRunner{
	
	private static final Logger logger = LoggerFactory.getLogger(PoolsawatApplication.class);
	
	@Value("${api.endpoint}")
	private String apiEndpointUrl;
	
	@Value("${server.port}")
	private String serverPort;

	public static void main(String[] args) {
		SpringApplication.run(PoolsawatApplication.class, args);
	}
	
	@Override
	public void run(String... args) throws Exception {
		logger.info("server.port ::=="+serverPort);
		logger.info("api.endpoint ::=="+apiEndpointUrl);
		logger.info("spring boot loaded");
	}
	
}

เริ่มสั่ง start server ที่ env dev (ส่ง argrument -Dspring-boot.run.profiles=dev)

$ mvn -s D:/pool13433/programs/.m2/settings.xml spring-boot:run -Dspring-boot.run.profiles=dev
...
2021-03-08 13:29:35.407  INFO 12204 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-03-08 13:29:35.504  INFO 12204 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8090 (http) with context path ''
2021-03-08 13:29:35.512  INFO 12204 --- [           main] c.p.starter.PoolsawatApplication         : Started PoolsawatApplication in 1.253 seconds (JVM running for 1.518)
2021-03-08 13:29:35.514  INFO 12204 --- [           main] c.p.starter.PoolsawatApplication         : server.port ::==8090
2021-03-08 13:29:35.514  INFO 12204 --- [           main] c.p.starter.PoolsawatApplication         : api.endpoint ::=="dev.poolsawat.com"
2021-03-08 13:29:35.514  INFO 12204 --- [           main] c.p.starter.PoolsawatApplication         : spring boot loaded

เริ่มสั่ง start server ที่ env prod (ส่ง argrument -Dspring.profiles.active=prod)

  • pack jar ด้วย mvn install
  • exc jar
$ mvn -s D:/pool13433/programs/.m2/settings.xml install -Dmaven.test.skip=true
...
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ poolsawat ---
[INFO] Installing D:\pool13433\aging\workspace\Poolsawat.Com\target\poolsawat.jar to D:\pool13433\programs\.m2\repository\com\poolsawat\poolsawat\0.0.1-SNAPSHOT\poolsawat-0.0.1-SNAPSHOT.jar
[INFO] Installing D:\pool13433\aging\workspace\Poolsawat.Com\pom.xml to D:\pool13433\programs\.m2\repository\com\poolsawat\poolsawat\0.0.1-SNAPSHOT\poolsawat-0.0.1-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
$ java -jar -Dspring.profiles.active=prod ./target/poolsawat.jar
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.3)

2021-03-08 13:33:49.548  INFO 7448 --- [           main] c.p.starter.PoolsawatApplication         : Starting PoolsawatApplication v0.0.1-SNAPSHOT using Java 1.8.0_281 on LAPTOP-69PUN6C7 with PID 7448 (D:\pool13433\aging\workspace\Poolsawat.Com\target\poolsawat.jar started by pool13433 in D:\pool13433\aging\workspace\Poolsawat.Com)
2021-03-08 13:33:49.550  INFO 7448 --- [           main] c.p.starter.PoolsawatApplication         : The following profiles are active: prod
2021-03-08 13:33:50.698  INFO 7448 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 9090 (http)
2021-03-08 13:33:50.716  INFO 7448 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-03-08 13:33:50.717  INFO 7448 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.43]
2021-03-08 13:33:50.807  INFO 7448 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-03-08 13:33:50.807  INFO 7448 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1191 ms
2021-03-08 13:33:50.993  INFO 7448 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-03-08 13:33:51.156  INFO 7448 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 9090 (http) with context path ''
2021-03-08 13:33:51.167  INFO 7448 --- [           main] c.p.starter.PoolsawatApplication         : Started PoolsawatApplication in 2.062 seconds (JVM running for 2.484)
2021-03-08 13:33:51.168  INFO 7448 --- [           main] c.p.starter.PoolsawatApplication         : server.port ::==9090
2021-03-08 13:33:51.169  INFO 7448 --- [           main] c.p.starter.PoolsawatApplication         : api.endpoint ::=="prod.poolsawat.com"
2021-03-08 13:33:51.169  INFO 7448 --- [           main] c.p.starter.PoolsawatApplication         : spring boot loaded

สรุปท้ายบทความ

วิธีการที่นำเสนอนี้เป็นเพียงวิธีการแยก environment แบบนึง สามารถทำด้วยวิธีแบบอื่น ๆ ได้ แต่ผมมองว่ามันจะเป็นวิธีการที่เข้าใจง่ายที่สุด หากผู้อ่านท่านใดลองนำไปทำตามแล้วเกิดติดปัญหา สามารถฝากคำถามไว้ครับ ขอบคุณสำหรับการติดตามบทความครับ

Github code

Flutter เริ่มต้น Setup Flutter เตรียมตัวก่อนเขียนโค๊ด EP1

บทความแรกนี้ สำหรับผู้เริ่มต้นอยกาที่จะเขียน Flutter โดยที่จะแนะนำการเตรียมความพร้อมของเครื่อง เช่น Android Studio , Emulators ,Dart SDK , Flutter SDK , VSCode เป็นต้น โดยเนื้อหาสำหรับบทความนี้จะพา setup บน Window OS (Win 10) ฉะนั้นตัวอย่างบางขั้นตอน Mac OS อาจจะทำไม่ได้

ขออภัยสำหรับผู้ที่เข้ามาอ่าน แต่ใช้งาน Mac ไว้ ณ ที่นี้

ดาวน์โหลดไฟล์ที่ต้องใช้ และติดตั้ง

  1. ดาวน์โหลดไฟล์ที่ต้องใช้ และติดตั้ง
    1. Flutter SDK ดาวน์โหลด จะได้ไฟล์ flutter.zip ให้ unzip flutter.zip (copy path ไว้ต้องใช้ในขั้นตอน set android studio config) set environment variables ทำตามขั้นตอน ข่างล่าง
      1. เปิด search ของ window (มุมล่างขวา แว่นขยาย) พิมพ์ ‘env’ กดที่ “Edit Environment Variables for your account”
      2. เลือก “Advance System Setting” (สังเกตุเมนูลำดับที่ 4 ทางซ้ายมือของหน้าจอ)
      3. เลือก “Environment Variables…” (จะอยู่ล่าง ๆ ของหน้าจอ)
      4. จะมีให้ใส่ 2 แบบ คือ Variables for user , Variables for system จะเลือกทำที่ไหนก็ได้ขั้นตอนเหมือนกัน
        1. มองหา Variable Name ที่ชื่อว่า Path จากนั้นให้กดเลือก และกด Edit
        2. กด New เพื่อสร้าง เพิ่ม Flutter Environment เข้าไปใน Path (*ให้ระบุ path flutter/bin เช่น D:\dev\programs\flutter\bin) จากนั้นกด OK
      5. ทดสอบว่า command flutter สามารถเรียกใช้งานผ่าน command line ได้หรือยัง
        1. เปิด search พิมพ์ cmd เลือก “Command Promt” ให้พิมพ์ไปใน command ว่า “flutter doctor” ถ้ามีการแสดงข้อความ “
          “Doctor summary (to see all details, run flutter doctor -v): … ” แสดงว่า flutter พร้อมใช้งานแล้ว
  2. Android Studio ติดตั้ง ทำตามขั้นตอนการ
    • ตรวจสอบ Adnroid SDK ปกติขั้นตอนการติดตั้งระบบให้ให้ download Android SDK ในขณะการติดตั้ง (อาจจะใช้เวลาดาวน์โหลด ตามความเร็ว internet ของแต่ละคน) แนะนำให้เลือกย้ายไฟล์ android sdk ไปที่ directory ที่มีขนาดพอสมควร เพราะไฟล์มี android sdk มีขนาดใหญ่
      • Files -> Settings -> Appearance & Behavior -> System Setting -> Android SDK -> Android SDK Location: {แก้เป็น Path SDK ที่ย้าย}

3. ติดตั้ง plugins flutter ,dart บน android studio

  • เข้าไฟที่ File -> Settings -> Plugins -> Maketplaces
  • ค้นหา flutter ติดตั้ง
  • ค้นหา dart ติดตั้ง

4. ตรวจสอบการตั้งค่าของ flutter ด้วย $flutter doctor -v

$flutter doctor -v
Downloading Material fonts...                                    1,373ms
Downloading Gradle Wrapper...                                       72ms
Downloading package sky_engine...                                  350ms
Downloading flutter_patched_sdk tools...                            3.7s
Downloading flutter_patched_sdk_product tools...                    3.4s
Downloading windows-x64 tools...                                    6.1s
Downloading windows-x64/font-subset tools...                       672ms
[√] Flutter (Channel dev, 1.27.0-8.0.pre, on Microsoft Windows [Version 10.0.19041.804], locale en-US)
    • Flutter version 1.27.0-8.0.pre at D:\dev\programs\flutter
    • Framework revision b7d4806243 (5 days ago), 2021-02-19 09:22:45 -0800
    • Engine revision 6993cb229b
    • Dart version 2.13.0 (build 2.13.0-30.0.dev)

[√] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at D:\dev\programs\Android\Sdk
    • Platform android-30, build-tools 30.0.3
    • ANDROID_HOME = D:\dev\programs\Android\Sdk
    • ANDROID_SDK_ROOT = D:\dev\programs\Android\Sdk
    • Java binary at: D:\dev\programs\Android\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)
    • All Android licenses accepted.

[√] Chrome - develop for the web
    • Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

[√] Android Studio (version 4.1.0)
    • Android Studio at D:\dev\programs\Android\Android Studio
    • Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)

[√] IntelliJ IDEA Community Edition (version 2020.3)
    • IntelliJ at D:\dev\programs\IntelliJ
    • Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart

[√] VS Code, 64-bit edition (version 1.53.2)
    • VS Code at C:\Program Files\Microsoft VS Code
    • Flutter extension version 3.19.0

[√] Connected device (2 available)
    • Chrome (web) • chrome • web-javascript • Google Chrome 88.0.4324.190
    • Edge (web)   • edge   • web-javascript • Microsoft Edge 88.0.705.74

• No issues found!

ถ้า setting [√] ทุกประเภท แสดงว่าพร้อมใช้งานและ แต่ถ้าหากยังมี [X] อยู่ก็ให้แก้ไขให้ผ่านก่อน ซึ่งวิธีแก้ไขก็สามารถ ค้นหาได้จาก google หรือถ้ายังไม่สามารถแก้ไขได้ สามารถฝากคำถามไว้ในเม้นข้างล่างบทความนี้ได้เลยครับ

สร้าง API ด้วย App Script ดึงข้อมูลบน Google Sheets

บทความแนะนำการสร้าง RESTFul API โดยที่จะใช้การเก็บข้อมูลบน Google Sheets ที่ทุกคนคุณเคย แค่มี gmail account ก็สามารถสร้าง API ได้แล้ว บวกกับความรู้เรื่องการเขียน javascript พื้นฐาน เข้าใจพื้นฐานเรื่องการพัฒนาเว็บ (API) น่าจะเพียงพอในการทำ API ในบทความนี้ได้แล้ว

สร้าง Google Sheets เพื่อเป็น ฐานข้อมูล

บางท่านอาจจะไม่ทราบว่า google sheets ที่ทำหน้าที่คล้าย microsoft excel จะสามารถเขียนโปรแกรม อ่าน เขียน ข้อมูลในไฟล์ได้ ข้อดีคือไม่ว่าใครก็สามารถนำข้อมูลในไฟล์ google sheets ไปใช้งานต่อได้โดยไม่ต้องเขียน query เหมือนฐานข้อมูลแบบปกติ

  1. ให้สร้างไฟล์ google sheets ขอตั้งชื่อว่า “poolsawat.com” ชื่อไม่สำคัญ โปรแกรมจะ refer กับ sheetId ฉะนั้นชื่อจะตั้งเป็นอะไรก็ได้
  2. สร้างคอลัมน์ที่มีชื่อตามนี้ (ภาษาอังกฤษ เท่านั้น) id ,topic ,content ,author ,date ,view
  3. สำหรับข้อมูลให้ใส่ตามใจเลย

4. เริ่มเขียนโปรแกรมเลือกที่ เครื่องมือ (tools) -> โปรแกรมแก้ไขสคริป (Script editor)

5. ได้หน้า AppScript Editor เพื่อใช้เขียนโปรแกรมให้แก้ไข ตามนี้

function doGet(request) {
  // sheet id 
  var ss = SpreadsheetApp.openById("ใส่ sheet id ของคุณ");
  var sheet = ss.getActiveSheet()

  const values = sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn()).getValues();
  // Converts data rows in json format
  const result = values.map(([a, b, c , d , e , f]) => {
    return ({ id: a, topic: b, content: c, author : d , date : e , view : f})
  })
  return ContentService.createTextOutput(JSON.stringify(result)).setMimeType(ContentService.MimeType.JSON);
}

openById : ให้ระบุ sheet id ที่ได้จาก https://docs.google.com/spreadsheets/d/187hL4DXXXXXXXXXXXXXXXXXXXXXXX-p_JOGjYxU/edit#gid=0 ซึ่งของแต่ละ sheet จะไม่ซ้ำกัน

6.กด “บันทึกโครงการ” script editor จากนั้นกด “เรียกใช้” ระบบขออนุญาตการบัญชีเจ้าของข้อมูล ก็ให้เลือกบัญชีเรา

7. ทำการเริ่มทดสอบ API

8. คัดลอกเว็บแอป URL ไปวางที่ browser ได้เลย

หลัก ๆ ขั้นตอนก็ประมาณนี้ ศึกษาเพิ่มเติมได้ที่นี้ Google App Script

Laravel Upload File to local storage and saved to database with Postman upload client UI EP8

File Storage เป็นเรื่องนึงที่ Laravel จัดการมาให้เรียบร้อยพร้อมใช้งาน ประหยัดเวลานักพัฒนาไปได้มากเลยทีเดียว ไม่ว่าจะเป็นการดึงไฟล์ (Retrieving Files) ,อัพโหลดไฟล์ (Storing Files) ,ดาวห์โหลดไฟล์ (Downloading Files) ,ลบไฟล์ (Delete Files) เหล่านี้เป็นเรื่องง่ายมาก ๆ ถ้าแอพของเราพัฒนามาด้วย Laravel

บทความนี้จะพาทุกคนมาลองเขียนโค๊ดง่าย ๆ ทดสอบการอัพโหลดไฟล์ด้วยเครื่องมือ Postman client ที่รองรับการอัพโหลดไฟล์ (แทนการเขียนหน้าฟอร์มอัพโหลดเอง) ทำการอัพโหลดไฟล์รูปภาพไปที่ Laravel Project ที่เขียนโค๊ดการอัพโหลดไฟล์ไว้ ทำหน้าที่รับ request ที่ส่ง binary data ระบบจะทำการ Save File ลง local drive และ Save Data ลง Database

เริ่มโค๊ดเลยดีกว่า

สร้าง Table photos เพื่อเอาไว้เก็บข้อมูล photos ที่อัพโหลดเข้าระบบ

CREATE TABLE `photos` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `photo_origin_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `photo_new_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `photo_temp_path` varchar(255) NOT NULL,
  `photo_extension` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `photo_status` enum('active','inactive') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `photo_date` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

สร้าง Class Model Photo (Photo.php)

php artisan make:Model Photo

แก้ไขไฟล์ app\Models\Photo.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Photo extends Model
{
  use HasFactory;
  protected $table = 'photos';
  protected $fillable = [
    'id', 'photo_origin_name', 'photo_new_name',
    'photo_extension', 'photo_status', 'photo_date', 'photo_temp_path'
  ];
  public $timestamps = false;
}

สร้าง Class Controller UploadFileController (UploadFileController.php)

<?php

namespace App\Http\Controllers;

use App\Models\Photo;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class UploadFileController extends Controller
{

  public function simpleUplaod(Request $request)
  {
    try {
      if ($request->file('photo')->isValid()) {
        $path = $request->photo->path();
        $extension = $request->photo->extension();
        $clientOriginalName = $request->photo->getClientOriginalName();
        $newFileName = time() . $clientOriginalName;
        $uploadedFile = $request->file('photo');

        // Save File to local drive
        Storage::putFileAs('photos', $uploadedFile, $newFileName);

        //Save File to Photo table
        $photo = new Photo();
        $photo->photo_temp_path = $path;
        $photo->photo_origin_name = $clientOriginalName;
        $photo->photo_new_name = $newFileName;
        $photo->photo_extension = $extension;
        $photo->photo_status = 'ACTIVE';
        $photo->photo_date = Carbon::now();
        $photo->save();

        return [
          'path' => $path,
          'extension' => $extension,
          'clientOriginalName' => $clientOriginalName,
          'newFileName' => $newFileName
        ];
      }
    } catch (\Throwable $th) {
      return $th->getMessage();
    }
  }
}

แก้ไขไฟล์ routes.php เพิ่ม /upload

Route::post('upload', 'App\Http\Controllers\[email protected]');

เนื่องด้วยการ request ส่ง data ไปที่ laravel โดยต้องมีการส่งจำเป็นต้องส่ง @csrf ไม่เช่นนั้น ระบบจะ response (419 PAGE EXPIRED) ให้เพิ่ม ignore path “/upload” App\Http\Middleware\VerifyCsrfToken

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
  /**
   * The URIs that should be excluded from CSRF verification.
   *
   * @var array
   */
  protected $addHttpCookie = true;

  protected $except = [
    'upload'
  ];
}

start server

php artisan serve

เปิดโปรแกรม Postman ขึ้นมา กำหนดตามนี้ จากนั้นกด Send

POST : http://127.0.0.1:8000/upload 
Body : form-data
        KEY : photo (File)
        VALUE : Chose File

ตรวจสอบข้อมูลที่ Table photos

ตรวจสอบไฟล์ที่ storage/app/photos/1608200088upload-1.PNG

สรุปท้ายบทความ

จากที่ได้ทดสอบการสร้างระบบ upload ไม่ได้ยุ่งยากอย่างที่คิด ตัวอย่างโค๊ดเป็นเพียงการ Push File ลง local Storage ความสามารถของ Laravel กับเรื่อง File System สามารถศึกษาเพิ่มเติมได้ที่ลิ้งนี้ https://laravel.com/docs/7.x/filesystem หากผู้อ่านท่านใดลองทำตามแล้วไม่ได้ หรือ ติดปัญหาสามารถ ทักมาสอบถามกันได้ที่ https://www.facebook.com/poolsawat.apin/ สำหรับบทความนี้ขอจบเนื้อหาเพียงเท่านี้ครับ

Laravel ทำ JWT Authentication มีโค๊ดตัวอย่าง (Laravel 7.x) EP7

JWT คืออะไร บทความนี้ไม่ขอกล่าว แต่แนะนำบทความนี้ อธิบายได้ละเอียดเลย “JSON Web Token มาตรฐานใหม่ ในการทำ Authentication” บทความที่ผมจะเขียนนี้จะพาทำ workshop ง่ายๆ แทบเหมือนว่า copy code จาก web “JSON Web Token Authentication for Laravel & Lumen” นี้มาเลยก็ว่าได้

ติดตั้ง Dependencies และ Config Project

  1. ต้องทำการติดตั้ง dependency ที่ชื่อว่า tymon/jwt-auth ด้วยคำสั่ง
$composer require tymon/jwt-auth

Using version ^1.0 for tymon/jwt-auth
./composer.json has been updated
Running composer update tymon/jwt-auth
Loading composer repositories with package information
Updating dependencies
...
[32mPackage manifest generated successfully.[39m
2 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

2. รันคำสั่ง Publish the config เพื่อสร้างไฟล์ jwt.php config ไฟล์ ที่ config/jwt.php ให้อัตโนมัติเองหลังจากรันคำสั่ง

$php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

Copied File [\vendor\tymon\jwt-auth\config\config.php] To [\config\jwt.php]
Publishing complete.

3. รันคำสั่ง Generate secret key เพื่อ generate jwt key ในไฟล์ .env JWT_SECRET=? เพื่อเป็น key ตั้งต้นให้การ Generate Token ทำ authenticate token หลังรันคำสั่งตรวจสอบ secret key ที่ .env

$php artisan jwt:secret

jwt-auth secret [Wwt4F********************************************UNvQ14f] set successfully.

4. แก้ไข class User.php เพิ่ม implement JWTSubject

<?php

namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

    // Rest omitted for brevity

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}

5. แก้ไข gaurds driver ของ api จากเดิม passport ไปเป็น jwt
– driver => jwt (jwt auth)
– provider => users (model auth user)

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],

...

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

...
 'providers' => [
    'users' => [
      'driver' => 'eloquent',
      'model' => App\Models\User::class,
    ],

6. create table users

CREATE TABLE `users` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email_verified_at` timestamp NULL DEFAULT NULL,
  `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

7. เพิ่ม basic authenticate route ที่ไฟล์ routes/api.php
– ‘middleware’ => ‘api’ (ประตูดัก request , response ของ route ที่มี pattern: /api/*)
– ‘prefix’ => ‘auth’ (prefix url ex: /api/auth/login )

Route::group([

    'middleware' => 'api',
    'prefix' => 'auth'

], function ($router) {


    Route::post('register', 'App\Http\Controllers\[email protected]');
    Route::post('login', 'App\Http\Controllers\[email protected]');
    Route::post('logout', 'App\Http\Controllers\[email protected]');
    Route::post('refresh', 'App\Http\Controllers\[email protected]');
    Route::post('me', 'App\Http\Controllers\[email protected]');

});

8. สร้าง AuthController สำหรับใส่ Auth Logic (login , logout , register , info)

$php artisan make:controller AuthController
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class AuthController extends Controller
{
  /**
   * Create a new AuthController instance.
   *
   * @return void
   */
  public function __construct()
  {
    $this->middleware('auth:api', ['except' => ['login', 'register']]);
  }

  public function register(Request $request)
  {
    $user = User::create([
      'name' => $request->name,
      'email' => $request->email,
      'password' => bcrypt($request->password),
    ]);

    $token = auth()->login($user);

    return $this->respondWithToken($token);
  }

  /**
   * Get a JWT via given credentials.
   *
   * @return \Illuminate\Http\JsonResponse
   */
  public function login()
  {
    $credentials = request(['email', 'password']);
    //return response()->json($credentials);
    if (!$token = auth()->attempt($credentials)) {
      return response()->json(['error' => 'Unauthorized'], 401);
    }

    return $this->respondWithToken($token);
  }

  /**
   * Get the authenticated User.
   *
   * @return \Illuminate\Http\JsonResponse
   */
  public function me()
  {
    return response()->json(auth()->user());
  }

  /**
   * Log the user out (Invalidate the token).
   *
   * @return \Illuminate\Http\JsonResponse
   */
  public function logout()
  {
    auth()->logout();

    return response()->json(['message' => 'Successfully logged out']);
  }

  /**
   * Refresh a token.
   *
   * @return \Illuminate\Http\JsonResponse
   */
  public function refresh()
  {
    return $this->respondWithToken(auth()->refresh());
  }

  /**
   * Get the token array structure.
   *
   * @param  string $token
   *
   * @return \Illuminate\Http\JsonResponse
   */
  protected function respondWithToken($token)
  {
    return response()->json([
      'access_token' => $token,
      'token_type' => 'bearer',
      'expires_in' => auth()->factory()->getTTL() * 60
    ]);
  }
}

9. ทดสอบระบบ
– [POST] http://127.0.0.1:8000/api/auth/register (ระบบจะสร้าง user ใน table users )
– [POST] http://127.0.0.1:8000/api/auth/login (ระบบเช็ค user ใน table users)
– [POST] http://127.0.0.1:8000/api/auth/me (ระบบแสดง user จาก bearer token )
– [POST] http://127.0.0.1:8000/api/auth/logout (ระบบ expired token ออกจากระบบ)

http://127.0.0.1:8000/api/auth/register
http://127.0.0.1:8000/api/auth/login
http://127.0.0.1:8000/api/auth/me (Bearer Token ด้วย)
http://127.0.0.1:8000/api/auth/logout (Bearer Token ด้วย)

สรุปท้ายบทความ

JWT เป็นเพียงการ Encode Data ที่จะกอบไปด้วย 3 ส่วนหลัก ๆ คือ Headers (เข้ารหัสแบบไหนอยู่ (เช่น SHA256, RSA)) ,Payload เป็นข้อมูลจริง ,Signature เอาไว้เช็คกับ Payload เป็นเพียงมาตรฐานเปิด (RFC 7519) ที่ช่วยเข้ามาแก้ไขโดยมีข้อดีหลัก ๆ 2 ข้อ คือขนาดไม่ใหญ่มาก ,ลักษณะการเก็บข้อมูลในตัวเอง

หากผู้อ่านท่านใดสงสัยหรืออยากที่จะแนะนำบทความ สามารถคอมเม้นทิ้งท้ายไว้ได้เลยครับ ^^

Spring Boot สร้าง RESTful API พร้อม Unit Tests แบบเข้าใจง่าย และรวดเร็ว EP2

การพัฒนาแอพพลิเคชั่น ข้อแนะนำคือควรทำ Unit Tests มองการพัฒนาในระยะยาว หากต้องมีการแก้ไข issue เล็กน้อย แต่ไม่แน่ใจว่าจะเกิด defect กับระบบเดิมหรือไม่ การมี unit tests ช่วยประหยัดเวลาการ regression tests ได้เยอะมาก และคุณจะเห็นประโยชน์ของการทำ Unit tests อย่างแน่นอน

รู้จัก @RestController annotation สร้าง routes RESTful API

annotation @RestController implement มาจาก @Controller ของ spring web เพื่อใช้งานสำหรับการสร้าง RESTful API (ไม่ใช่ static view) เพิ่มความสะดวกในการพัฒนา RESTful API ให้ง่ายยิ่งขึ้น

ตัวอย่างการสร้าง Route (text/plain)

package com.poolsawat.starter.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
	
	@RequestMapping("/")
	public String index() {
		return "Greetings from Spring Boot!";
	}
	
}

ทดสอบ http://localhost:8080/

Greetings from Spring Boot!

เพิ่ม Route (application/json)

... 
	@RequestMapping("/domain")
	public String[] json() {		
		return new String[] {"www",".","poolsawat",".","com"};
	}
...

restart server จากนั้น ทดสอบ http://localhost:8080/domain

[
  "www",
  ".",
  "poolsawat",
  ".",
  "com"
]

เพิ่ม Test Dependencies ใน POM

ก่อนที่จะเริ่มเขียน Unit Tests จำเป็นต้องเพิ่ม “spring-boot-starter-test” เข้าไปที่ไฟล์ POM (ไฟล์ pom.xml) มาเพิ่มกันเลย

...
<dependencies>
	...
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	...
</dependencies>
...

สร้าง src/test/java/com/poolsawat/starter/controller/HelloControllerTest.java

package com.poolsawat.starter.controller;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {
	@Autowired
	private MockMvc mvc;

	@Test
	public void getHello() throws Exception {
		mvc.perform(MockMvcRequestBuilders.get("/")
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andExpect(content().string(equalTo("Greetings from Spring Boot!")));
	}
	@Test
	public void getDomain() throws Exception {
		mvc.perform(MockMvcRequestBuilders.get("/domain")
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andExpect(content().json("[\"www\",\".\",\"poolsawat\",\".\",\"com\"]"));
	}
}

อธิบายการทำงาน

  • @SpringBootTest inject annotation Test เพื่อเริมการ Test
  • @AutoConfigureMockMvc inject annotation AutoConfiMockMVC (MockMVC มีหน้าที่สำหรับทำ Unit Tests มี feature mockup data)
  • @Autowired เป็นการ auto inject MockMVC bean ก่อนการจะเรียกใช้งานใน Testcase
  • @Test ระบุที method บอกให้รู้ว่า รันเคส method นี้
  • mvn.perform mockup route ที่ต้องการทดสอบ ระบุ MockMvcRequestBuilders.get(“/”) หมายถึง (“localhost:8080/”) ตรวจสอบ content-type คือต้องเป็น “MediaType.APPLICATION_JSON” กำหนดให้เป็น application/json เท่านั้น
  • andExpect
    • .andExpect(status().isOk()) expect htto status code OK (status code 200) หากไม่ใช่จะ fail ทันที
    • .andExpect(content().string(equalTo(“Greetings from Spring Boot!”))) expect content body ต้องเป็นคำว่า “Greetings from Spring Boot!” หากไม่ใช่จะ fail ทันที

จากนั้นสร้าง maven test build

apply -> run

ตรวจสอบที่ console panel

[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.poolsawat.starter.controller.HelloControllerTest
...
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.606 s - in com.poolsawat.starter.controller.HelloControllerTest
2020-10-15 10:57:54.262  INFO 5864 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.231 s
[INFO] Finished at: 2020-10-15T10:57:54+07:00
[INFO] Final Memory: 18M/220M
[INFO] ------------------------------------------------------------------------

ในทุกครั้งที่มีการแก้ไข java code จำเป็นต้องมีการ restart web server spring boot มี hot reload (การ auto restart web server เมื่อมี code change)

เพิ่ม “spring-boot-devtools”

...
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
	<optional>true</optional>
</dependency>
...

จากนั้นทำการ restart อีกครั้ง ต่อไปเวลาแก้ไข code ก็จะไม่ต้อง restart เองแล้ว

สรุปท้ายบทความ

การพัฒนาแอพพลิเคชั่นที่มี Test ถือว่าเป็นแอพพลิเคชั่นที่ดี ข้อดีของการทำ Test มีเยอะมากช่วยลดเวลาการ Regression Test ได้เยอะ ถ้าแอพพลิเคชั่นของคุณยังไม่มี test เริ่มทำได้แล้ว เพราะถ้าไม่ทำคุณจะคุยกับเขา (ทีมพัฒนา แอพพลิเคชั่นที่มี test) ไม่รู้เรื่อง บทความต่อไปจะเป็นเรื่องเกี่ยวกับอะไร คอยติดตามกันนะครับ

Spring Boot ทำความรู้จัก เริ่มติดตั้งและเริ่มใช้งานเบื้องต้น EP1

Spring boot คืออะไร

spring boot เป็น framework package ที่ต่อยอดมาจาก spring framework ช่วยให้เราสามารสร้างแอพพลิเคชั่น (application) แบบ stand-alone แบบรวดเร็ว และง่ายดาย

บ่อยครั้งที่ต้องมานั่งปวดหัวกับเรื่องการ configuration project ในขั้นตอนการ setup เริ่มต้นในการสร้างแอพพลิเคชั่น ปัญหาเหล่านี้จะถูกทำให้เสร็จเรียบร้อยแล้ว เมื่อมาใช้งาน spring boot

Features ต่าง ๆ ที่มีมาใน Spring boot

  • การสร้างแอพพลิเคชั่นแบบ stand-alone
  • embed Tomcat ,Jetty web server ยอดนิยมมาให้เรียบร้อยแล้ว หรือแม้แต่ Underflow ก็มีมาให้แล้ว
  • มีการกำหนด dependencies starter (POM) สำหรับพัฒนาไม่ต้องเพิ่มหลาย dependencies เหมือน spring framework แบบแต่ก่อน
  • มี auto configuration มาให้พร้อม ไม่ต้องยุ่งวุ้นวายกับการ config project เหมือนแต่ก่อน (ดีม๊วก ๆ )
  • ให้ feature monitoring ,metrics, health checks มาพร้อมแล้วเรียกใช้งานได้เลย
  • ไม่มีการสร้าง XML configuration file อีกต่อไปเพราะทุกอย่างจะ code บน java (kotlin) ทั้งหมดเลย

จะเริ่มเขียน Spring boot ต้องเตรียมตัวอย่างไรบ้าง

สำหรับมือใหม่ หลาย ๆ คนที่กำลังจะเรียนรู้ spring framework สั่งที่ต้องทราบก่อนที่จะเริ่มพัฒนาแอพพลิเคชั่นด้วย Java Framework อย่าง Spring Boot ต้องรู้เกี่ยวกับอะไร จะอธิบายย่อย ๆ ตามนี้

  • IDE (Integrated Development Environment) เครื่องมือสำหรับพัฒนาแอพพลิเคชั่น ที่ดัง ๆ สำหรับพัฒนา Java ก็พวก Eclipse ,IntelliJ IDEA เป็นต้น
  • Maven (Project Management Tools) สำหรับ Java Developer ทำงานได้ง่ายยิ่งขึ้น การที่จะต้องคอยหา JAR หรือบางครั้งเราจะเรียกว่า lib (dependencies) ที่มีมากมายซะเหลือเกิน อีกทั้งยังต้องมานั่งตรวจสอบเรื่องเวอชั่นของ JAR อีกละก็ ไม่สนุกแน่ ๆ สำหรับ Developer Java ปัญหาเหล่านี้จะถูกแก้ไขด้วย Maven อีกทั้งยังมีความสามารถในการทำกระบวนการ Build ,Compile , Test ,Deploy ,Documentation ได้อีกมากมาย อีกทั้งยังมี plugins ให้เรียกใช้งานอีกเพียบเลย ถือว่าดีม๊วก ๆ
  • Spring IO หน่วยงานพัฒนา Java Framework ที่โด่งดัง และยังคงได้รับความนิยมจนถึงยุคปัจจุบันด้วยความที่ไม่หยุดพัฒนา จึงทำให้ framework ได้รับความนิยมอยู่จนถึงปัจจุบัน

เริ่มติดตั้ง และสร้าง Hello World กัน

การ initializr Spring Boot Project มีด้วยกันหลายวิธี ผมขอยกตัวอย่างวิธีนี้

  1. เข้าไป generate spring boot project ที่ https://start.spring.io/
    1. Project ให้เลือก Maven Project (Gradle Project) เป็นเครื่องมืออีกแบบ ฝั่ง mobile dev ชอบใช้กัน เพราะมีมาใน android project)
    2. Language ให้เลือก Java (แล้วแต่ความชำนาญ ของผู้พัฒนาจะเลือกเป็น Kotlin หรือ Groovy ก็ได้)
    3. Spring Boot ให้เลือก version 2.3.4 เป็นเวอร์ชั่น ที่ stable แล้ว
    4. Project Metadata
      1. Group ใส่เป็น com.poolsawat (กำหนดเป็น domain ขององค์กร)
      2. Artifact ใส่เป็น Poolsawat (ชื่อสิ่งประดิษฐ์กำหนดเป็นชื่อของแอพพลิเคชั่น )
      3. Name ใส่เป็น Poolsawat (ชื่อแอพพลิเคชั่น)
      4. Description ใส่ Demo project for Spring Boot หรือจะใส่อธิบาย destination ของแอพพลิเคชั่น สั้น ๆ
      5. Package name ใส่ com.poolsawat.starter ใส่เป็น java package สำหรับเก็บ source code จะกำหนดภายใต้ src/com/poolsawat/starter/**
      6. Packaging เลือก Jar เพราะจะเลือกใช้ stand-lone application ที่รันด้วย embed Tomcat web server
      7. Java เลือก 8 (จะเลือก 10 , 15 แล้วแต่ JDK ในเครื่องที่ติดตั้งไว้)
      8. Dependencies เลือก “web”
      9. กด “GENERATE” downloadลงเครื่อง จะได้ไฟล์ .zip มา รอทำในขั้นถัดไป

2. เปิด IDE ตัวอย่างจะใช้ Eclipse IDE
        1. Unzip Poolsawat.zip ย้ายไฟล์ทั้งหมดไปยัง workspace ของ Eclipse ที่สร้างไว้
        2. หน้า Eclipse เลือก File -> Import… -> Maven -> Poolsawat

        3. จะมี Poolsawat เข้ามาที่ Project Explorer ทางซ้ายมือของ Eclipse IDE

3. แก้ไข Code ไฟล์ PoolsawatApplication.java ตามนี้

package com.poolsawat.starter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class PoolsawatApplication {

	public static void main(String[] args) {
		SpringApplication.run(PoolsawatApplication.class, args);
	}

	@GetMapping("/hello")
	public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
		return String.format("Hello %s!", name);
	}

}

4. สร้าง Maven build เลือกเมนู Run -> Run Configurations… -> Maven Build -> New Launch configuration -> ใส่ค่าตามภาพนี้

Apply -> Run

5. รอ… maven download dependencies สักครู่…..

...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.4.RELEASE)

2020-10-15 00:06:06.708  INFO 4832 --- [           main] c.p.starter.PoolsawatApplication         : Starting PoolsawatApplication on PoolPC-PC with PID 4832 (D:\workspace-blog\Poolsawat\target\classes started by PoolPC in D:\workspace-blog\Poolsawat)
2020-10-15 00:06:06.711  INFO 4832 --- [           main] c.p.starter.PoolsawatApplication         : No active profile set, falling back to default profiles: default
2020-10-15 00:06:07.773  INFO 4832 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-10-15 00:06:07.781  INFO 4832 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-10-15 00:06:07.782  INFO 4832 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.38]
2020-10-15 00:06:07.848  INFO 4832 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-10-15 00:06:07.848  INFO 4832 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1078 ms
2020-10-15 00:06:08.018  INFO 4832 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-10-15 00:06:08.174  INFO 4832 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-10-15 00:06:08.191  INFO 4832 --- [           main] c.p.starter.PoolsawatApplication         : Started PoolsawatApplication in 1.823 seconds (JVM running for 2.231)

6. ทดสอบเข้าลิ้ง http://localhost:8080/hello

สรุปท้ายบทความ

การพัฒนาเว็บด้วย Spring boot นั้นทำได้ไม่ยากเลย ค่อนข้างง่ายกว่าวิธีการเดิมของ Spring Framework ที่ผ่าน ๆ มา ไม่ต้อง configuration ให้ยุ่งยาก ไม่ต้องลำคาญกับการต้องมา control version ของ dependencies ที่มากมายซะเหลือเกิน เลยทำให้ผมชอบที่จะใช้งาน spring boot กับทุก ๆ project ของผมเลยครับ บทความหน้าจะเป็นเรื่องอะไร คอยติดตามกันนะครับ

Laravel สร้าง RESTful API ง่าย ๆ ด้วย Resource Routes Controller EP5

รูปแบบลักษณะงานเว็บไซต์ในปัจจุบัน เกิน 80 – 90 % ต้องมีการทำระบบ RESTful API เพื่อให้บริการข้อมูลกับ Application platform รูปแบบต่าง ๆ ไม่ว่าจะเป็น mobile และ web เพราะเป็นรูปแบบการพัฒนาที่ได้รับความนิยมมากในปัจจุบัน กล่าวคือการเชื่อมโยง application ด้วยข้อมูลจากแหล่งเดียวกัน นั้นเอง

การพัฒนา RESTful API Service สามารถทำได้กับหลาย ๆ ภาษาโปรแกรมมิ่งทั่วไป (ถ้าภาษาโปรแกรมมิ่งมี feature http ต้องสามารถทำ RESTful API ได้อย่างแน่นอน) แต่สิ่งที่จะทำให้การพัฒนา RESTful นั้นง่ายและสะดวกยิ่งขึ้นก็คือเครื่องมือ หรือ cli (command line user interface ) สำหรับช่วยทำให้การเขียนโปรแกรมของเราง่าย และสะดวกมากยิ่งขึ้นไปอีก

laravel มี artisan cli (symfony class console) ช่วยทำให้งาน develop สะดวกและง่ายยิ่งขึ้น บทความนี้จะพามาทำความรู้จัก cli นี้กัน

สร้าง Resource Controller RESTful API แรกกัน

laravel มี feature Resource Controllers ที่จะช่วยสร้าง RESTful function template controller ขึ้นมาให้ เราเพียงแค่ implement function ตาม RESTful action เท่านั้นก็สามารถเรียกใช้งานได้เลย โดยมีวิธีการเรียกใช้งานตามตัวอย่าง ข้างล่างนี้

$ php artisan make:controller PhotoController --resource

laravel สร้าง generate /app/Http/Controllers/PhotoController.php ภายในไฟล์จะมีการสร้าง function RESTful template มาให้

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PhotoController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}

มาทำความรู้จักการทำงานแต่ละ function มามีการทำงานอย่างไร ตามการอธิบายจากตารางนี้

Actions Handled By Resource Controller

VerbURIActionRoute Name
GET/photosindexphotos.index
GET/photos/createcreatephotos.create
POST/photosstorephotos.store
GET/photos/{photo}showphotos.show
GET/photos/{photo}/editeditphotos.edit
PUT/PATCH/photos/{photo}updatephotos.update
DELETE/photos/{photo}destroyphotos.destroy

เพิ่ม routes mapping ที่ไฟล์ /routes/api.php

Route::resource('photos', 'App\Http\Controllers\PhotoController');

เพิ่ม route:resource เป็น static function สำหรับการทำ RESTful routes เพิ่มเพียงบรรทัดเดียวก็สามารถเรียก http methods ต่าง ๆ เหล่านี้ GET ,POST ,PUT ,DELETE ,PATCH ได้เลย ง่ายมาก ๆ

implement PhotoController ตาม function การทำงานเพื่อใช้ตรวจสอบการ call จาก REST client

ผมจะเพิ่ม function response()->json(); เพื่อทำการ return http response คืนค่ากลับไปในรูปแบบ application/json format

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PhotoController extends Controller
{
  /**
   * Display a listing of the resource.
   *
   * @return \Illuminate\Http\Response
   */
  public function index()
  {
    return response()->json(['name' => 'index']);
  }

  /**
   * Show the form for creating a new resource.
   *
   * @return \Illuminate\Http\Response
   */
  public function create()
  {
    return response()->json(['name' => 'create']);
  }

  /**
   * Store a newly created resource in storage.
   *
   * @param  \Illuminate\Http\Request  $request
   * @return \Illuminate\Http\Response
   */
  public function store(Request $request)
  {
    return response()->json(['name' => 'store', 'payload' => $request->all()]);
  }

  /**
   * Display the specified resource.
   *
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function show($id)
  {
    return response()->json(['name' => 'show', 'id' => $id]);
  }

  /**
   * Show the form for editing the specified resource.
   *
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function edit($id)
  {
    return response()->json(['name' => 'edit', 'id' => $id]);
  }

  /**
   * Update the specified resource in storage.
   *
   * @param  \Illuminate\Http\Request  $request
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function update(Request $request, $id)
  {
    return response()->json(['name' => 'update', 'payload' => $request->all(), 'id' => $id]);
  }

  /**
   * Remove the specified resource from storage.
   *
   * @param  int  $id
   * @return \Illuminate\Http\Response
   */
  public function destroy($id)
  {
    return response()->json(['name' => 'destroy', 'id' => $id]);
  }
}

หลังจากปรับแก้ PhotoController.php เรียบร้อย ทำการ รันคำสั่ง

$ php artisan serve
Starting Laravel development server: http://127.0.0.1:8000

ทดสอบ RESTful API

curl http://127.0.0.1:8000/api/photos

{“name”:”index”}

curl http://127.0.0.1:8000/api/photos/create

{“name”:”create”}

curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"poolsawat.com\"}" http://127.0.0.1:8000/api/photos

{“name”:”store”,”payload”:{“name”:”poolsawat.com”}}

curl http://127.0.0.1:8000/api/photos/9999

{“name”:”show”,”id”:”9999″}

curl http://127.0.0.1:8000/api/photos/9999/edit

{“name”:”edit”,”id”:”9999″}

curl -X PUT -H "Content-Type: application/json" -d "{\"name\":\"poolsawat.com\"}" http://127.0.0.1:8000/api/photos/99999

{“name”:”update”,”payload”:{“name”:”poolsawat.com”},”id”:”99999″}

curl -X DELETE http://127.0.0.1:8000/api/photos/9999

{“name”:”destroy”,”id”:”9999″}

สรุปท้ายบทความ

การทำ RESTful API ด้วย Laravel จากที่ได้ใช้งานมาได้สักระยะ จากประสบการณ์ค่อนข้างชอบ เพราะด้วยตัวภาษาโปรแกรมมิ่ง เวลาจะ compile ไม่จำเป็นต้อง restart server เพราะเป็นภาษาคริป เลยถือเป็นข้อดีอย่างนึงสำหรับทำ prototype api ง่าย ๆ เพื่อนำเสนอลูกค้าหรือใช้งานกันเองภายในบริษัท ต้องการศึกษาเพิ่มเติมเกี่ยวกับการทำ Resource Controllers ของ Laravel ศึกษาได้ที่ลิ้งนี้ Resource Controllers

บทความต่อไปจะเป็นเรื่องอะไร คอยติดตามกันนะครับ

Cypress แนะนำอธิบาย 20 API Commands ที่ใช้งานบ่อย EP2

visit
ใช้สำหรับเข้า website test ถือว่าเป็น command ที่ต้องรู้ลำดับแรก ๆ ตัวอย่างการใช้งาน cy.visit(‘https://google.com‘) มี function callback ที่น่าสนใจ คือ onBeforeLoad , onLoad สามารถใส่ behavior function การทำงานในส่วนนี้ได้

get
ระบุ selector element ของหน้าจอ เพื่อจะเป็น target ในการ action command ถัดไปกรณี ต้องการให้ระบุ element เพื่อทำงานอะไรบ้างอย่าง เช่น set value ในช่อง text ,set text บน label เป็นต้น ตัวอย่างการเรียกใช้งาน cy.get(‘{selector}’) โดย selector คือ input ,.class ,#id ,[name=””] เป็นต้น

type
ใช้สำหรับ set value ให้กับ input tag ต่าง ๆ เช่น input [text ,date ,phone ,password ,number ,…] รวมถึง textarea ได้อีกด้วย ตัวอย่างการใช้งาน cy.get(‘{selector}’).type(‘commade type’)

satang pro referral

clear
ใช้ clear value ในช่อง input ตัวอย่างการใช้งาน cy.clear()

as
ทำหน้าที่เป็น command กำหนด alias เป็นการกำหนด reference ให้กับ command ที่กำลังสนใจ หรือ ใช้งานซ้ำ ๆ สามารถ reuse เรียกใช้งานซ้ำได้ ตัวอย่างการใช้งาน cy.(‘{selector}’).as(‘mySelector’) อยากต้องการระบุอ้างอิงถึงก็เพียงแค่ cy.get(‘@mySelector’) ถึงชื่อ alias ก็สามารถใช้ทำงานได้เหมือนการเรียก cy.get(‘{selector}’) นั้นอีกครั้ง

check
ใช้กับ input [radio ,checkbox] กำหนด state ให้กับ element เหล่านี้เป็นการ checked ตัวอย่างการใช้งาน cy.get(‘{selector}’).check() โดยที่ {selector} นั้นต้องเป็น radio ,checkbox command ถึงจะทำงานได้ถูกต้อง

click
ชื่อก็บอกอยู่แล้ว กำหนด event click ให้กับ element ที่กำลังสนใจ เกือบแทบทุก element สามารถเรียก command นี้ได้เกือบหมด (ถ้าไม่ถูก Disabled) ตัวอย่างการใช้งานจะใช้งานร่วมกับ command get คือ cy.get(‘{selector}’).click()

contains
สำหรับเทียบค่าข้อความที่คาดหวังว่าจะมีใน element ที่เรากำลังสนใจ (คล้ายกับการ LIKE ‘%%’) ตัวอย่างการใช้งาน cy.get(‘{selector}’).contains(‘ข้อความ’) ถ้าหา element ที่มีข้อความไม่พบจะตก fail case เลย

debug
กำหนดการ pause ให้การรัน testcase หยุดช่ำขณะเพื่อดูการทำงานของโปรแกรม สามารถกด continues ต่อไปได้ เมื่อต้องการให้โปรแกรมทำงานต่อไป

each
การ LOOP elements ที่ได้จากการ get(‘{selector}’) แล้วต้องการที่จะทำงานอะไรบ้างอย่างกับ element ที่ selector ได้ ก็จะเรียก command each นี้ โดย command จะมี callback function ให้เขียนคำสั่งอื่นได้ ๆ ตัวอย่างการใช้งาน cy.get(‘{selector}’).each(()=> {/* todo something */ })

eq
ตัวย่อของ equal ที่แปลว่า เท่ากัน case ที่ใช้งานบ่อย คือการเทียบหาลำดับของ elements ที่พบได้มากกว่า 1 element ตัวอย่างการใช้งาน cy.get(‘{selector}’).eq(0) โดย 0 คือ index ของ elements ที่เจอ

find
ต้องการค้นหา element เป้าหมายเพียง element เดียว หลังจากที่ selector ได้ elements มากกว่า 1 หรือจะระบุ selector ตั้งแต่แรกเริ่ม get เลยก็ได้ ตัวอย่างการใช้งาน cy.get(‘{selector}’).find(‘.class-unique’) command จะ return element มาเพียง 1 element เท่านั้น

fixture
command เรียก static resource date ต่าง ๆ ที่ถูกจัดเก็บไว้ที่ path cypress/fixture/** โดยมีประเภทไฟล์ที่สามารถเรียกไฟล์ได้หลายนามสกุลด้วยกัน เช่น .json ,.csv ,.txt ,.png ,.pjg เป็นต้น ตัวอย่างการเรียกใช้งาน cy.fixture(‘{file-name}’).then(response => {/* doto something */ })

log
แสดง variable หรือ text ออกทางหน้าจอ run ui คล้ายกับ console.log(”) ของ javascript เพื่อแต่พื้นที่การแสดงอยู่คนละที่กัน

next
ถัดไป จะ selector element ตำแหน่งถัดไปของ element ก่อนเรียก command นี้ โดยสังเกตุจะเป็น element level เดียวกับ element ที่เรียก command นี้ ตัวอย่างการใช้งาน cy.get(‘{selector}’).next() จะได้ element ถัดไปทันที

prev
ก่อนหน้า เมื่อมี element ถัดไป (next command) ก็ต้องมีการหา element ก่อนหน้า ดังนั้น command นี้จะทำหน้าที่หา element ก่อนหน้าที่จะเรียก command นี้

not
เป็นนิเสธน์ ใช้กรอง element ไม่สนใจ elements ที่ถูกเรียกภายใน command นี้ ตัวอย่างการเรียกใช้งาน cy.get(‘{selectors}’).not(‘{.not-use-element}’)

request
ใช้เรียก api หรือ url ที่ต้องการจะเข้าถึงแบบ background run (คล้าย ๆ การเรียก AJAX ของ jQuery เหมือนกัน ตัวอย่างการเรียกใช้งาน cy.request(‘{url-api}’) หรือจะเพิ่มการเรียก method อื่น ๆ ด้วย cy.request({method : ‘POST’ , url : ‘{url-api}’})

should
ถือว่าเป็นพระเอกของเรื่องการทำ automated testing เลยก็ว่าได้เพราะถ้าการเขียน testscript ไม่มีการเขียนการคาดหวังผลลัพธ์ เพื่อที่จะใช้ตรวจสอบข้อมูลต่าง ๆ ละก็ไม่รู้ว่าการเขียน automated test จะมีประโยชน์อะไรเลย ตัวอย่างการใช้งานก็เรียกว่าทำได้หลากหลายมาก เช่น cy.get(‘{selector}’).should(‘have.text’,'{ข้อความ}’) หรือแม้แต่จะเรียกแบบ callback function ก็ทำได้โดย cy.get(‘{selector}’).should($selector => { /* todo something */ }) โดยการตรวจสอบในรุปแบบอื่น ๆ สามารถเข้าไปดูได้จากลิ้งนี้ command should

submit
ใส่ action Submit ให้กับ element FORM ตัวอย่างการเรียกใช้งาน cy.get(‘selector*’).submit() *คือ selector ของ FORM

wait
จะเรียกว่าเป็น command ที่เป็นตัวช่วยชีวิตกรณีการไม่แน่ใจว่าการทำงานจะเสร็จเมื่อไหร่ มักจะใส่ wait นี้เพื่อรอการทำงานก่อนหน้านี้ให้แน่ใจว่าทำงานเสร็จเรียบร้อย ตัวอย่างการใช้งาน cy.wait(4000) คือ ให้รอ 4 วินาที และจึงทำงานต่อไปได้

และนี้คือ API commands ของ Cypress ที่น่าจะเป็น commands ที่เรียกใช้งานได้บ่อย ที่สุด บทความต่อไปจะไปล้วงลึกการทำงานของ Spy/Stub ที่เป็นเรื่องที่ไม่รู้ไม่ได้ เลยจริง ๆ รอติดตามนะครับ

Cypress แนะนำโครงสร้างของ Project EP1

โครงสร้างไฟล์ Project

หลังจากที่ได้ อ่านบทความเรื่อง Cypress Automate Testing (E2E) 101 แล้ว มาต่อถึงบทความนี้ จะมาอธิบายโครงสร้างไฟล์ โฟรเดอร์ ต่าง ๆ ของ Project Cypress กัน

package.json
ไฟล์แรกที่ต้องมีการถูกแก้ไขเมื่อครั้งเริ่ม Setup Project จะมีการเพิ่มโค๊ดเป็นที่แรก ด้วย “cypress”: “^4.9.0” เพื่อติดตั้ง dependencies ของ Project ยังมี “scripts” : {} ที่ยังสามารถ กำหนดค่า script command ลัดของเราเองเพื่อที่จะทำให้เรียกใช้งานได้ง่ายขึ้น npm run {script}

cypress.json
ไฟล์สำคัญของ Project Cypress สำหรับจัดการค่า Config ของ Project สำหรับ config ค่าต่าง ๆ เช่น
– baseUrl กำหนด url ของ website ที่ใช้ทดสอบ
– screenshotsFolder ระบุตำแหน่งที่เก็บไฟล์ภาพหลังจากใช้ command cy.screenshot() ค่า default “cypress/screenshots”
– browsers กำหนด browser ต่าง ๆ สำหรับใช้ทดสอบ Chrome , Firefox ,Edge ,Electron
– อื่น ๆ

satang pro referral

cypress/fixtures
เก็บไฟล์ static เช่น json ,csv ,txt ,image ฯลฯ โดยต้องการเรียกใช้งานเพียงเรียก command cy.fixture(“{filename}”).then(()=>{ /* todo */ }) ถือว่าสะดวกมาก ๆ

cypress/integration
เก็บ testscript ไฟล์ สามารถสร้างโฟรเดอร์ แบ่งย่อย testscript ได้ จะเก็บไฟล์ {filename}.spec.js

cypress/plugins
ไฟล์เพิ่ม plugins รวมถึง cy.task({taskname},()=>{}) ที่อยากจะจัดการ จะมาเขียนเก็บไว้ที่ไฟล์นี้

cypress/supports
โฟรเดอร์ที่มีไฟล์ตั้งต้น 2 ไฟล์ คือ index.js ,commands.js
– commands.js เก็บ Custom Commands ของเราที่อยากจะแชร์แบ่งให้ testscript อื่น ๆ เข้าถึงและเรียกใช้งานได้
– index.js เป็นไฟล์ที่สำหรับ import ไฟล์ที่อยากต้องการเรียกใช้งาน จะมาเพิ่มในไฟล์นี้

บทความหน้าจะมาพูดถึงการเรียก API commands ของ Cypress ซึ่งมีให้ใช้งานเยอะมาก ถ้าเราเรียกใช้งานให้เหมาะสมกับสถานะการ

#jQuery[1] ตั้งค่าภาษาไทยให้ Datatable

Datatable

แนะนำการตั้งค่าเครื่องมือต่าง ๆ บน Jquery Datatable ให้เป็นภาษาไทย

Datatable เป็น Plugin ที่พัฒนาจาก Jquery เป็นความสามารถทั้งหมดที่การแสงดข้อมูลผ่านหน้าตารางควรมีแต่โดย Default ของ Datatable นี้จะเป็นภาษาอังกฤษ ซึ่งมันก็ดีแต่ถ้าอยากจะตั้งค่าให้เป็นภาษาไทยละจะทำอย่างไร

ตัวอย่างโค๊ด

<!DOCTYPE html>
<html>
   <head>
    <title>Datatable Thai Language</title>

<link rel="stylesheet" href="[local_path]/css/datatables/dataTables.bootstrap.min.css' ?>">
<link rel="stylesheet" href="[local_path]css/datatables/dataTables.css' ?>">
<link rel="stylesheet" href="[local_path]css/datatables/responsive.bootstrap.min.css' ?>">

<script type="text/javascript" src="[local_path]js/datatables/jquery.dataTables.min.js'?>"></script>
<script type="text/javascript" src="[local_path]js/datatables/dataTables.bootstrap.min.js'?>"></script>
<script type="text/javascript" src="[local_path]js/datatables/dataTables.responsive.min.js'?>"></script>
<script type="text/javascript" src="[local_path]js/datatables/responsive.bootstrap.min.js'?>"></script>

   </head>
<body>

  <table class="bordered table">
    <thead>
      <tr>
      <th>code_id</th>
      <th>name_th</th>
      <th>addr_th</th>
      <th>phone</th>
      <th>email</th>
      <th>contact</th>
      <th>join_date</th>
      <th>#</th>
      <th>#</th>
      </tr>
   </thead>
   <tbody>
<!-- ข้อมูล-->
   </tbody>
</table>

</body>
</html>

<script type="text/javascript">

// เพิ่มส่วนนี้เข้าไปจะถือว่าเป็นการตั้งค่าให้ Datatable เป็น Default ใหม่เลย

$.extend(true, $.fn.dataTable.defaults, {
    "language": {
              "sProcessing": "กำลังดำเนินการ...",
              "sLengthMenu": "แสดง_MENU_ แถว",
              "sZeroRecords": "ไม่พบข้อมูล",
              "sInfo": "แสดง _START_ ถึง _END_ จาก _TOTAL_ แถว",
              "sInfoEmpty": "แสดง 0 ถึง 0 จาก 0 แถว",
              "sInfoFiltered": "(กรองข้อมูล _MAX_ ทุกแถว)",
              "sInfoPostFix": "",
              "sSearch": "ค้นหา:",
              "sUrl": "",
              "oPaginate": {
                            "sFirst": "เิริ่มต้น",
                            "sPrevious": "ก่อนหน้า",
                            "sNext": "ถัดไป",
                            "sLast": "สุดท้าย"
              }
     }
});

// เรียกใช้งาน Datatable function

$('.table').DataTable();

</script>

Datatable_th

เพียงเท่านี่เราก็จะได้หน้าตา Datatable ที่เป็นภาษาไทยแล้ววครับ