mVoter 2020 Backstage #3: Test of Time

Photo by noor Younis on Unsplash

ကျွန်တော်ဒီ Blog Post Series လေးမှာ ကျွန်တော်တို့ မြန်မာနိုင်ငံရဲ့ ၂၀၂၀ ပြည့်နှစ်အထွေထွေရွေးကောက်ပွဲ အတွက် တိကျမှန်ကန်တဲ့အချက်အလက်တွေပေးနိုင်ဖို့ ဖန်တီးခဲ့တဲ့ mVoter 2020 Android App လေးထဲက Tech Stack တွေ အကြောင်းရယ်၊ ဘာလို့ဒီ Tech Stack တွေကိုရွေးချယ်ခဲ့တယ်ဆိုတာကို တစ်ခြားသူတွေလည်း ဗဟုသုတရအောင် မျှဝေပေးသွားပါမယ်။ ဒီအပိုင်းမှာ လွယ်မယောင်ယောင်နဲ့ နည်းနည်းလေး ခေါင်းစားခဲ့တဲ့ Countdown Timer လေးအကြောင်းပြောပြမယ်။

တစ်ခြားအပိုင်းတွေဖတ်ချင်တယ်ဆို -
#1- Conductors
#2- App Update Mechanism


ကျွန်တော်တို့ mVoter 2020 ရဲ့ မဲပေးနည်း ကိုကြည့်မယ်ဆို ထိပ်ဆုံးမှာ ၂၀၂၀ အထွေထွေ ရွေးကောက်ပွဲစမဲ့အချိန်ထိ ဘယ်လောက်အချိန်ကျန်သေးတယ်ဆိုတာကို ပြထားပေးတာကိုတွေ့ရမယ်။ Source ကတော့ Github မှာကြည့်လို့ရတယ်။

ဒီ countdown လေး

ပထမဆုံးအနေနဲ့ ၂၀၂၀ အထွေထွေ ရွေးကောက်ပွဲစမဲ့အချိန် ဟာ မြန်မာစံတော်ချိန်ဖြစ်တယ်။ ဒီတော့ ကျွန်တော်တို့က constant သတ်မှတ်ထားတဲ့ ဒီ အချိန်ကို Asia/Rangoon အဖြစ်သတ်မှတ်ပေးရတယ်။ JSR-310 API က TimeZone တွေအဆင်ပြေအောင် လုပ်ထားပေးတယ်။

val electionDate = ZonedDateTime.of(
  year = 2020,
  month = Month.NOVEMBER.value,
  dayOfMonth = 8,
  hour = 6,
  minute = 0,
  second = 0,
  nanoOfSecond = 0,
  zone = ZoneId.of("Asia/Rangoon")
)

တကယ်လို့ ၂၄ နာရီအထက်ဆို ရက်နဲ့ပြမယ်၊ ၂၄ နာရီ အောက်ရောက်သွားပြီဆိုရင်တော့ နာရီ, မိနစ်, စက္ကန့်ဖြစ်သွားမယ်။ သုညဖြစ်သွားပြီ ဆိုရင် မပြတော့ဘူး။ ဒီ State သုံးခုကို ရေးဖို့ Sealed Class ကိုသုံထားတယ်။ ပြောရမယ်ဆိုသူက Enum အမိုက်စားပဲ။ CountDown State လို့ပြောလိုက်ရင် ဒီသုံးခုကလွဲပြီးဖြစ်လို့ကိုမရလို့ ကျွန်တော်ရေးထားတဲ့ Countdown API ကိုသုံးတဲ့သူအနေနေဲ့ ဘာလဲဆိုတာ ရှင်းရှင်းလင်းလင်းသိရတယ်။

sealed class CountDown {

  object ShowNone : CountDown()

  data class ShowDay(val dayLeft: Int) : CountDown()

  data class ShowHourMinSec(
    val hour: Int,
    val minute: Int,
    val seconds: Int
  ) : CountDown()

}

