Android端末の時刻を変更するJUnitテストルール(Api23~34対応)

投稿日:  更新日:

スケジュール管理やアラーム機能を提供するアプリのテストで、端末の時刻を自由に変更できたら便利です。

ですので、端末の時刻を変更するテストルールを作成してみました。

この記事は、以前に投稿した「Android端末の時刻を変更するJUnitテストルール」を改訂したものです。

API23~34で動作するように、テストルールの記述を改良しています。

※環境:Android Studio Jellyfish | 2023.3.1
    Kotlin 1.9.0
    Compose Compiler 1.5.1
    androidx.test.uiautomator:uiautomator 2.3.0
    androidx.test.ext:junit 1.1.5
    androidx.test:rules 1.5.0
    junit:junit 4.13.2

スポンサーリンク

時刻を変更する方法

時刻を変更する方法として、次の3つがあげられます。

  • dateツールを使う
  • プログラム中から行う
  • SettingsアプリをGUI操作

dateツールを使う

adb shellコマンドでAndroidの仮想端末へ接続し、dateツールを使って変更します。

adb shellコマンド
$ adb devices                   -> 起動中のエミュレータを調べる
List of devices attached
emulator-5554   device

$ adb -s emulator-5554 shell    -> 仮想端末へ接続
emu64xa:/ $ su                  -> Rootへ移行
emu64xa:/ #                     -> プロンプト#がRootの証し

使い方は次の通りです。コマンドライン中に“SET”があれば時刻を設定し、無ければ現在時刻を表示します。

# date -h
usage: date [-u] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]

Set/get the current date/time. With no SET shows the current date.

Default SET format is "MMDDhhmm[[CC]YY][.ss]"

※MM ... 月、DD ... 日、hh ... 時、mm ... 分

例えば、12月31日23時59分に変更してみましょう。

# date
Fri May  3 07:03:04 GMT 2024
# date 12312359
Tue Dec 31 23:59:00 GMT 2024
# date
Tue Dec 31 23:59:02 GMT 2024

ただし、この方法は「Root権限が必要」です。

UiAutomatorのUiDeviceからadb shellを発行できますが、この場合はRootになれないので使えません。

プログラム中から行う

システムのリソースへアクセスする権限を、プログラム(アプリ)へ持たせる必要があります。

しかし、一般の開発者に、このようなプログラミングは許されていません。

ですので、この方法は不可能です。

端末にプリインストールされているSettingsアプリは、この権限を持ちます。端末のベンダーがアプリの実装を行っているからです。

SettingsアプリをGUI操作

UiAutomatorのUiDeviceを使うと、ユーザが行う端末の操作と同等なことが、プログラム中からできます。

これを使い、Settingsアプリを開いてGUI操作で時刻を変更します。

時刻変更のピッカー

時刻変更のGUIには、2タイプのピッカーがあります。ダイヤルとドラムタイプです。

【ダイヤルタイプ】

ダイヤルタイプのピッカー
【ドラムタイプ】

ドラムタイプのピッカー

実機において、採用するピッカーはSettingsアプリの実装を行うベンダーが決めます。

Settingアプリはプリインストールされるため、OSのバージョンアップ以外でピッカーが変わることはありません。

しかし、AVD(エミュレータ)において、採用するピッカーは起動時に決まるようです。

ピッカーの選択に法則性が見つけられません。ですので、ランダムなのかも知れません。

ピッカーのタイプはリソース名により判別が可能です。

/* Pickerのタイプ */
internal enum class PickerType(val res: String) {
    DIAL("android:id/radial_picker"),
    CALENDAR("android:id/date_picker_day_picker"),
    DRUM("android:id/numberpicker_input")
}

internal fun getTimePickerType(
    uiDev: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
): PickerType {
    return if(hasObjByRes("android:id/timePicker")) {
        val _isDrum = uiDev.hasObject(By.res(PickerType.DRUM.res))
        val _isDial = uiDev.hasObject(By.res(PickerType.DIAL.res))
        when {
            _isDrum -> PickerType.DRUM
            _isDial -> PickerType.DIAL
            else -> throw UiObjectNotFoundException("Unknown picker !")
        }
    }
    else
        throw UiObjectNotFoundException("Picker not found !")
}
サブ関数(hasObjBy***)
internal fun hasObjByRes(
    resource: String,
    uiDev: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
) = uiDev.wait(Until.hasObject(By.res(resource)), 2000)

