Android Instrumentation测试离线案例

cld4siwp  于 2023-09-29  发布在  Android
关注(0)|答案(7)|浏览(120)

在我的仪器测试中,我使用Robotium。大多数情况下,我能够测试一切,但离线情况。
一旦我禁用数据(使用adb,F8快捷键在模拟器等。...)测试断开。它在设备/模拟器中继续,但没有报告结果。
所以,我有一个想法,只把应用程序在离线模式,而不是整个设备。问题是我不知道怎么...
使用iptablesApi我需要root我的设备。我读到过Mobiwol应用程序使用某种VPN来限制应用程序的互联网访问,而不需要root设备。

问题Mobiwol应用如何阻止每个应用的互联网连接?或者有其他方法可以离线测试apk?
2014年12月30日编辑

我忘了说,我可以离线运行测试,但我必须在设备处于离线状态时开始测试。目前,我将我的测试分为离线和在线测试。在运行ONLINES之后,我执行著名的adb kill-serveradb start-server。之后我执行OFFLINES。

wgx48brx

wgx48brx1#

只是提出一些建议,因为这里似乎有不同的问题。1)如果你想做的只是在运行OFFLINE测试用例之前关闭数据,你可能想简单地尝试使用robotium本身来完成。
示例:对于WiFi:

WifiManager wifi=(WifiManager)solo.getCurrentActivity().getSystemService(Context.WIFI_SERVICE);
wifi.setWifiEnabled(false);

对于移动的数据(使用反射):

ConnectivityManager dataManager=(ConnectivityManager)solo.getCurrentActivity().getSystemService(Context.CONNECTIVITY_SERVICE);

Method dataClass = ConnectivityManager.class.getDeclaredMethod(“setMobileDataEnabled”, boolean.class);
dataClass.setAccessible(true);
dataClass.invoke(dataManager, true);

您可以在运行OFFLINE套件中的单个测试用例之前,在setup()方法中执行上述两个调用。完成OFFLINE套件中的所有测试用例后,您可以在最后的teardown()方法中启用WiFi/DATA。
2)看看你在OP中发布的应用程序,似乎它:

  • 根据操作系统版本使用ipTables
  • 根据UID为所有需要WiFi/数据的应用程序创建脚本头
  • 应该从包管理器中获取设备上安装的应用程序列表沿着任何隐藏的应用程序等。
  • 再次根据用户选择的黑名单执行脚本,并使用用户所需的规则覆盖ipTable中的现有规则。

很肯定,虽然必须有相当困难的代码所有这些..听起来容易得多的形式要点。
希望这对你有所帮助。
附言:如果你确实想出了一些东西,请发布一个更新的答案,想知道你是如何使它工作的

mfuanj7w

mfuanj7w2#

在花了几个小时试图做类似于user2511882的解决方案,我仍然有一个异常,因为缺少权限(是的,“修改系统设置”权限被激活)。
最后我用UI automator来做:

public static void setAirplaneMode(boolean enable)
{
    if ((enable ? 1 : 0) == Settings.System.getInt(getInstrumentation().getContext().getContentResolver(),
            Settings.Global.AIRPLANE_MODE_ON, 0))
    {
        return;
    }
    UiDevice device = UiDevice.getInstance(getInstrumentation());
    device.openQuickSettings();
    // Find the text of your language
    BySelector description = By.desc("Airplane mode");
    // Need to wait for the button, as the opening of quick settings is animated.
    device.wait(Until.hasObject(description), 500);
    device.findObject(description).click();
    getInstrumentation().getContext().sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}

您需要在androidTest清单文件中包含ACCESS_NETWORK_STATE:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

别忘了在测试后禁用它。
如果您有英语以外的其他语言,您需要将“飞行模式”更改为您的语言文本。因为我有几个翻译,我从一个resource字符串中读取它。

2fjabf4q

2fjabf4q3#

LinkedIn Test Butler有一个很棒的库,您可以通过简单的调用启用,禁用WiFi和移动的数据:

TestButler.setGsmState(false);
TestButler.setWifiState(false);

此库的主要优点是它不需要在您的清单中获得任何权限,有关更多详细信息,请参阅项目网站:
https://github.com/linkedin/test-butler

t9eec4r0

t9eec4r04#

抱歉,如果我过于简单化了,但是把手机/模拟器置于飞行模式怎么样?通过实际的用户界面。我就是这么做的。

mitkmikd

mitkmikd5#