ခေါင်းစားစရာက ရက်ဘယ်လောက်လိုသေးတယ်လို့ ပြောတဲ့အချိန်မှာ စကားပြောကို သုံးမှာလား၊ အတိအကျတွက်မှာလားဆိုတာ စဖြစ်တယ်။ ဘယ်လိုကွာသွားလဲ။ ပြောရလွယ်အောင် အောက်တိုဘာ ၁၅ မှာ မွေးနေ့ပွဲလုပ်မယ်လို့ သတ်မှတ်လိုက်မယ်။ ဒါဆို အောက်တိုဘာ ၁၀ ရက်နေ့ ၅ နာရီလောက်မှာ ဘော်ဒါတွေ့ရင်း စကားပြောကြတဲ့အချိန်မှာ “မင်းမွေးနေ့ပွဲ လုပ်ဖို့ ၅ ရက်လိုသေးတယ်နော်” လို့ပြောကြတယ်။ ဒါပေမဲ့ တကယ့် အတိအကျကွာခြားချက် ဆိုရင်တော့ ၁၀ ရက်နေ့ ညနေ ၅ နာရီကနေ ၁၅ ရက်နေ့ ၁ နာရီက ၄ ရက်နဲ့ ၂၁ နာရီ (4.875 ရက်) ဖြစ်တယ်။ အိုကေ ဒသမကို အနီးစပ်ဆုံးယူမယ်ထားဦးတော့ အဖြေက ၅ ရက် ဆိုတော့ မှန်သေးတယ်ထင်ရတယ်။ တကယ်လို့ ခြားနားချက်က ၄ ရက် ၁၀ နာရီ ဆိုတဲ့ အချိန်မှာ 4.42 ရက် ကို အနီးစပ်ဆုံးယူရင် အဖြေက ၄ ရက် ဖြစ်နေပြန်တယ်။ ဒီတော့ အတိအကျတွက်ပြီး အနီးစပ်ဆုံးယူတာက User ဘက်ကကြည့်ရင် အချိန်တွေက လွဲနေ သယောင်ဖြစ်နေမယ်။ Ceiling သို့မဟုတ် floor ကို ယူရင် လည်း ဒီလိုပဲ ကြုံရဦးမှာပဲ။ ဒီတော့ ကျွန်တော်တို့က ရက်ကိုပြတဲ့အခါမှာ အတိအကျကွာခြားချက် ကိုမယူပဲ စကားပြောကွာခြားချက်နဲ့ ပြမယ်ဆိုပြီး သတ်မှတ်လိုက်တယ်။ ဒီတော့ ကျွန်တော်တို့က အရင်ဆုံးအနေနဲ့ ဘာလုပ်လဲဆိုတော့ ဝင်လာတဲ့ from နဲ့ to ရက်ခြားနားချက်မရှာခင်မှာ toLocalDate ဆိုပြီး နာရီ၊မိနစ်၊စက္ကန့်တွေကို ဖျက်လိုက်တယ်။

နောက်တစ်ခုက Separation of Concern။ ကျွန်တော်တို့တွက်မှာကတော့ မြန်မာစံတော်ချိန်နဲ့။ ဒါပေမဲ့ ကျွန်တာ်တို့ရဲ့ Time Difference Calculator က Timezone specific ဖြစ်နေတယ်ဆို တစ်ခြားနေရာ ပြန်သုံးမရဘူး။ သူ့တာဝန်က ကွာခြားချက်တွက်ဖို့၊ မလိုအပ်တဲ့ domain specific condition ဖြစ်တဲ့ မြန်မာစံတော်ချိန်ကို ထည့်စဉ်းစားစရာမလိုဘူး။ ဆိုတော့ ဝင်လာသမျှ DateTime တွေကို UTC ပြောင်းပလိုက်တယ်။

ဒါပေမဲ့ ရက်/နာရီအတိအကျကွာခြားချက် ကို ထပ်ရှာရမယ်။ ဟန် နေပါဦး ခုနကပြောတေ့ အတိအကျမလိုဘူးဆို။ ဘယ်လိုဖြစ်ရပြန်တာလဲ? ဟဲဟဲ ဘာလို့လိုနေတာလဲဆိုတော့ ကျွန်တော်တို့ရဲ့ ဒုတိယ case ဖြစ်တဲ့ ၂၄ နာရီအောက်ရောက်သွားတဲ့အချိန်ဆိုတာကို တွက်ဖို့ကျတော့ ကျွန်တော်တို့က အချိန်တွေကို မဖယ်ထားပဲ တိကျတဲ့ နာရီခြားနားချက်ကို ထပ်ရှာရမယ်။ 1.2 ရက် ဆိုတာ ၂၄ နာရီအောက် မရောက်သေးဘူး။ 0.99 ဆိုရင်တော့ ရောက်သွားပြီ။ ဒီ condition check အတွက် ရက်/နာရီ အတိအကျ ကွာခြားချက်ကို ရှာပေးရမယ်။