ピッカーの操作

UiAutomatorのUiDeviceにより、ピッカーを操作します。

ドラムタイプは特別な処理が必要です。

  • AMPMの境界(例:23時⇒0時、24時制)を跨がない。
    ⇒ AMPMドラムの繰り上がりを避けるためです。
  • 時の境界(例:59分⇒0分)を跨がない。
    ⇒ 時ドラムの繰り上がりを避けるためです。
ダイヤルドラム
「AMPM時分」の文字座標を計算して、その座標をクリックします。

/**
 * ダイヤルタイプのピッカー
 */
private const val OutsideRingRatio = 0.8    // 24表記の文字の位置
private const val InsideRingRatio = 0.5     // 同上

internal fun setTime24WithDial(
    hour: Int,
    min: Int,
    uiDev: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
){
    require(hour >= 0) {"year >= 0"}
    require(min >= 0) {"month >= 0"}

    when(val _tHour = hour % 24) {
        0 -> setHourWithDial(uiDev, InsideRingRatio, 12, 0)
        in 1..11 -> setHourWithDial(uiDev, OutsideRingRatio, 12, _tHour)
        12 -> setHourWithDial(uiDev, OutsideRingRatio, 12, 12)
        in 13..23 -> setHourWithDial(uiDev, InsideRingRatio, 12, _tHour % 12)
    }

    val _tMin = min % 60
    setMinWithDial(uiDev, OutsideRingRatio, 60, _tMin)
}

internal fun setTime12WithDial(
    hour: Int,
    min: Int,
    uiDev: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
){
    require(hour >= 0) {"year >= 0"}
    require(min >= 0) {"month >= 0"}

    val _tHour = hour % 12
    setHourWithDial(uiDev, OutsideRingRatio, 12, _tHour)

    val _tMin = min % 60
    setMinWithDial(uiDev, OutsideRingRatio, 60, _tMin)

    val _tAM = if(hour / 12 % 2 == 0) "AM" else "PM"
    findObjByTextAndClick(_tAM, uiDev)
}

private fun setHourWithDial(
    uiDev: UiDevice,
    ringRatio: Double,
    resolution: Int,
    position: Int
) {
    val _radial = findObjByRes("android:id/radial_picker", uiDev)
    val _rect = _radial.visibleBounds
    val _center = _radial.visibleCenter
    val _radius = (_rect.height().toDouble() / 2.0) * ringRatio
    _radial.recycle()

    val _andle = 2.0 * PI * (position.toDouble() / resolution.toDouble())
    val _x = _radius * cos(_andle - PI / 2.0)           // 3時の位置が 0[rad]、0時の位置へ補正
    val _y = _radius * sin(_andle - PI / 2.0)

    uiDev.click(_center.x + _x.roundToInt(), _center.y + _y.roundToInt())
    uiDev.waitForIdle(2000)
}

private fun setMinWithDial(
    uiDev: UiDevice,
    ringRatio: Double,
    resolution: Int,
    position: Int
) {
    val _radial = findObjByRes("android:id/radial_picker", uiDev)
    val _rect = _radial.visibleBounds
    val _center = _radial.visibleCenter
    val _radius = (_rect.height().toDouble() / 2.0) * ringRatio
    _radial.recycle()

    // 文字盤の文字は、近いほど吸着力が強い
    //   ⇒ 文字へ吸着しないように、文字との距離を調整
    val _toPosition = when(position % 5) {
        1 -> position.toDouble() + 0.3
        2 -> position.toDouble()
        3 -> position.toDouble()
        4 -> position.toDouble() - 0.3
        else -> position.toDouble()
    }
    val _toAndle = 2.0 * PI * (_toPosition / resolution.toDouble())
    val _toX = _radius * cos(_toAndle - PI / 2.0)       // 3時の位置が 0[rad]、0時の位置へ補正
    val _toY = _radius * sin(_toAndle - PI / 2.0)

    // 文字盤の文字間の値は、ドラッグ&移動して指定する
    //  ⇒ 文字へ吸着しないように、文字上を通過しない方向から移動
    val _fromPosition = when(position % 5) {
        1, 2 -> position.toDouble() + 5.0
        3, 4 -> position.toDouble() - 5.0
        else -> position.toDouble()
    }
    val _fromAngle = 2.0 * PI * (_fromPosition / resolution.toDouble())
    val _fromX = _radius * cos(_fromAngle - PI / 2.0)   // 3時の位置が 0[rad]、0時の位置へ補正
    val _fromY = _radius * sin(_fromAngle - PI / 2.0)

    uiDev.drag(
        _center.x + _fromX.roundToInt(), _center.y + _fromY.roundToInt(),
        _center.x + _toX.roundToInt(), _center.y + _toY.roundToInt(),
        20
    )
    uiDev.waitForIdle(2000)
}
それぞれのドラムをスワイプして、AMPM時分に合わせます。

