ุ6 Refectoring code

ที่ผ่านมา ทุกครั้งเวลาเขียนเทส เราจะสร้าง object ของคลาสที่ต้องการจะเทสขึ้นมา แล้วก็จะทดสอบการเท่ากัน. ในตัวอย่างนี้ เราจะมีโปรแกรมเปลี่ยน องศาฟาเรนไฮซ์ เป็น เซลเซียส และก็กลับกัน โดยที่เราจะมี  Interface ตัวหนึ่ง แล้วจะมีคลาส 2 คลาสคือ Celsius กับ Fahrenheit ดังรูป




Celsius ทำการ implement เรียบร้อยแล้ว แต่ Fahrenheit ยังไม่ได้ทำ คือเราจะทำการเขียนเทสก่อน แล้วค่อยเอาโค๊ดที่ได้ไปแปะไว้ที่ Fahrenheit ทีหลัง

ส่วนโค๊ดก็เป็นดังนี้
โค๊ดสำหรับ Calculator
package com.javachef.core;
public interface Calculator
{
double toCelsius();
double toFahrenheit();
double getValue();
void setValue(double value);
}
view raw gistfile1.java hosted with ❤ by GitHub
โค๊ดสำหรับ Celsius
package com.javachef.core;
public class Celsius implements Calculator
{
protected double c;
@Override
public double toCelsius()
{
return this.c;
}
@Override
public double toFahrenheit()
{
return ( c * 9 ) / 5 + 32;
}
@Override
public double getValue()
{
return this.c;
}
@Override
public void setValue(double value)
{
this.c = value;
}
}
view raw gistfile1.java hosted with ❤ by GitHub
โค๊ดสำหรับ Fahrenheit
package com.javachef.core;
public class Fahrenheit
{
}
view raw gistfile1.java hosted with ❤ by GitHub
โค๊ดสำหรับ เทส
package com.javachef.core;
import static org.junit.Assert.assertEquals;
import org.junit.Ignore;
import org.junit.Test;
public class CalculatorTest
{
@Test
public void testCtoF()
{
Celsius celcius = new Celsius();
Calculator cel = celcius;
cel.setValue(100);
assertEquals(212, cel.toFahrenheit(), 0);
assertEquals(100, celcius.c, 0);
}
@Test
public void testFtoC()
{
throw new RuntimeException();
}
}
view raw gistfile1.java hosted with ❤ by GitHub

ณ จุดนี้ เวลารัน คุณก็จะได้ อันหนึ่งที่บอกว่าเทสไม่ผ่าน ที่ไม่ผ่านก็เพราะว่าทำการ throw new RuntimeException(); แต่เราต้องการเทส function  double toCelsius(); จาก interface ดังนั้นเราก็ต้องมีคลาส Fahrenheit เพื่อจะได้ทำการ implement ฟังก์ชั่น toCelsius ก่อนอื่น เราก็มาดีไซน์กันก่อนว่าจะให้ทำงานยังไง เราก็เริ่มเขียนเทสก่อนเลยครับ

throw new RuntimeException(); ในที่นี้จะเป็นตัวบอกเราว่าเราทำงานไปถึงไหน มีอะไรทียังรอ. คุณอาจเขียนแบบนี้ก็ได้คือ throw new RuntimeException("Waiting for implement");

ว่าแล้วก็เขียนเทสก่อนสำหรับ Fahrenheit  กันเลยครับ

@Test
public void testFtoC()
{
Fahrenheit fahrenheit = new Fahrenheit();
Calculator fah = fahrenheit;
fah.setValue(212);
assertEquals(100,fah.toCelsius(),0);
assertEquals(212,fahrenheit.f , 0);
}
view raw gistfile1.java hosted with ❤ by GitHub

แต่ว่าตอนนี้ก็ยังรันไม่ผ่าน เพราะคลาส Fahrenheit เราไม่ได้สร้างอะไรไว้เลย ไม่ได้ implements แม้กระทั้ง Calculator  ว่าแล้วก็เริ่มกันเลยครับ