val preciseHourUntil = fromDateTime.until(toDateTimeAtUtc, ChronoUnit.HOURS)

//Check if more than 24 hours remaining
if (preciseHourUntil >= 24.0) {
  return ShowDay(
    dayDifference.toDays()
      .toInt()
  )
} else {
  //DO STUFFS
}

အိုကေ ဒါဆိုရက် ပြဖို့အတွက်အဆင်ပြေသွားပြီ။ ၂၄ ရီအောက်ကို ဆက်မသွားခင်မှာ ကျွန်တော် အနေနဲ့ ကျွန်တော်ရေးထားတာမှန်သွားကြောင်း ဘယ်လိုပြောနိုင်လဲဆိုတာကိုပြောပြပေးမယ်။ ကျွန်တော်က ဒီလိုမျိုး အချိန်တွေ မတွက်ခင်မှာကတည်းက Test Case လေးတွေဖန်တီးထားပြီး ဒီ Test Case တွေအတွက် ဘာအဖြေရမယ်ဆိုတာကို သတ်မှတ်ထားလိုက်တယ်။ ပြီးတော့ ဒီအဖြေကို check ဖို့ unit test လေးတွေရေးထားလိုက်တယ်။ ပြီးတော့မှ တကယ်တွက်တဲ့ code ကိုစရေးတယ်။

ဒါဆို ကျွန်တော်တို့ ရေးနေတဲ့အချိန်မှာကော ရေးပြီးရင်ကော ကိုယ်ရေးထားတာမှန်လား မှားလား သိချင်ရင် App ကြီးတစ်ခုလုံး ပြန် run၊ system time တွေသွားပြင် လုပ်စရာမလိုပဲ test case လေးတွေ pass လားကြည့်လိုက်ရင်ရပြီ။

ဒါဆိုရင် ၂၄ နာရီအောက် case တွေအတွက်လည်း Test လေးတွေဆက်ရေးမယ်။

@Test
fun test23Hours59Minutes59SecondsLeft() {
  val fromDateTime = LocalDateTime.of(2020, Month.NOVEMBER, 7, 6, 0, 1)
  val toDateTime = LocalDateTime.of(2020, Month.NOVEMBER, 8, 6, 0, 0)

  val expected = CountDown.ShowHourMinSec(23, 59, 59)
  val actual = calculator.calculate(fromDateTime, toDateTime)

  Assert.assertEquals(expected, actual)
}

နာရီ မိနစ် စက္ကန့် ခြားနားချက် တွက်တာကတော့ ရှင်းပါတယ်။

val secondsUntil = formDateTimeAtUtc.until(toDateTimeAtUtc, ChronoUnit.SECONDS)

if (secondsUntil < 0) {
  return ShowNone
} else {
  val durationUntil = Duration.between(
    formDateTimeAtUtc,
    toDateTimeAtUtc
  )

  val hourUntil = durationUntil.toHours()
  val minuteUntil = durationUntil.minusHours(durationUntil.toHours())
    .toMinutes()
  val secondUntil = durationUntil.minusMinutes(durationUntil.toMinutes())
    .seconds

  return ShowHourMinSec(
    hour = hourUntil.toInt(),
    minute = minuteUntil.toInt(),
    seconds = secondUntil.toInt()
  )
}

Test တွေ အဆင်ပြေပြီဆိုတော့မှ တကယ့် ဖုန်းပေါ်မှာ run ကြည့်ပြီး လျှောက်စမ်းကြည့်တယ်။ တွက်ထားတဲ့ logic မှန်တယ်ဆိုတာ ကျွန်တော်ကသိနေပြီးသားဆိုတော့ UI မှာပြတာ လှမလှ ဆိုတာကိုပဲစစ်တယ်၊။ အဲမှာ 0 seconds ရောက်သွားပြီးတာနဲ့ UI ပြောင်းသွားတက အဆင်ပြေရဲ့လားစစ်တုန်း edge case သွားတွေ့တယ်။ စက္ကန့်က 0 ဖြစ်သွားပြီး နောက်တစ်က္ကန့်ကြာသွားတဲ့အချိန်မှာ ချက်ချင်းမပျောက်သွားပဲ နောက်တစ်က္ကန့်စာ ဆက်ရှိနေတယ် ဒီတော့ -1 ဆိုပြီးသွားပေါ်နေတယ်။ ဘာလို့ပါလိမ့်ဆိုတော့ လိုက်ကြည့်တော့ nano seconds ကြောင့်ဖြစ်နေတယ်။ 1 seconds အပြည့်အဝ မကျော်သေးတဲ့ အချိန်မှာ တက်နေတဲ့ ပြဿနာလို့ခန့်မှန်းမိတယ်။ သေချာအောင် test လေးရေးကြည့်လိုက်တယ်။@Test