/**
 * ドラムライプのピッカー
 */
internal fun setTime24WithDrum(
    hour: Int,
    min: Int,
    uiDev: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
){
    require(hour >= 0) {"year >= 0"}
    require(min >= 0) {"month >= 0"}

    val _current = Calendar.getInstance()

    val _tHour = hour % 24
    val _tMin = min % 60
    val _cHour = _current[Calendar.HOUR_OF_DAY]
    val _cMin = _current[Calendar.MINUTE]

    val _area = findObjByRes("android:id/timePicker", uiDev)
    val _aMin = _area.visibleBounds.rightOf2()
    val _aHour = _area.visibleBounds.leftOf2()

    // 時刻の指定(時、分)
    setFieldWithDrum(uiDev, Calendar.HOUR_OF_DAY, _aHour, _cHour, _tHour)
    setFieldWithDrum(uiDev, Calendar.MINUTE, _aMin, _cMin, _tMin)

    _area.recycle()
}

internal fun setTime12WithDrum(
    hour: Int,
    min: Int,
    uiDev: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
){
    require(hour >= 0) {"year >= 0"}
    require(min >= 0) {"month >= 0"}

    val _current = Calendar.getInstance()

    val _tHour = hour % 12
    val _tMin = min % 60
    val _tAM = hour / 12 % 2    // 0:AM, 1:PM
    val _cHour = _current[Calendar.HOUR]
    val _cMin = _current[Calendar.MINUTE]
    val _cAM = _current[Calendar.AM_PM]

    val _area = findObjByRes("android:id/timePicker", uiDev)
    val _aAM = _area.visibleBounds.rightOf3()
    val _aMin = _area.visibleBounds.centerOf3()
    val _aHour = _area.visibleBounds.leftOf3()

    // 時刻の指定(AM/PM、時、分)
    setFieldWithDrum(uiDev, Calendar.AM_PM, _aAM, _cAM, _tAM)
    setFieldWithDrum(uiDev, Calendar.HOUR, _aHour, _cHour, _tHour)
    setFieldWithDrum(uiDev, Calendar.MINUTE, _aMin, _cMin, _tMin)

    _area.recycle()
}

private fun setFieldWithDrum(
    uiDev: UiDevice,
    field: Int,
    area: Rect,
    current: Int,
    target: Int
) {
    val _times = target - current

    if(_times != 0) {
        val _next = if(_times > 0) current + 1 else current - 1

        val _cLabel = when(field) {
            Calendar.AM_PM -> if(current > 0) "PM" else "AM"
            Calendar.HOUR_OF_DAY -> "%02d".format(current)  // 00,01,02,...,23
            Calendar.HOUR -> if(current == 0) "12" else current.toString() // 12,0,1,..,11
            Calendar.MINUTE -> "%02d".format(current)       // 00,01,02,...,59
            else -> current.toString()
        }
        val _nLabel = when(field) {
            Calendar.AM_PM -> if(target > 0) "PM" else "AM"
            Calendar.HOUR_OF_DAY -> "%02d".format(_next)  // 00,01,02,...,23
            Calendar.HOUR -> if(_next == 0) "12" else _next.toString() // 12,0,1,..,11
            Calendar.MINUTE -> "%02d".format(_next)       // 00,01,02,...,59
            else -> current.toString()
        }

        val _toObj = findObjByText(_cLabel, area, uiDev)
        val _to = _toObj.visibleCenter
        val _fromObj = findObjByText(_nLabel, area, uiDev)
        val _from = _fromObj.visibleCenter

        swipeDrum(uiDev, _from, _to, Math.abs(_times))

        _toObj.recycle()
        _fromObj.recycle()
    }
}
サブ関数(swipeDrum)
private fun swipeDrum(
    uiDev: UiDevice,
    from: Point,
    to: Point,
    times: Int
) {
    val _deltaY = ((from.y - to.y) * 0.3f).toInt() // スワイプ距離へ遊び
    repeat(times) {
        uiDev.swipe(from.x, from.y + _deltaY, to.x, to.y, 20)
        uiDev.waitForIdle(2000)
    }
}
サブ関数(findObjBy***)
internal fun findObjByRes(
    resource: String,
    uiDev: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
): UiObject2 {
    return uiDev.wait(Until.findObject(By.res(resource)), 2000)
        ?: throw UiObjectNotFoundException("ResourceName:\"${resource}\"")
}