使用@Illyct的解决方案(请投票支持他们的答案https://stackoverflow.com/a/64765567/191761

InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand("svc wifi disable")
InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand("svc data disable")
0s7z1bwu

0s7z1bwu6#

这里有一个解决方案,它使用UiAutomator从下拉状态栏中启用或禁用“飞行模式”,如果启用,则关闭所有网络。它适用于大多数Android操作系统版本,并使用用户Aorlinn(2019年10月12日)的部分答案。

app/build.gradle

dependencies {
    androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
}
AndroidManifest.xml

不需要额外的权限,甚至不需要<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />,因为UiAutomator只需要点击状态栏UI中的一个按钮。因此,应用程序不需要直接访问来修改设备的Wifi或移动的数据设置。

Kotlin

import android.os.Build
import android.provider.Settings
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObjectNotFoundException
import androidx.test.uiautomator.Until
import org.hamcrest.Matchers.`is`
import org.junit.Assert
import org.junit.Assume.assumeNoException
import org.junit.Assume.assumeThat
import java.io.IOException

private var airplaneModeOn = 0
private val OFF = 0
private val ON = 1
private var airplaneModeButtonPosition: Point? = null

/**
 * Turn off the Internet connectivity by switching "Aeroplane mode" (flight mode) on. From:
 * https://stackoverflow.com/questions/27620976/android-instrumentation-test-offline-cases#68956544
 */
@Test
fun checkAppWillWork_withNoInternetConnection() {
    try {
        airplaneModeOn = getCurrentAirplaneModeSetting()
    } catch (e: Exception) {
        e.printStackTrace()
        assumeNoException("Cannot retrieve device setting of 'Airplane Mode'. Aborting the test.", e)
    }

    // Check that Airplane mode is off
    assertThat(airplaneModeOn, `is`(OFF))

    // Press the "Aeroplane mode" button on the 'Quick Settings' panel
    toggleAeroplaneModeButton()

    // Verify Airplane mode is on
    assumeThat("Cannot change the 'Aeroplane Mode' setting. Test aborted.", airplaneModeOn, `is`(ON))
    assertThat(airplaneModeOn, `is`(ON))

    // Do some tests when the Internet is down
    // Note: Switch the Internet back on in cleanup()
}

/**
 * Turn the Internet connectivity on or off by pressing the "Aeroplane mode" button. It opens the
 * status bar at the top, then drags it down to reveal the buttons on the 'Quick Settings' panel.
 */
@Throws(UiObjectNotFoundException::class)
private fun toggleAeroplaneModeButton() {
    try {
        airplaneModeOn = getCurrentAirplaneModeSetting()
    } catch (e: SecurityException) {
        e.printStackTrace()
        Assert.fail()
    } catch (e: IOException) {
        e.printStackTrace()
        Assert.fail()
    }

    // Open the status bar at the top; drag it down to reveal the buttons
    val device = UiDevice.getInstance(getInstrumentation())
    device.openQuickSettings()

    // Wait for the button to be visible, because opening the Quick Settings is animated.
    // You can use any string here; You only need a time delay wait here.
    val description = By.desc("AeroplaneMode")
    device.wait(Until.hasObject(description), 2000)

    // Search for and click the button
    var buttonClicked = clickObjectIfFound(device,
            "Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
    if (!buttonClicked) {
        // Swipe the Quick Panel window to the LEFT, if possible
        val screenWidth = device.displayWidth
        val screenHeight = device.displayHeight
        device.swipe((screenWidth * 0.80).toInt(), (screenHeight * 0.30).toInt(),
                     (screenWidth * 0.20).toInt(), (screenHeight * 0.30).toInt(), 50)
        buttonClicked = clickObjectIfFound(device,
                "Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
    }
    if (!buttonClicked) {
        // Swipe the Quick Panel window to the RIGHT, if possible
        val screenWidth = device.displayWidth
        val screenHeight = device.displayHeight
        device.swipe((screenWidth * 0.20).toInt(), (screenHeight * 0.30).toInt(),
                     (screenWidth * 0.80).toInt(), (screenHeight * 0.30).toInt(), 50)
        clickObjectIfFound(device,
                "Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
    }

    // Wait for the Internet to disconnect or re-connect
    device.wait(Until.hasObject(description), 6000)

    // Close the Quick Settings panel, using the guide from:
    // https://stackoverflow.com/questions/22634446/sending-intent-to-broadcastreceiver-from-adb
    UiDevice.getInstance(getInstrumentation()).executeShellCommand(
        "am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS")

    // Close the Quick Settings panel. This is the old way, which will give
    // a deprecation warning for using Intent.ACTION_CLOSE_SYSTEM_DIALOGS
    // getInstrumentation().context
    //        .sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))

    // Verify the change in the device settings
    try {
        airplaneModeOn = getCurrentAirplaneModeSetting()
    } catch (e: SecurityException) {
        e.printStackTrace()
        Assert.fail()
    } catch (e: IOException) {
        e.printStackTrace()
        Assert.fail()
    }
}

/**
 * On Android 8/9, use an 'adb shell' command to retrieve the "Airplane Mode" setting, like this:
 *
 *
 * `adb shell settings list global | grep airplane_mode_on   ==>   "airplane_mode_on=0"`
 *
 *
 * (But note that grep is not available on the Android shell. See guidance URLs below)
 *
 *
 *  * https://www.reddit.com/r/tasker/comments/fbi5ai/psa_you_can_use_adb_to_find_all_the_settings_that/
 *  * https://stackoverflow.com/questions/33970956/test-if-soft-keyboard-is-visible-using-espresso
 *
 *
 * On all other Android OS versions, use `Settings.System.getInt()` to retrieve the "Airplane Mode" setting.
 * It sets `airplaneModeOn = 1 (true)` or `airplaneModeOn = 0 (false)`
 *
 * @throws IOException if `executeShellCommand()` didn't work
 * @throws SecurityException if `Settings.System.getInt()` didn't work
 */
@Throws(IOException::class, SecurityException::class)
private fun getCurrentAirplaneModeSetting(): Int {
    if (Build.VERSION.SDK_INT in 26..28 /*Android 8-9*/) {
        val shellResponse = UiDevice
                .getInstance(getInstrumentation())
                .executeShellCommand("settings list global")

        airplaneModeOn = when {
            shellResponse.contains("airplane_mode_on=1") -> 1
            shellResponse.contains("airplane_mode_on=0") -> 0
            else -> throw IOException("Unsuitable response from adb shell command 'settings list global'")
        }

    } else {
        // Oddly this causes a SecurityException on Android 8,9 devices
        airplaneModeOn = Settings.System.getInt(
                getInstrumentation().context.contentResolver,
                Settings.Global.AIRPLANE_MODE_ON,
                0)
    }
    return airplaneModeOn
}

/**
 * Make UiAutomator search for and click a single button, based on its text label.
 *
 * Sometimes multiple buttons will match the required text label. For example,
 * when Airplane mode is switched on, "Mobile data Aeroplane mode" and "Aeroplane mode"
 * are 2 separate buttons on the Quick Settings panel, on Android 10+.
 * For Aeroplane mode, always click on the last matched item.
 *
 * @param textLabels You must supply the language variants of the button that you want to click,
 * for example "Cancel" (English), "Cancelar" (Spanish), "취소" (Korean)
 * @return True if a button was found and clicked, otherwise return false
 */
private fun clickObjectIfFound(device: UiDevice, vararg textLabels: String): Boolean {
    for (languageVariant in textLabels) {
        val availableButtons = device.findObjects(By.text(languageVariant))
        if (availableButtons.size >= 1) {
            if (airplaneModeButtonPosition == null) {
                availableButtons[availableButtons.size - 1].click()
                airplaneModeButtonPosition = availableButtons[availableButtons.size - 1].visibleCenter
                return true
            } else {
                // Use the stored position to avoid clicking on the wrong button
                for (button in availableButtons) {
                    if (button.visibleCenter == airplaneModeButtonPosition) {
                        button.click()
                        airplaneModeButtonPosition = null
                        return true
                    }
                }
            }
        }
    }
    return false
}

@After
fun cleanup() {
    // Switch the Internet connectivity back on, for other tests.
    if (airplaneModeOn == ON) {
        toggleAeroplaneModeButton()
        assertThat(airplaneModeOn, `is`(OFF))
    }
}

如果您的设备使用的语言与英语不同,则必须调整字符串"Aeroplane mode"。例如,您可以在这里查看大约70种语言的翻译:https://github.com/aosp-mirror/platform_frameworks_base/search?q=global_actions_toggle_airplane_mode(截至2023年9月,您必须登录GitHub帐户才能查看搜索结果)。

yacmzcpb

yacmzcpb7#

由于@user2511882的回答,您可以通过以下方式在Android X Test中使用Application context而不是Activity:

internal fun switchWifi(value: Boolean) {
val wifiManager = ApplicationProvider.getApplicationContext<YourApplicationClass>().getSystemService(Context.WIFI_SERVICE) as WifiManager
wifiManager.isWifiEnabled = value}

考虑到这种方法仅适用于API <= 28。还有其他方法,如使用UI AutomatorAPI > 28

相关问题