@Test
fun testDatePassedbyNanoSec() {
  val fromDateTime = LocalDateTime.of(2020, Month.NOVEMBER, 8, 6, 0, 0, nanOfSecond = 1)
  val toDateTime = LocalDateTime.of(2020, Month.NOVEMBER, 8, 6, 0, 0)

  val expected = CountDown.ShowNone
  val actual = calculator.calculate(fromDateTime, toDateTime)

  Assert.assertEquals(expected, actual)
}

အဲမှာ Test fail တော့မှ ဒီပြဿနာ သေချာပြီဆိုတာသိသွားတယ်။ Solution ကတော့

//Beforeval secondsUntil = formDateTimeAtUtc.until(toDateTimeAtUtc, ChronoUnit.SECONDS)

if (secondsUntil < 0) {
  return ShowNone
} else ..//After

val nanoSecondsUntil = formDateTimeAtUtc.until(toDateTimeAtUtc, ChronoUnit.NANOS)if (nanoSecondsUntil < 0) {
  return ShowNone
} else ..

secondsUntil အစား nanoSecondsUntil နဲ့ပြောင်းတွက်လိုက်တယ်ဆိုရင် ဒီ ပြဿနာမရှိတော့ဘူး။

Green Light 💚 💚

ဒါဆိုရင် ကျွန်တော်တို့ရဲ့ Countdown Calculator က လုံးဝမှန်တယ်ဆိုတာသေချာသွားပြီ။ ပြစရာ သက်သေအနေနဲ့လည်း Test Case ရှိတယ်။ ဒီလိုပြဿနာမျိုးမှာဆို ခင်ဗျား အနေနဲ့ Test ရေးကိုရေးသင့်တယ်။ Countdown ကြီးလည်နေတာကို ထိုင်စောင့်ပြီး စစ်မယ်ဆိုရင်တော့ ခင်ဗျား အချိန်တွေ မလိုပဲ အများကြီးကုန်နေမယ်။ Codebase တစ်ခုလုံးမှာ Test Coverage အပြည့်အဝ အကုန်ရှိဖို့မလိုပေမဲ့ ဒီလိုမျိုး တွက်ချက်တဲ့အပိုင်းတွေ ဆိုရင်တော့ Test လေးတွေ ရေးထားပါလို့ အကြံပေးချင်တယ်။ ဒီလောက်ဆိုရင်တော့ ကျွန်တော်တို့ Countdown ကိုဘယ်လိုအတွေးနဲ့ ဘယ်လိုမှန်အောင် ရေးခဲ့လဲဆိုတာ သိပြီလို့ထင်ပါတယ်။ Code တွေက Github မှာ Open Source ပေးထားပါတယ်။ mVoter 2020 ကို Download လုပ်မယ်ဆိုရင်တော့ ကျွန်တော်တို့ mvoterapp.com မှာ အခမဲ့ရယူလိုရပါတယ်။


ကျွန်တော်ရေးတဲ့၊ ပြောတဲ့ အကြောင်းတွေသဘာကြတယ်ဆို ကျွန်တော်နဲ့ ကိုလင်းဖြိုးပေါင်းလုပ်နေတဲ့ ပထမဆုံး ဗမာလိုပြောတဲ့ နည်းပညာ Podcast ဖြစ်တဲ့ Techshaw Podcast လေးရှိပါတယ်။ ကျွန်တော်တို့တွေ နည်းပညာအကြောင်းတွေ အစုံအလင် ပြောဖြစ်ပါတယ်။ Follow မလုပ်ရသေးရင် လုပ်လိုက်ဦးနော်။

Show Comments