internal fun findObjByText(
    text: String,
    uiDev: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
): UiObject2 {
    return uiDev.wait(Until.findObject(By.text(text)), 2000)
        ?: throw UiObjectNotFoundException("Text:\"${text}\"")
}

internal fun findObjByTextAndClick(
    text: String,
    uiDev: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
) {
    findObjByText(text, uiDev).apply {
        click()
        recycle()
    }
}
サブ関数(Rect.拡張関数)
private fun Rect.leftOf2() = Rect(left, top, left + width() / 2, bottom)
private fun Rect.rightOf2() = Rect(right - width() / 2, top, right, bottom)
private fun Rect.leftOf3() = Rect(left, top, left + width() / 3, bottom)
private fun Rect.centerOf3() = Rect(left + width() / 3, top, right - width() / 3, bottom)
private fun Rect.rightOf3() = Rect(right - width() / 3, top, right, bottom)
スポンサーリンク

Automaticモード

端末の時刻の制御方法に、「Automaticモード」があります。

Automaticモードは、ネットワーク(ntp)から時刻(日付を含む)を取得して、端末を正確な時刻へ自動調整する機能です。

Automaticモードの間は任意の時刻を設定することが出来ません。ですので、任意の時刻を設定したければ、Automaticモードの無効化が必要になります。

Automaticモード切替

有効⇒無効

    private fun _SetSystemTime(hour: Int, min: Int) {
        val _api = Build.VERSION.SDK_INT
        val _setTimeMesg = PageMesgs.getValue(_api).settime
        val _automaticMesg = PageMesgs.getValue(_api).auto
        val _okMesg = PageMesgs.getValue(_api).ok
        val _cancelMesg = PageMesgs.getValue(_api).cancel

        // 「Date & time」設定ページを開く
        showDateTimePage(context)

        // 時刻の自動(Network)設定を無効化
        findObjByText(_setTimeMesg, uiDev).apply {
            if (! isEnabled)
                findObjByTextAndClick(_automaticMesg, uiDev)
            recycle()
        }

        // 時刻の変更
        findObjByTextAndClick(_setTimeMesg, uiDev) // Pickerを開く
        val _picker = getTimePickerType(uiDev)
        Log.i(TAG, "PickerType(API${Build.VERSION.SDK_INT}) = ${_picker}")
        try {
            val _isHour24 = isHour24()
            Log.i(TAG, "Picker=${_picker} Hour24=${_isHour24}")
            when(_picker) {
                PickerType.DRUM ->
                    if(_isHour24 != null && _isHour24)
                        setTime24WithDrum(hour, min, uiDev);
                    else
                        setTime12WithDrum(hour, min, uiDev)
                PickerType.DIAL ->
                    if(_isHour24 != null && _isHour24)
                        setTime24WithDial(hour, min, uiDev)
                    else
                        setTime12WithDial(hour, min, uiDev)
                else -> {}
            }
            findObjByTextAndClick(_okMesg, uiDev)       // OKボタン
        } catch (e: UiObjectNotFoundException) {
            findObjByResAndClick(_cancelMesg, uiDev)    // CANCELボタン
            throw UiObjectNotFoundException("Time:${hour}:${min}")
        }

        // 「Date & time」設定ページを閉じる
        findObjByText(_setTimeMesg, uiDev).recycle()
        uiDev.pressBack()       // バックボタン押下
    }
サブ関数(isHour24)
/**
 * 24時間表示の有無を調べます。※targetContextが必須!
 *
 * @return false:無効、true:有効、null:ロケールのデフォルト(日本:12時間表示)
 */
