Android的测试支持库为测试Android应用提供了大量框架。该库提供了一组API快速构建和运行测试代码,包括JUnit4和功能用户界面(UI)测试。能够从Android Studio IDE中或命令行这执行。html
Android的测试支持库可经过Android SDK管理器获取。java
AndroidJUnitRunner:兼容JUnit 4测试运行器。 Espresso:UI测试框架;适合在单个应用的功能UI测试。 UI Automator:UI测试框架;适用于跨应用的功能UI测试及安装应用。python
AndroidJUnitRunner类是JUnit测试运行器,可让你在Android设备上执行JUnit3或JUnit4中风格的测试类,兼容Espresso和UI Automator测试框架。测试运行器加载测试包和应用,运行测试并报告测试结果。该类取代 InstrumentationTestRunner类(仅支持JUnit 3)。android
这个运行器的主要特色:git
JUnit支持github
得到Instrumentation信息正则表达式
测试筛选shell
测试分片数据库
要求的Android2.2(API 8)或更高。express
AndroidJunitRunner经过InstrumentationRegistry提供了访问instrumentation的API。
InstrumentationRegistry.getInstrumentation()返回当前正在运行的Instrumentation
InstrumentationRegistry.getContext()返回此Instrumentation软件包的上下文。
InstrumentationRegistry.getTargetContext()返回目标应用的应用上下文。
InstrumentationRegistry.getArguments()返回传递给此Instrumentation的参数Bundle。当你要访问命令行参数时很是有用。
测试运行器兼容JUnit3和JUnit4的(最高JUnit4.10)测试。在同一个包混淆JUnit 3和和JUnit4测试代码可能会致使不可预测的结果。instrumented JUnit 4测试类在设备或仿真器上运行,必须在前面加上@RunWith(AndroidJUnit4.class)
注释。
好比测试CalculatorActivity类中的加操做:
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnitRunner; import android.test.ActivityInstrumentationTestCase2; @RunWith(AndroidJUnit4.class) public class CalculatorInstrumentationTest extends ActivityInstrumentationTestCase2<CalculatorActivity> { @Before public void setUp() throws Exception { super.setUp(); // Injecting the Instrumentation instance is required // for your test to run with AndroidJUnitRunner. injectInstrumentation(InstrumentationRegistry.getInstrumentation()); mActivity = getActivity(); } @Test public void typeOperandsAndPerformAddOperation() { // Call the CalculatorActivity add() method and pass in some operand values, then // check that the expected value is returned. } @After public void tearDown() throws Exception { super.tearDown(); } }
InstrumentationRegistry类能够访问测试运行的信息。该类包括Instrumentation对象,目标应用上下文对象,测试应用上下文对象及传递到测试的命令行参数。
JUnit 4.x的测试可使用annotation来配置测试运行,并支持Androidannotation:
@RequiresDevice:物理设备上运行。
@SdkSupress:限定最低SDK版本。例如@SDKSupress(minSdkVersion=18)
。
@SmallTest,@MediumTest和@LargeTest:测试分级。
单个test suite能够分片,同一Instrumentation的同一分片能够做为一个组。每一个片都有索引号。当运行测试,使用-e numShards选项指定片数和-e shardIndex选项来指定要运行的片。
例如分红10个碎片,仅执行第二片测试,请使用如下命令:
adb shell am instrument -w -e numShards 10 -e shardIndex 2
Espresso提供了一组API来构建UI测试来测试用户流程。这些API让你写简洁和可靠运行的自动化UI测试。Espresso很是适合白盒自动测试,测试代码利用了被测应用的代码细节。
Espresso的主要特性:
视图匹配(View matching): 灵活的API用于查看和适配目标应用。
Action API:一套扩展的action API自动化UI交互。
UI线程同步(UI thread synchronization)以提升测试的可靠性。
要求Android2.2(API 8)或更高。
Espresso.onView()方法能够访问目标应用程序的UI组件并与之交互。该方法接受一个Matcher参数,搜索视图层来定位视图实例。定位方法能够基于类名、内容描述、R.id、显示的文本。好比
onView(withId(R.id.my_button));
若是搜索成功,onView()方法返回对应view的引用,可执行用户操做和断言。
AdapterView由子view动态生成。若是目标视图在AdapterView(ListView或GridView)中,onView()方法可能不起做用,由于可能只加载了一部分,Espresso.onData()则能够。
ViewActions能够执行视图点击(View clicks),滑动(Swipes),按键或者按钮(Key and button press)、文本输入(Typing text)、打开连接(Opening a link)。
// Type text into an EditText view, then close the soft keyboard onView(withId(R.id.editTextUserInput)) .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard()); // Press the button to submit the text change onView(withId(R.id.changeTextBt)).perform(click());
因为时间问题,在Android设备上测试随机失败。以前通常经过sleep和超时处理解决。Espresso测试框架处理Instrumentation和UI线程之间的同步,很好地解决了这些问题。
API参考:developer.android.com/reference/android/support/test/package-summary.html
测试参考:http://developer.android.com/training/testing/ui-testing/espresso-testing.html
UI Automator提供了一组API来构建基于交互UI的测试。API容许你执行操做,如打开设置菜单,很是适合黑盒自动化测试,测试代码不依赖于应用的内部实现。
主要特性:
UI Automator Viewer:检查的布局层次。
API来获取设备状态信息并执行操做。
API跨应用测试。
要求Android4.3(API等级18)或者更高。
uiautomatorviewer提供了一个方便的图形用户界面进行扫描和分析在Android设备上当前显示的UI组件。您可使用此工具来检查的布局层次和查看UI组件。
UiDevice 类能够访问设备并进行操做。你能够调用它的方法来访问设备属性,如当前的方向或显示尺寸。该UiDevice类也让您执行操做,例如:旋转设备;按下D- pad按钮;按Back、Home、Menu等;打开通知树栏;当前窗口截图等。好比按Home键:UiDevice.pressHome()。
更 多应用相关的API: UiCollection枚举容器的UI元素以计数,或经过文字(或属性等)定位子元素; UIObject表示是在设备上可见的UI元素; UiScrollable:为可滚动UI容器提供查找支持; UiSelector:查询一个或者多个UI元素; Configurator: 设置参数。好比:
// Initialize UiDevice instance mDevice = UiDevice.getInstance(getInstrumentation()); // Perform a short press on the HOME button mDevice().pressHome(); // Bring up the default launcher by searching for // a UI component that matches the content-description for the launcher button UiObject allAppsButton = mDevice .findObject(new UiSelector().description("Apps")); // Perform a click on the button to bring up the launcher allAppsButton.clickAndWaitForNewWindow();
API参考:http://developer.android.com/reference/android/support/test/package-summary.html
实例:http://developer.android.com/training/testing/ui-testing/uiautomator-testing.html
https://pypi.python.org/pypi/uiautomator 用python封装了uiautomator,操做起来更简单,推荐使用。
android { defaultConfig { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } }
启动Android SDK管理器。
在SDK管理器窗口,滚动到软件包列表的末尾,找到其余文件夹,若有必要,展开以显示其内容。
选择 Android Support Repository。
点击Install packages。
下载后安装支持库文件到您现有的Android SDK目录。该库文件位于你的SDK的子目录:<sdk>/extras/android/m2repository。
Android的测试支持库位于android.support.test包。
Gradle中使用:build.gradle文件中添加这些依赖关系:
dependencies { androidTestCompile 'com.android.support.test:runner:0.4' // Set this dependency to use JUnit 4 rules androidTestCompile 'com.android.support.test:rules:0.4' // Set this dependency to build and run Espresso tests androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' // Set this dependency to build and run UI Automator tests androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2' }
设置AndroidJUnitRunner为默认测试:
强烈建议您一块儿使用Android测试支持库与Android Studio IDE。 Android的Studio支持测试开发,如:
基于Gradle的灵活的构建系统,支持测试代码的依赖管理。
单个项目包含应用程序源代码和测试代码。
支持虚拟或物理设备的部署和运行测试,支持命令行或图形用户界面
更多介绍参见:http://developer.android.com/sdk/index.html
http://wiki.jikexueyuan.com/project/android-weekly/issue-145/android-ui-auto-testing.html
http://developer.android.com/intl/zh-cn/training/testing/ui-testing/espresso-testing.html
http://www.stevenmarkford.com/android-ui-testing-with-espresso-basics-tutorial/
http://www.csdn.net/article/2015-08-19/2825493-using-espresso-for-easy-ui-testing
做者博客:http://my.oschina.net/u/1433482/ python测试开发精华群 291184506 PythonJava单元白盒测试 144081101
android测试框架(Android Testing Framework)是开发环境的一部分,它提供了架构和强大的工具帮助你从单元到框架测试应用的各个方面。
本部分有点偏旧,若是和其余部份内容有冲突,以其余内容为准。
关键特性:
基 于 JUnit,可直接使用JUnit测试一些与Android AP不相关的类,或使用 Android的JUint 扩展来测试 Android 组件。若是你刚开始接触 Android 测试,能够先从 AndroidTestCase开始写一些通用目的的测试用例,而后再写较复杂的测试用例。
Android JUint扩展提供了对 Android 特定组件的测试类,提供了建立mock对象辅助方法及控制组件生命周期的方法。
Test suite包含在测试包中,操做和主程序包相似。
Eclipse ADT包含了建立和测试的SDK 工具,并可经过命令行在其它IDE使用。这些工具从被测试的应用读取信息并自动建立测试包的build 文件,mainfest文件和文件目录结构。
SDK 提供了moneyrunner(用python书写脚本)及Monkey(发送伪随机事件的UI压力测试工具)。
本文档描述了android测试框架的基础,包含测试结构、开发测试的API以及启动测试和查看测试结果的工具。本文假设你有android应用编程以及JUnit测试的基础。
测试框架图以下:
android的构建和测试工具假设测试项目都组织成相似测试、测试类、测试包和测试项目的标准结构。
android测试基于JUnit。一般JUnit测试就是测试“待测试应用”的方法。测试方法构成的类为test cases (或者test suites)。
JUnit 中编译测试源文件到class文件中。相似地android中用android的编译工具编译测试包中的测试源文件为class文件。在JUnit 中test runner来执行测试类。在android中使用测试工具加载测试包和被测应用,而后调用android的test runner。
测试项目就是一个目录或者eclipse 工程,可在其中新建源代码、manifest文件以及测试包的其它文件。建议使用Android tool建立测试项目:
自动配置InstrumentationTestRunner为测试执行器。必须使用InstrumentationTestRunner或者其子类来执行JUnit测试。
为测试包取合适的名字。若是被测应用为com.mydomain.myapp,Android tool自动命名测试包名为com.mydomain.myapp.test。
自动建立合适的构建文件、manifest 文件以及目录结构。
推荐的目录结构:
MyProject/ AndroidManifest.xml res/ ... (resources for main application) src/ ... (source code for main application) ... tests/ AndroidManifest.xml res/ ... (resources for tests) src/ ... (source code for tests)
注:这个在实际操做中每每根据IDE而不一样,请以具体IDE的实例为准。
Android测试API基于JUnit API 并扩展了instrumentation框架和Android特有的测试类。
使用JUnit的TestCase类可对未使用android API的类进行单元测试。TestCase也是AndroidTestCase(测试依赖于android的对象)的基类。 AndroidTestCase还提供了android特有的 setup、teardown以及其它帮助方法。
JUnit的Assert类能够显示结果,assert方法比较指望值与实际结果,在比较失败时抛出异常。android一样提供了扩展了比较类型的断言类,以及用来测试UI的断言类。
注意android测试API支持JUnit 3的代码风格,不支持JUnit 4。
android instrumentation是android系统中一系列控制方法或钩子(hooks)。这些钩子能够的正常生命周期内独立地控制组件。它一样能够控制android如何加载应用。
一般android组件会按照系统指定的生命周期来运行,例如Activity的生命周期开始于它被Intent激活,其onCreate()方法会被调 用,接下来是onResume(),当用户启动另一个应用,onPause()方法会调用 ,若是Activity调用finish()方法,它的onDestroy()方法也会被调用。android framework API不会提供方法让你在代码中直接调用这些回调方法,可是用instrumentation能够。
系统把应用中的全部组件运行在同一个进程中,你可让一些组件,好比content provider,运行在单独的进程中。可是没法强制应用与另外其余正在运行的应用运行在同一个进程中。
经过android imstrumentation,你能够在测试代码中直接调用回调方法,让你渗透组件的生命周期,就像调试。下面的测试代码演示了如何用instrumentation来测试Activity保存和恢复状态:
// Start the main activity of the application under test mActivity = getActivity(); // Get a handle to the Activity object's main UI widget, a Spinner mSpinner = (Spinner)mActivity.findViewById(com.android.example.spinner.R.id.Spinner01); // Set the Spinner to a known position mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION); // Stop the activity - The onDestroy() method should save the state of the Spinner mActivity.finish(); // Re-start the Activity - the onResume() method should restore the state of the Spinner mActivity = getActivity(); // Get the Spinner's current position int currentPosition = mActivity.getSpinnerPosition(); // Assert that the current position is the same as the starting position assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
代码中使用的关键方法是getActivity(),它属于android instrumentation API。调用该方法才会启动Activity。你能够提早配置测试所需的环境(test fixture)。
Instrumentation能够加载测试包和被测试应用到同一个进程,这样测试代码能够调用应用组件的方法,修改和检查组件中的域。
android提供了几个继承自TestCase和Assert的test case 类,它们都有andorid 特有的setup、teardown以及其它的辅助方法。
通用的test case类,继承了TestCase和Assert类。它提供了标准的JUnit中的setup()和teardown()方法,同时还有JUnit的Assert方法。另外它也提供了用来测试权限的方法以及经过清除必定的类引用来防止内存泄露的方法。
android测试框架的一个重要特色是组件test case类。它们有独特的setup和teardown及控制组件生命周期。同时它们也提供mock方法。
一、Activity Testing
二、Content Provider Testing
三、Service Testing
Android并无为 BroadcastReceiver提供单独的test case 类。能够经过测试发送Intent对象给它的组件来测试BroadcastReceiver,检查BroadcastReceiver回复是否正确。
用 ApplicationTestCase测试Application对象的setup和teardown。这些对象维护着应用程序包中全部组件信息的全局 状态,该test case 用于验证manifest 文件中的<application>元素是否正确配置。然而记住这个test case没法控制应用包组件的测试。
若是想在test case 类中使用 instrumentation 的方法,必须使用InstrumentationTestCase或者它的子类。Activity test case 继承该基类。
Android test case 类继承自JUnit,能够用断言来显示测试结果。assertion方法将测试返回的真实值和指望值进行比较,若是比较失败它会抛出AssertionException。用Assertion比打印log更方便,有更好的测试性能。
除了JUnit的Assert类的方法,测试API同时也提供了MoreAsserts和ViewAsserts类:
MoreAsserts包含更多强大的断言,例如进行正则表达式匹配的assertContainsRegex(String, String)。
ViewAsserts包含不少关于View的断言,例如它包含用来测试View是否在屏幕的特定的(x, y)位置的assertHasScreenCoordinates(View, View, int, int)方法,这些断言简化了UI中的几何和对准测试。
为了解决测试过程当中的依赖,Android提供了建立模拟系统对象的类,好比Context对象、 ContentProvider对象、ContentResolver对象以及Service对象。有些test case能模拟的Intent对象。经过使用这些模拟对象,你能够将测试与系统的其他部分隔离开,同时也知足了测试中的依赖,这些类都在包 android.test和android.test.mock中。
模拟对象经过打桩或者重载正常操做来实现将测试与正在运行的系统隔 离。例如MockContentResolver对象用它自有的与系统隔离的本地框架来代替一般的resolver框架。同时 MockContentResolver不使用notifyChange(Uri, ContentObserver, boolean)方法,这样测试环境之外的observer对象不会被意外触发。
模拟对象类也经过提供正常类的子类来知足测试的依赖,该子类除了你覆写的方法外其它都是不起做用的。例如,MockResources对象是Resources类的子类,每一个方法在调用时都会抛出异常。要使用它,你只须要重载须要的方法。
下面是Android中可用的模拟对象类:
MockApplication、 MockContext、MockContentProvider、MockCursor,、MockDialogInterface、 MockPackageManager和MockResources提供了简单有用的模拟策略(打桩),在调用时都会抛出 UnsupportedOperationException异常。使用它,你只须要重载须要的方法。
注意:MockContentProvider和MockCursor是API Level 8 中新加入。
MockContentResolver 经过屏蔽系统正常的resolver框架来为content provider 提供隔离的测试。MockContentResolver不是在系统中查找提供authority的content provider,而是使用本身的内部表,你必须显式地用addProvider(String, ContentProvider)方法将provider添加到表中。
经过这个特性能够将模拟的content provider与authority关联,新建provider对象但使用测试数据,你甚至能够设置provider的authority为null。 实际上MockContentResolver对象将你的测试与包含真实数据的provider隔离。你能够控制provider的功能并防止测试影响真 实数据。
android提供了两个Context类来提供测试:
IsolatedContext类提供隔离的Context,使用该Context的文件、目录和数据库操做都会在测试区域。尽管功能有限,但足以应对系统调用,容许在不影响当前设备上的真实数据的前提下测试应用的数据操做。
RenamingDelegatingContext 的Context的大部分功能都由现有的Context来处理,可是文件和数据库操做都由IsolatedContext来处理,隔离的部分使用一个测试 目录,而且建立特殊的文件和目录名,你能够本身控制命名,也可让constructor自动指定。该类为进行数据操做建立隔离区域提供了快捷办法,同时 不会影响Context其它的正常操做。
test case由test runner类运行,test runner加载test case类、初始化、运行及清理测试。android test runner必须配置,这样启动应用的系统工具能够控制测试包如何加载test case和被测试应用。通常在manifest文件中设定。
InstrumentationTestRunner是android中主要的test runner类,它扩展了JUnit test runner框架而且是已配置,可以执行任何android系统提供的test case 类而且支持全部类型的测试。
你 可指定测试包的manifest文件的<instrumentation>标签内容为Instrumentation 或其子类。InstrumentationTestRunner的代码在共享库android.test.runner中,因此它一般没有连接到你的代 码,必须在<uses-library>标签中指定。一般不须要手动去设定,Eclipse ADT以及android 命令行工具都会自动生成它们而且把它们加到测试包的manifest文件中。
注意:若是使用的是InstrumentationTestRunner以外的test runner,必须修改<instrumentation>标签并指向你想使用的类。
要运行InstrumentationTestRunner类必须用android 工具调用内部系统类。Eclipse ADT中这些类都会被自动调用,命令行工具执行测试的时用Android Debug Bridge (adb)运行这些类。
系 统类加载和启动测试包,杀掉被测试应用包正在运行的进程,而且从新加载被测试包的实体,而后它们把控制权交给 InstrumentationTestRunner,由它来执行测试包中的每一个test case。你也能够经过Eclipse ADT中的setting或者命令行工具中的flag来控制哪些 test case或者方法的运行。
既不是系统类也不是 InstrumentationTestRunner运行被测试应用,而是test case。它要么调用被测试包中的方法,要么调用它本身的方法以改变被测试包生命周期。应用程序彻底由test case 控制,测试开始前由test case来初始化测试环境,
关于更多的运行测试,能够参见 Testing from Eclipse with ADT和 Testing from Other IDEs。
Android 测试框架返回测试结果给启动测试的工具。在eclipse ADT中结果会在新的JUnit视图面板中显示,命令行会在STDOUT中显示。二者均可以看到显示每一个test case 名字和你所运行的方法的小结,同时会看到全部失败的断言,其中包含指向产生失败的测试代码所在行的连接。失败的断言同时也会列出指望值和实际值。
测试结果根据IDE不一样而有不一样。
SDK提供了两个应用测试工具:
UI/Application Exerciser Monkey,一般称为"monkey",向设备发送伪随机事件流(如击键、触控、手势)的命令行工具。你能够经过Android Debug Bridge (adb)来运行它,对应用进行压力测试而后报告遇到的错误。你能够经过每次使用相同的随机数种子来重复步骤。
monkeyrunner是一套API,基于Python。该API包含以下功能:链接到设备、安装和卸载软件包、截图、比较图片、运行应用对应的测试应用。经过该API,你能够写出功能强大复杂的测试,经过命令行工具monkeyrunner来运行。
测试环境须要同时处理android应用包名和java包标识符。它们都使用一样的命名格式,可是表明着彻底不一样的实体。
android 包名是.apk文件对应的一个独一无二的系统名字,由应用包的manifest文件中<manifest>标签中 的"android:package"属性来设定。测试包的名字必须和被测试包的名字不一样,一般android工具会用被测试包的名字后加 上".test"来做为测试包的名字。
测试包也会用包名来定位它所测试的应用,元由测试包的manifest文件中<instrumentation>素的"android:targetPackage"属性设定。
java包标识符对应源文件,包名反映了源文件所在目录,它同时会影响类与成员间彼此的可访问性。
android 工具会帮助你设定测试包的名字。根据你的输入,工具会设定测试包的名字以及测试的目标包的名字。只有在被测试应用工程已经存在的状况下这些工具才会起做用。
默 认状况下,这些工具会将测试类的包标识符设定为与被测试应用的包标示符一致。若是你想暴露被测试包中的一些成员你可能须要作一些修改。若是要修改,只修改 java 包标示符,不要修改android 包名,只修改test case 的源文件而不要修改测试包中R.java文件的包名,由于修改它会形成与被测试包中的R.java类冲突。不要将测试包的android包名修改为和它所 测试的应用的包名同样,由于这样它们的名字在系统中再也不是独一无二的。
测试内容
What To Test详细地描述了android应用中应该被测试的关键功能以及可能会影响该功能的情况。
大部分的单元测试是专门针对你正在测试的andorid组件。Activity Testing、 Content Provider Testing和 Service Testing中都有一章节列出“须要测试什么”。
尽可能在真实的设备上运行这些测试。其次用Android Emulator来加载已经配置好你所但愿测试的硬件、屏幕、版本的android vitual device。
http://developer.android.com/intl/zh-cn/tools/testing/testing_android.html
http://www.uml.org.cn/mobiledev/201306074.asp
微博 http://weibo.com/cizhenshi 做者博客:http://my.oschina.net/u/1433482/ python测试开发精华群 291184506 PythonJava单元白盒测试 144081101
实例下载地址:https://github.com/googlesamples/android-testing/tree/master/runner/AndroidJunitRunnerSample
记录时间:2016-02-19
使用方法:在Android Studio中导入根目录,android-testing/runner/AndroidJunitRunnerSample
app/build.gradle内容以下:
apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion '23.0.2' defaultConfig { applicationId "com.example.android.testing.androidjunitrunnersample" minSdkVersion 8 targetSdkVersion 23 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } productFlavors { } } dependencies { // App's dependencies, including test compile 'com.android.support:support-annotations:23.0.1' compile 'com.google.guava:guava:18.0' // Testing-only dependencies // Force usage of support annotations in the test app, since it is internally used by the runner module. androidTestCompile 'com.android.support:support-annotations:23.0.1' androidTestCompile 'com.android.support.test:runner:0.4.1' androidTestCompile 'com.android.support.test:rules:0.4.1' androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' }
类Calculator的内容以下:
package com.example.android.testing.androidjunitrunnersample; import static com.google.common.base.Preconditions.checkArgument; /** * A simple calculator with a basic set of operations. */ public class Calculator { public enum Operator {ADD, SUB, DIV, MUL} /** * Addition operation */ public double add(double firstOperand, double secondOperand) { return firstOperand + secondOperand; } /** * Substract operation */ public double sub(double firstOperand, double secondOperand) { return firstOperand - secondOperand; } /** * Divide operation */ public double div(double firstOperand, double secondOperand) { checkArgument(secondOperand != 0, "secondOperand must be != 0, you cannot divide by zero"); return firstOperand / secondOperand; } /** * Multiply operation */ public double mul(double firstOperand, double secondOperand) { return firstOperand * secondOperand; } }
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <!-- Copyright 2015 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.testing.androidjunitrunnersample" > <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".CalculatorActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
类CalculatorActivity
package com.example.android.testing.androidjunitrunnersample; import android.app.Activity; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; /** * {@link android.app.Activity} which contains a simple calculator. Numbers can be entered in the * two {@link EditText} fields and result can be obtained by pressing one of the * operation {@link Button}s at the bottom. */ public class CalculatorActivity extends Activity { private static final String TAG = "CalculatorActivity"; private Calculator mCalculator; private EditText mOperandOneEditText; private EditText mOperandTwoEditText; private TextView mResultTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_calculator); mCalculator = new Calculator(); mResultTextView = (TextView) findViewById(R.id.operation_result_text_view); mOperandOneEditText = (EditText) findViewById(R.id.operand_one_edit_text); mOperandTwoEditText = (EditText) findViewById(R.id.operand_two_edit_text); } /** * OnClick method that is called when the add {@link Button} is pressed. */ public void onAdd(View view) { compute(Calculator.Operator.ADD); } /** * OnClick method that is called when the substract {@link Button} is pressed. */ public void onSub(View view) { compute(Calculator.Operator.SUB); } /** * OnClick method that is called when the divide {@link Button} is pressed. */ public void onDiv(View view) { try { compute(Calculator.Operator.DIV); } catch (IllegalArgumentException iae) { Log.e(TAG, "IllegalArgumentException", iae); mResultTextView.setText(getString(R.string.computationError)); } } /** * OnClick method that is called when the multiply {@link Button} is pressed. */ public void onMul(View view) { compute(Calculator.Operator.MUL); } private void compute(Calculator.Operator operator) { double operandOne; double operandTwo; try { operandOne = getOperand(mOperandOneEditText); operandTwo = getOperand(mOperandTwoEditText); } catch (NumberFormatException nfe) { Log.e(TAG, "NumberFormatException", nfe); mResultTextView.setText(getString(R.string.computationError)); return; } String result; switch (operator) { case ADD: result = String.valueOf(mCalculator.add(operandOne, operandTwo)); break; case SUB: result = String.valueOf(mCalculator.sub(operandOne, operandTwo)); break; case DIV: result = String.valueOf(mCalculator.div(operandOne, operandTwo)); break; case MUL: result = String.valueOf(mCalculator.mul(operandOne, operandTwo)); break; default: result = getString(R.string.computationError); break; } mResultTextView.setText(result); } /** * @return the operand value which was entered in an {@link EditText} as a double */ private static Double getOperand(EditText operandEditText) { String operandText = getOperandText(operandEditText); return Double.valueOf(operandText); } /** * @return the operand text which was entered in an {@link EditText}. */ private static String getOperandText(EditText operandEditText) { String operandText = operandEditText.getText().toString(); if (TextUtils.isEmpty(operandText)) { throw new NumberFormatException("operand cannot be empty!"); } return operandText; } }
布局文件:activity_calculator.xml
<?xml version="1.0" encoding="utf-8"?> <!-- Copyright 2015 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" android:orientation="vertical" tools:context=".CalculatorActivity"> <EditText android:id="@+id/operand_one_edit_text" android:hint="@string/type_operand_one_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="numberDecimal"/> <EditText android:id="@+id/operand_two_edit_text" android:hint="@string/type_operant_two_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="numberDecimal"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/operation_add_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/add_operation_text" android:onClick="onAdd"/> <Button android:id="@+id/operation_sub_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/sub_operation_text" android:layout_toRightOf="@id/operation_add_btn" android:layout_toEndOf="@id/operation_add_btn" android:onClick="onSub"/> <Button android:id="@+id/operation_div_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/div_operation_text" android:layout_below="@id/operation_add_btn" android:onClick="onDiv"/> <Button android:id="@+id/operation_mul_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/mul_operation_text" android:layout_below="@id/operation_add_btn" android:layout_toRightOf="@id/operation_add_btn" android:layout_toEndOf="@id/operation_add_btn" android:onClick="onMul"/> </RelativeLayout> <TextView android:id="@+id/operation_result_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/activity_vertical_margin"/> </LinearLayout>
总共有6个测试文件,其中HintMatcher.java、OperationHintInstrumentationTest.java、OperationHintLegacyInstrumentationTest.java基于junit3,不建议涉及。
CalculatorAddParameterizedTest包含了参数化(数据驱动), 只测试相加,实际和传统的Junit测试相似,这种测试不建议放入Android test。
package com.example.android.testing.androidjunitrunnersample; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import android.test.suitebuilder.annotation.SmallTest; import java.lang.Iterable; import java.util.Arrays; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.runners.Parameterized.Parameters; /** * JUnit4 tests for the calculator's add logic. * * <p> This test uses a Junit4s Parameterized tests features which uses annotations to pass * parameters into a unit test. The way this works is that you have to use the {@link Parameterized} * runner to run your tests. * </p> */ @RunWith(Parameterized.class) @SmallTest public class CalculatorAddParameterizedTest { /** * @return {@link Iterable} that contains the values that should be passed to the constructor. * In this example we are going to use three parameters: operand one, operand two and the * expected result. */ @Parameters public static Iterable<Object[]> data() { return Arrays.asList(new Object[][]{ {0, 0, 0}, {0, -1, -1}, {2, 2, 4}, {8, 8, 16}, {16, 16, 32}, {32, 0, 32}, {64, 64, 128}}); } private final double mOperandOne; private final double mOperandTwo; private final double mExpectedResult; private Calculator mCalculator; /** * Constructor that takes in the values specified in * {@link CalculatorAddParameterizedTest#data()}. The values need to be saved to fields in order * to reuse them in your tests. */ public CalculatorAddParameterizedTest(double operandOne, double operandTwo, double expectedResult) { mOperandOne = operandOne; mOperandTwo = operandTwo; mExpectedResult = expectedResult; } @Before public void setUp() { mCalculator = new Calculator(); } @Test public void testAdd_TwoNumbers() { double resultAdd = mCalculator.add(mOperandOne, mOperandTwo); assertThat(resultAdd, is(equalTo(mExpectedResult))); } }
CalculatorInstrumentationTest.java,该例子演示了Espresso的使用,同时对通用的测试操做作了精巧的封装,是个比较好的学习对象:
/* * Copyright 2015, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.testing.androidjunitrunnersample; import junit.framework.TestSuite; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; import org.junit.runner.RunWith; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnitRunner; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.LargeTest; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard; import static android.support.test.espresso.action.ViewActions.typeText; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; /** * JUnit4 Ui Tests for {@link CalculatorActivity} using the {@link AndroidJUnitRunner}. * This class uses the JUnit4 syntax for tests. * <p> * With the new AndroidJUnit runner you can run both JUnit3 and JUnit4 tests in a single test * suite. The {@link AndroidRunnerBuilder} which extends JUnit's * {@link AllDefaultPossibilitiesBuilder} will create a single {@link * TestSuite} from all tests and run them. */ @RunWith(AndroidJUnit4.class) @LargeTest public class CalculatorInstrumentationTest { /** * A JUnit {@link Rule @Rule} to launch your activity under test. This is a replacement * for {@link ActivityInstrumentationTestCase2}. * <p> * Rules are interceptors which are executed for each test method and will run before * any of your setup code in the {@link Before @Before} method. * <p> * {@link ActivityTestRule} will create and launch of the activity for you and also expose * the activity under test. To get a reference to the activity you can use * the {@link ActivityTestRule#getActivity()} method. */ @Rule public ActivityTestRule<CalculatorActivity> mActivityRule = new ActivityTestRule<>( CalculatorActivity.class); @Test public void noOperandShowsComputationError() { final String expectedResult = mActivityRule.getActivity().getString(R.string.computationError); onView(withId(R.id.operation_add_btn)).perform(click()); onView(withId(R.id.operation_result_text_view)).check(matches(withText(expectedResult))); } @Test public void typeOperandsAndPerformAddOperation() { performOperation(R.id.operation_add_btn, "16.0", "16.0", "32.0"); } @Test public void typeOperandsAndPerformSubOperation() { performOperation(R.id.operation_sub_btn, "32.0", "16.0", "16.0"); } @Test public void typeOperandsAndPerformDivOperation() { performOperation(R.id.operation_div_btn, "128.0", "16.0", "8.0"); } @Test public void divZeroForOperandTwoShowsError() { final String expectedResult = mActivityRule.getActivity().getString( R.string.computationError); performOperation(R.id.operation_div_btn, "128.0", "0.0", expectedResult); } @Test public void typeOperandsAndPerformMulOperation() { performOperation(R.id.operation_mul_btn, "16.0", "16.0", "256.0"); } private void performOperation(int btnOperationResId, String operandOne, String operandTwo, String expectedResult) { // Type the two operands in the EditText fields onView(withId(R.id.operand_one_edit_text)).perform(typeText(operandOne), closeSoftKeyboard()); onView(withId(R.id.operand_two_edit_text)).perform(typeText(operandTwo), closeSoftKeyboard()); // Click on a given operation button onView(withId(btnOperationResId)).perform(click()); // Check the expected test is displayed in the Ui onView(withId(R.id.operation_result_text_view)).check(matches(withText(expectedResult))); } }
类CalculatorTest也是传统Junit测试的展现,比第一个类CalculatorAddParameterizedTest多了些测试而已:
package com.example.android.testing.androidjunitrunnersample; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; /** * JUnit4 unit tests for the calculator logic. */ @RunWith(AndroidJUnit4.class) @SmallTest public class CalculatorTest { private Calculator mCalculator; @Before public void setUp() { mCalculator = new Calculator(); } @Test public void addTwoNumbers() { double resultAdd = mCalculator.add(1d, 1d); assertThat(resultAdd, is(equalTo(2d))); } @Test public void subTwoNumbers() { double resultSub = mCalculator.sub(1d, 1d); assertThat(resultSub, is(equalTo(0d))); } @Test public void subWorksWithNegativeResult() { double resultSub = mCalculator.sub(1d, 17d); assertThat(resultSub, is(equalTo(-16d))); } @Test public void divTwoNumbers() { double resultDiv = mCalculator.div(32d,2d); assertThat(resultDiv, is(equalTo(16d))); } @Test(expected = IllegalArgumentException.class) public void divDivideByZeroThrows() { mCalculator.div(32d,0d); } @Test public void mulTwoNumbers() { double resultMul = mCalculator.mul(32d, 2d); assertThat(resultMul, is(equalTo(64d))); } }
相关参考资料:https://io2015codelabs.appspot.com/codelabs/android-studio-testing#1
中文参考:http://www.jianshu.com/p/03118c11c199