package com.javachef.core;
public class Fahrenheit implements Calculator
{
protected double f;
@Override
public double toCelsius()
{
return ( f - 32 ) * 5 / 9 ;
}
@Override
public double toFahrenheit()
{
return this.f;
}
@Override
public double getValue()
{
return this.f;
}
@Override
public void setValue(double value)
{
this.f = value;
}
}
view raw gistfile1.java hosted with ❤ by GitHub
พอรันเทสเสร็จ ก็จะผ่านหมด ถ้าไม่หมด ก็ลองดูที่คุณ implement ว่ามีอะไรผิดหรือไม่ จนกว่าเทสเคสจะผ่าน

กลับมาที่ CalculatorTest.java คุณจะเห็นว่า ทุกครั้งที่เราต้องเขียนเทส เราจะสร้าง object ของ Calculator ขึ้นมารัวๆ . Junit เข้าใจวัยรุ่นตรงจุดนี้ ก็เลยสร้าง annotation อีกอันหนึ่งชื่อ @Before ซึ่งตัวนี้หมายความว่า ทุกครั้งที่ Junit จะทำการทดสอบเทสที่เราเขียนไว้ใต้ @Test , Junit จะเรียก @Before ขึ้นมาก่อนทุกครั้งไป.

โอเคแล้วมันหมายความว่าหยั่งใด ก็จะหมายความว่า หากคุณเซตอัพ @Before ไว้แล้ว คราวนี้พอจะเขียนเทสอันต่อไปก็เริ่มเขียนโค๊ดต่อจาก @Before ในส่วนของ @Test ได้เลย ไม่งงนะคนดี ไปดูโค๊ดกันเถอะตัวเอง

package com.javachef.core;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
public class CalculatorTest
{
private Celsius celcius;
private Fahrenheit fahrenheit;
@Before
public void init()
{
celcius = new Celsius();
fahrenheit = new Fahrenheit();
}
@Test
public void testCtoF()
{
Calculator cel = celcius;
cel.setValue(100);
assertEquals(212, cel.toFahrenheit(), 0);
assertEquals(100, celcius.c, 0);
}
@Test
public void testFtoC()
{
Calculator fah = fahrenheit;
fah.setValue(212);
assertEquals(100,fah.toCelsius(),0);
assertEquals(212,fahrenheit.f , 0);
}
}
view raw gistfile1.java hosted with ❤ by GitHub


เวลาเขียนเทส ควรจะเทสเป็นเรื่องๆ ไป ไม่ควรเทสหลายเรื่องในเคสเดียวกัน เพื่อเราจะหาบักได้ง่ายเวลาอะไรมีปัญหาขึ้นมา


พอถึงตรงนี้ คุณจะเริ่มคุ้นกับการเขียนเทสเคสขึ้นมาบ้างแล้ว อันที่จริง เราจะไม่เขียนโค๊ดแล้วมาเทสนะครับ แต่จะเขียนเทสก่อนแล้วค่อยโค๊ด !

แม่นแล้วครับ ศัพท์เทกนิคคือ Test-Driven Development  เอาสั้นๆง่ายๆก่อนนะ แล้วเราจะลงลึกหลังเมื่อถึงเวลา แต่ตอนนี้ก็จะประมาณว่า สมมุติคุณอยากใช้โค๊ดแบบไหน ก็เขียนโค๊ดแบบนั้นลงไปในเทสเคสก่อน แล้วจึ่งค่อยทำการ implement เพื่อให้ได้ในสิ่งที่ต้องการ เหมือนโค๊ดข้างบน

โพสต่อไปเราจะมาดูกันเรื่องการเทสเรื่อง exception  และ เทสว่าโปรแกรมทำงานภานในเวลาที่กำหนดหรือไม่ ! น่าสนใจใช่ไหม ไปชมโพสต่อไปกันเลย