fun isHour24(
    context: Context = InstrumentationRegistry.getInstrumentation().targetContext
) : Boolean? {
    val _resolver = context.getContentResolver()
    try {
        val _time_12_24 = Settings.System.getInt(_resolver, Settings.System.TIME_12_24)
        return (_time_12_24 == 24)
    }
    catch (e: Settings.SettingNotFoundException) {
        return null
    }
}

無効⇒有効

    private fun _SetAutomatic() {
        val _api = Build.VERSION.SDK_INT
        val _setTimeMesg = PageMesgs.getValue(_api).settime
        val _automaticMesg = PageMesgs.getValue(_api).auto

        // 「Date & time」設定ページを開く
        showDateTimePage(context)

        // 時刻の自動(Network)設定を有効化
        findObjByText(_setTimeMesg, uiDev).apply {
            if (isEnabled)
                findObjByTextAndClick(_automaticMesg, uiDev)
            recycle()
        }

        // 「Date & time」設定ページを閉じる
        findObjByText(_setTimeMesg, uiDev).recycle()
        uiDev.pressBack()       // バックボタン押下
    }

Settingsページの文面の違い

APIにより、Settingsアプリの文面が異なります。マップで管理し、APIで切り替えます。

/* Settingのページで用いられるメッセージ */
internal data class MesgData(
    val auto: String,
    val setdate: String,
    val settime: String,
    val ok: String,
    val cancel: String
)
internal val PageMesgs = mapOf(
    23 to MesgData("Automatic date & time", "Set date", "Set time", "OK", "CANCEL"),
    24 to MesgData("Automatic date & time", "Set date", "Set time", "OK", "CANCEL"),
    25 to MesgData("Automatic date & time", "Set date", "Set time", "OK", "CANCEL"),
    26 to MesgData("Automatic date & time", "Set date", "Set time", "OK", "CANCEL"),
    27 to MesgData("Automatic date & time", "Set date", "Set time", "OK", "CANCEL"),
    28 to MesgData("Automatic date & time", "Set date", "Set time", "OK", "CANCEL"),
    29 to MesgData("Use network-provided time", "Date", "Time", "OK", "Cancel"),
    30 to MesgData("Use network-provided time", "Date", "Time", "OK", "Cancel"),
    31 to MesgData("Set time automatically", "Date", "Time", "OK", "Cancel"),
    32 to MesgData("Set time automatically", "Date", "Time", "OK", "Cancel"),
    33 to MesgData("Set time automatically", "Date", "Time", "OK", "Cancel"),
    34 to MesgData("Set time automatically", "Date", "Time", "OK", "Cancel")
)
スポンサーリンク

「Data & TIme」ページを開く

日付の変更を行うには、GUI操作を行う前にSettingsアプリの「Data & TIme」ページを開く必要があります。

/**
 * Settingsアプリの「Time & Date」ページを開きます。
 *
 * @param context コンテキスト
 */
internal fun showDateTimePage(
    context: Context = InstrumentationRegistry.getInstrumentation().context
) {
    val intent = Intent(Settings.ACTION_DATE_SETTINGS)
    intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
    context.startActivity(intent)
}
スポンサーリンク

テストルールの作成

ここまで、説明した内容を統合して、テストルールを作成します。

仕様は次の通りです。

  • サポートするAPI:API≧23
  • 12時制、24時制は自動判別
  • 時刻の変更はテスト中のみ有効、テスト後はAutomaticモードが有効
/**
 * システム時刻を設定するルールを提供します.
 * テストが終わると自動的にAutomaticモード(Networkに経由による時刻合わせ)に変更されます.
 *
 * @param hour 時(0,1,2,...,23)  ※24時制
 * @param min 分
 */
@TargetApi(23)
class SysTimeTestRule(
    var hour: Int = -1, var min: Int = -1
) : TestRule {

    val context = InstrumentationRegistry.getInstrumentation().context
    val uiDev = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

    // ----- ルール本体 -----------------------------------------------
    override fun apply(base: Statement, description: Description): Statement {
        return object : Statement() {
            override fun evaluate() {
                try {
                    setSysTime()        // 前処理
                    base.evaluate()     // テストの実施
                } finally {
                    setAutomatic()      // 後処理
                }
            }
        }
    }

    /**
     * システム時刻をネット経由で自動的に設定します.
     */
    fun setAutomatic() {
        Log.i(TAG, "setAutomatic")
        _SetAutomatic()
        hour = -1
        min = -1
    }

    /**
     * システム時刻を設定します.
     * @param hour 時(0,1,2,...,23)  ※24時制
     * @param min 分
     */
    fun setSysTime(hour: Int = this.hour, min: Int = this.min) {
        Log.i(TAG, "setSysTime ${hour}:${min}")
        if (hour >= 0 && min >= 0) {
            _SetSystemTime(hour, min)
            this.hour = hour
            this.min = min
        }
    }

    // ----------------------------------------------------------------
    private fun _SetAutomatic() { ... }
    private fun _SetSystemTime(hour: Int, min: Int) { ... }
}

※テストルールの作り方については「JUnitのテストルールの作り方」を参照

スポンサーリンク

テストルールの使用

使用方法はその他のテストルールと変わりません。@Ruleを付けてプロパティを宣言するだけです。

コンストラクタで時刻の指定関数で時刻の指定
class Rule_SysTime_Test1 {

    @get:Rule
    var timeRule = SysTimeTestRule(11, 59)

    @Before
    fun setUp() {
    }

    @After
    fun tearDown() {
    }

    // ----------------------------------------------------------------
    @Test
    fun A1_コンストラクタで時刻の指定_1159() {
        val _time = LocalTime.now()
        Truth.assertThat(_time.hour).isEqualTo(11)
        Truth.assertThat(_time.minute).isEqualTo(59)
    }
}
class Rule_SysTime_Test2 {

    @get:Rule
    var timeRule = SysTimeTestRule()

    @Before
    fun setUp() {
    }

    @After
    fun tearDown() {
    }
	
    // ----------------------------------------------------------------
    @Test
    fun B1_関数で時刻の指定_午前0916() {
        timeRule.setSysTime(9, 16)

        val _time = LocalTime.now()
        Truth.assertThat(_time.hour).isEqualTo(9)
        Truth.assertThat(_time.minute).isEqualTo(16)
    }
}
スポンサーリンク

関連記事:

新たなテストルールの作り方を紹介します。 JUnitとAndroidJUnitにはいくつかの有用なテストルールがすでに用意されています。しかし、十分とは言えません。テストルールを自作してテスト環境を機能拡張しましょう! ...
多言語対応したアプリのテストで、対応地域の動作を確認するためにロケールが変更できたら便利です。なので、ロケールを変更するテストルールを作成してみました。 ...
GUIを用いたアプリのテストで、テストの開始前にアニメーションを無効化することが定石となっています。理由は「テストの安定性を確保するためである」と、ドキュメントに記載されています。 この無効化の処理を自動で行えたら便利です。なので、アニメーションを無効にするテストルールを作成してみました。 ...
スケジュール管理やアラーム機能を実装するアプリのテストで、端末の時刻を自由に変更できたら便利です。なので、端末の時刻を変更するテストルールを作成してみました。 この記事は「Android端末の時刻を変更するJUnitテストルール(Api23~34対応)」で改訂されました。 ...
スケジュール管理やアラーム機能を実装するアプリのテストで、端末の日付を自由に変更できたら便利です。なので、端末の日付を変更するテストルールを作成してみました。 ※この記事は「Android端末の日付を変更するJUnitテストルール(Api23~34対応)」で改訂されました。 ...
地図を表示して自身の地理的位置(Geolocation)を管理するアプリのテストで、疑似的に端末の位置が変更できたら便利です。なので、端末の位置を変更するテストルールを作成してみました。 ...
スケジュール管理やアラーム機能を提供するアプリのテストで、端末の日付を自由に変更できたら便利です。 ですので、端末の日付を変更するテストルールを作成してみました。 この記事は、以前に投稿した「Android端末の日付を変更するJUnitテストルール」を改訂したものです。 API23~34で動作するように、テストルールの記述を改良しています。 ※環境:Android Studio Jellyfish | 2023.3.1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.test.uiautomator:uiautomator 2.3.0     androidx.test.ext:junit 1.1.5     androidx.test:rules 1.5.0     junit:junit 4.13.2 ...
スポンサーリンク