测试你的App是开发过程中的重要组成部分。通过对应用程序持续的运行测试,你可以验证程序的正确性、功能和可用在发布之前。
测试还提供了以下优点:
这里的特指测试通过代码实现的“单元”测试。所以,它可以更早、更快的帮我们发现问题,使我们的代码重构更有信心;单元测试虽然前期编写会比较耗时,但是它可以有效的代码的质量,不会导致项目后期代码充满坏味道,严重拖慢开发进度;所以,减少你技术债务。
在Android测试库、Android平台和开源社区都提供了帮助你编写Android应用测试的工具:
Espesso
Espresso是android应用开发自带测试库。他是一款白盒风格的UI测试工具。UI测试就都是黑盒的么?为什么会是白盒风格。
说白盒是因为,通过Espresso编写测试调用Android控件的方式和 Android开发中是一样的。 来看一段Android开发中,Activity中编写的代码。
/** Called when the user clicks the Send button */
public void sendMessage(View view) {
// Do something in response to button
Intent intent = new Intent(this, DisplayMessageActivity.class);
EditText editText = (EditText) findViewById(R.id.edit_message);
String message = editText.getText().toString();
intent.putExtra(EXTRA_MESSAGE, message);
startActivity(intent);
}
通过?R.id.edit_message
?调用布局文件中输入框中的内容,并转交到另外一个Activity处理。
再来看一段 Espesso 的测试代码:
@Test
public void InputEditCase() throws InterruptedException{
onView(withId(R.id.edit_message)).perform(typeText(STRING_TO_BE_TYPED));
onView(withId(R.id.send_button)).perform(click());
onView(withId(R.id.display_message)).check(matches(isDisplayed()));
onView(withId(R.id.display_message)).check(matches(withText("hello," +STRING_TO_BE_TYPED)));
}
?
同样使用的是 R.id.edit_message 的定位方式来查找控件,是不是白盒?我们通常的黑盒UI自动化测试是通过UI属性查看工具(如:UIAutomatorViewer)确定元素的属性来进行定位的。Espesso不需要,你看代码就好了,准确点是看Android的布局文件的控件定义。
但是,Espesso的运行是基于 SDK 的,所以,要想运行一条用例必须在Android模拟器(或真机)上安装App,启动App,然后基于UI的操作来运行测试用例。
Robolectric
Robolectric是一款第三方的开源的Android单元测试框架。运行在JVM上,所以它运行速度上会比 Espesso快上很多。
@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
@Test
public void clickingButton_shouldChangeResultsViewText() throws Exception {
MyActivity activity = Robolectric.setupActivity(MyActivity.class);
Button button = (Button) activity.findViewById(R.id.button);
TextView results = (TextView) activity.findViewById(R.id.results);
button.performClick();
assertThat(results.getText().toString()).isEqualTo("Robolectric Rocks!");
}
}
来一段官方Demo,robolectric的做法是通过实现一套JVM能运行的Android代码,然后在单元测试运行的时候去截取android相关的代码调用,然后转到他们的他们实现的代码去执行这个调用的过程。
你不明白原理也没关系,反正知道Robolectric的运行不需要你真正的打开App去执行测试,就像运行一段普通的Java代码一样。所以速度上当然就很快了。
AndroidJUnitRunner
AndroidJUnitRunner本质上不算是个测试工具,它只是Google基于Junit针对Anroid封装的一个测试用例运行器而已。至于它用来运行Espesso还是Uiautomator的用例都是可以的。那Robolectric呢?没看到上面的例子中Robolectric有自己的运行器叫RobolectricTestRunner
@RunWith(AndroidJUnit4.class)
public class MainActiveTest{
……
}
如果看到测试类是用 AndroidJUnit4 注释的,说明用的就是AndroidJUnitRunner运行器的。
测试应用
最后,更好的编写测试用例的平台,当然是Google家的亲儿子了。器大活好不粘人!(现在才发现不是去幼儿园的车,晚了,把车门给我捍死,一个都不准下车。)
Android Studio 以简化测试为设计宗旨。 您只需完成几次点击,便可建立一个在本地 JVM 上运行的 JUnit 测试,或建立一个在设备上运行的仪器测试。
当然,您也可以通过集成测试框架来扩展测试能力,例如可以集成 Mockito 在本地单元测试中测试 Android API 调用,以及集成 Espresso 或 UI Automator 在仪器测试中演练用户交互。 您可以利用 Espresso 测试记录器自动生成 Espresso 测试。
用户在不同的级别上与你的应用产生交互。从按下按钮到将信息下载到他们的设备上,因此,你应该在迭代开发应用程序时测试各种用例和交互。
使用迭代开发工作流
当你的应用程序进行扩展时,你可能会发现需要从服务器获取数据,与设备的传感器进行交互,可以还需要访问本地存储,或呈现复杂的用户界面。应用程序的多样性需要一个全面的测试策略。
在迭代开发一个新的特性时,首先需要编写一个新的测试,或者在现有单元测试中添加新的测试例和断言。测试一开始是失败的,因为这个新特性还没有实现。
在设计新功能时,考虑单元测试的设计很重要。对于每个功能单元编写一个相应的单元测试。你的单元测试应该尽可多的涵盖到所有可能与单元的交互,包括标准的交互、无效的输入、以及资源不可用的情况。
图:测试驱动开发:两个周期与迭代过程。
完整的工作流,如上图,包含一系列嵌套的循环迭代。其中长、慢、UI驱动的集成测试,你可以用更短、更快的单元测试驱动开发周期来完成。这一系列循环一直持续到你的应用程序满足每个用例为止。
测试金字塔
图:测试金字塔,显示应该在应用程序的测试套件中包含的三个测试类别。
测试金字塔,如上图所示,说明你的应用程序应该如何包括三种类型的测试:小测试、中测试和大测试。
注:这里测试金字塔的定义是Google针对Android应用的测试划分。不同类型的应用,通过测试金字塔模型进行划层级的划分,以及每层的定义会有所不同。
尽管小测试的速度很快,而且重点突出,可以让您快速处理失败,但它们是低保真和独立的,让你很难相信通过测试的应用是可以正常运行的,所以需要通过大测试进行补足。
由于每个测试类别的不同特性,你的测试工作应该包含测试金字塔的每一层测试。尽管每个类别的测试比例可能因应用程序的使用情况而有所不同,但我们通常建议以下分类:70%的小测试、20%的中等测试和10%的大测试。
小测试
在添加和更改应用程序的功能时,通过创建和编写单元测试来确保这些功能按照预期的来运行。虽然可以在Android模拟器或真实设备中运行单元测试,但是在开发环境中运行单元测试通常会更快、更容易,根据需要添加Stub或mock方法实现与Android系统进行交互。
Robolectric
如果你的应用程序的测试环境要求单元测试与Android框架进行更广泛的交互,那么你可以使用Robolectric。此工具执行测试友好、基于Java 逻辑Stub来模拟Android框架,由社区来维护这些Stub。
Robolectric测试几乎完全符合Android设备上运行测试的完全保真度,但仍比设备测试运行得更快。它还支持Android平台的以下几个方面:
?
Robolectric有自己的一套测试API,并引入了一些新概念。有关将Robolectric的API与应用程序的测试相结合的更多信息,请参见该工具的 用户指南
Mock对象
你可以通过修改Android.jar版本来运行单元测试,从而来监控应用与之交互的Android框架元素。这个JAR文件不包含任何代码,所以你的应用程序调用Android框架默认会抛出异常。 要测试与Android系统交互的代码元素,请使用像Mockito这样的框架来配置模拟对象。
检测单元测试
你也可以在Andorid模拟器或真实设备上运行检测单元测试,而不涉及任何stub或mock框架。因为这种测试形式的执行时间明显比本地单元测试慢,所以最好仅在需要评估应用程序对当前设备硬件的行为时才依赖此方法。
中测试
在开发环境中测试了应用程序的每个单元之后,应验证在模拟器或设备上运行时组件的行为是否正确。 中等测试允许您完成这部分的开发过程。 如果某些应用程序的组件依赖于物理硬件,则这些测试对于创建和运行尤其重要。
中等测试你的应用程序如何协调多个单元,但它们不测试完整的应用程序。中等测试的用例包括服务测试、集成测试以及mock外部依赖性行为的UI测试。
通常情况下,最好在Android模拟器或Firebase测试实验室等基于云服务上测试应用程序,而不是在真实设备上测试应用程序,因为你可以更轻松,快速地测试多种屏幕大小和硬件配置组合。
大测试
尽管测试应用程序中的每一层的功能是非常重要的,但是测试涉及整个堆栈的常见工作流和用例(从UI到业务逻辑到数据层)同样重要。
如果您的应用程序足够小,则可能只需要一套大型测试来评估你的应用程序。 否则,你应该根据团队所有权,垂直功能或用户目标来划分你的大型测试套件。
对于你所编写的每一个大型的基于工作流程的测试,你还应该编写中测试来检查工作流中包含的每个UI组件的功能。通过这种方式,即使在最初的几个步骤中,相应的大型测试也会失败了,你的测试套件仍然可以在关键用户操作的每一步中继续识别潜在的问题。
AndroidJUnitRunner类定义了一个基于JUnit框架的测试运行器,它允许你在Android设备上运行JUnit 3或JUnit 4样式的测试类。便于将测试包和应用程序加载到真实设备或Android模拟器上,运行测试并报告结果。
AndroidJUnitRunner类支持来自Android测试支持库的以下工具和框架:
JUnit4 Rules
测试说明语言包括用于管理的关键应用程序组件的生命周期涉及你的测试代码,如Activitie 和Service。学习如何定义这些规则,看到?JUnit4规则指南
Espresso
Espresso同步异步任务,同时自动执行以下应用内交互:
在View对象上执行操作。 * 完成跨越应用程序进程边界的工作流程。仅在Android 8.0(API级别26)及更高版本上可用。 * 评估具有辅助功能需求的用户如何使用你的应用。 * 查找并激活RecyclerView和AdapterView对象中的项目。 * 验证传出意图的状态。 * 在WebView对象中验证DOM的结构。 * 跟踪你的应用程序中长时间运行的后台操作。
要详细了解这些互动以及如何在应用程序的测试中使用它们,请参阅 Espresso指南。
UI Automator
我们建议仅当你的应用程序必须与系统进行交互的操作时,才使用UI Automator来测试你的应用程序。 由于UI Automator与系统应用程序UI交互,因此你需要在每次系统更新后重新运行并修复UI Automator测试。 此类更新包括Android平台版本升级和Google Play服务的新版本。
作为使用UI Automator的替代方案,我们建议添加封闭测试或将大型测试分为一系列小型和中型测试。特别是,一次侧重于测试一个应用程序间通信,例如将信息发送到其他应用程序,并对意图结果进行响应。Espresso-Intents工具可以帮助您编写这些较小的测试。
UI Automator框架在你的应用程序的系统应用程序内执行交互,例如检查当前显示UI的层次结构、截取屏幕截图以及分析设备的当前状态。有关UI Automator如何检查正在测试的应用程序的更多详细信息,请参阅 UI Automator指南 。
Android Test Orchestrator
Android Test Orchestrator在其自己的Instrumentation沙箱中运行每个UI测试,通过减少测试之间的共享状态并在每个测试的基础上隔离应用程序崩溃来提高测试套件的可靠性。
单元测试是你的应用程序测试策略的基本测试。 通过针对您的代码创建和运行单元测试,你可以轻松验证各个单元的逻辑是否正确。 在每次构建之后运行单元测试可帮助你快速捕获并修复由代码更改引入到应用程序的软件回归。
单元测试通常以可重复的方式实现尽可能小的代码单元(可以是方法,类或组件)的功能。 当你需要验证应用程序中特定代码的逻辑时,你应该构建单元测试。 例如,你创建了一个类,单元测试可以帮助检查该类是否处于正确的状态。 通常,单元测试是相对独立的,你的测试只会影响和检查被测试单元的变更,mock框架可以用来隔离你要测试单元的依赖。
注意:单元测试不适合测试复杂的UI交互事件。 相反,您应该使用UI测试框架,如UI自动化测试中所述。
为了测试Android应用程序,你通常需要创建这些类型的单元测试:
接下来将告诉你如何构建这两种类型单元测试。
本课的教训将告诉您如何构建这些类型的自动化单元测试。
Lessons
Building Local Unit Tests(创建本地单元测试)
学习如何构建在本地机器上运行的单元测试。
Building Instrumented Unit Tests(创建Instrumented单元测试)
了解如何构建在Android设备或模拟器上运行的单元测试。
如果你的单元测试没有依赖或者只有简单的Android依赖,则应该在本地开发机器上运行测试。这种测试方法非常高效,因为它可以帮助你避免每次运行测试时将目标应用程序和单元测试代码加载到真机或模拟器上的开销。因此,运行单元测试的执行时间大大减少了。通过这种方法,你通常使用mock框架(如Mockito)来完成任何依赖关系。
设置测试环境
在你的Android Studio项目中,必须将本地单元测试的源文件存储在module-name/src/test/java/ 目录中。在创建新项目时,该目录已经存在。
你还需要配置项目的测试依赖,以使用JUnit 4框架提供的标准API。如果你的测试需要与Android依赖关系进行交互,请包含Mockito库以简化本地单元测试。要了解有关在本地单元测试中使用模拟对象的更多信息,请参阅模拟Android依赖关系。
在你的App程序的目录下找到build.gradle文件中,将这些库指定为依赖项:
dependencies {
// Required -- JUnit 4 framework
testCompile 'junit:junit:4.12'
// Optional -- Mockito framework
testCompile 'org.mockito:mockito-core:1.10.19'
}
创建本地单元测试类
你的本地单元测试类应该写成一个JUnit 4测试类。 JUnit是Java最流行和广泛使用的单元测试框架。这个框架的最新版本,JUnit 4,允许你用比前一版本更清晰,更灵活的方式编写测试。与以前的基于JUnit 3的Android单元测试方法(使用JUnit 4)不同,你不需要扩展junit.framework.TestCase类。也不需要在测试方法名称前加上“test”关键字,或者使用junit.framework或junit.extensions包中的任何类。
要创建基本的JUnit 4测试类,请创建一个包含一个或多个测试方法的Java类。 测试方法从@Test注释开始,包含代码来练习和验证要测试的组件中的单个功能。
以下示例显示了如何实现本地单元测试类。 测试方法emailValidator_CorrectEmailSimple_ReturnsTrue验证被测试的应用程序中的isValidEmail()方法是否返回正确的结果。
import org.junit.Test;
import java.util.regex.Pattern;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class EmailValidatorTest {
@Test
public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
assertThat(EmailValidator.isValidEmail("name@email.com"), is(true));
}
...
}
要测试应用程序中的组件是否会返回预期的结果,请使用junit.Assert方法执行验证检查(或断言),以便将待测组件的状态与某个预期值进行比较。 为了使测试更具可读性,可以使用Hamcrest匹配器(如is()和equalTo()方法)将返回的结果与期望的结果进行匹配。
Mock Android依赖
默认情况下,针对Gradle的Android插件将针对android.jar库的修改版本执行本地单元测试,该库不包含任何实际的代码。 相反,从你的单元测试方法调用Android类抛出一个异常。 这是为了确保只测试你的代码,而不依赖于Android平台的任何特定行为(你没有明确地mock)。
你可以使用mock框架在代码中删除外部依赖项,以便以预期的方式轻松测试组件与依赖项的交互。 通过用mock对象代替Android依赖关系,可以将单元测试与Android系统的其余部分分离,同时验证这些依赖关系中正确的方法被调用。Java的Mockito模拟框架(版本1.9.5及更高版本)提供了与Android单元测试的兼容性。借助Mockito可以配置模拟对象以在调用时返回某个特定的值。
要使用此框架将mock对象添加到本地单元测试中,请遵循以下编程模型:
1、在你的 build.gradle 文件中包含Mockito库依赖项,如设置上面的测试环境中所述。
2、在单元测试类定义的开始处,添加 @RunWith(MockitoJUnitRunner.class)注释。 这个注释告诉Mockito测试运行器验证你对框架的使用是否正确,并且简化了你的模拟对象的初始化。
3、要为Android依赖项创建一个模拟对象,请在字段声明之前添加@Mock注释。
4、为了Stub依赖的行为,可以通过使用when()和return()方法来满足条件时,可以指定一个条件和返回值。
以下示例显示如何创建使用模拟Context对象的单元测试。
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import android.content.SharedPreferences;
@RunWith(MockitoJUnitRunner.class)
public class UnitTestSample {
private static final String FAKE_STRING = "HELLO WORLD";
@Mock
Context mMockContext;
@Test
public void readStringFromContext_LocalizedString() {
// Given a mocked Context injected into the object under test...
when(mMockContext.getString(R.string.hello_word))
.thenReturn(FAKE_STRING);
ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext);
// ...when the string is returned from the object under test...
String result = myObjectUnderTest.getHelloWorldString();
// ...then the result should be the expected one.
assertThat(result, is(FAKE_STRING));
}
}
要了解有关使用Mockito框架的更多信息,请参阅示例代码中的Mockito API参考和SharedPreferencesHelperTest类。
Error: "Method ... not mocked"
如果您运行测试,从Android SDK调用API,你不会使用mock,可能会收到一个错误,说这种方法没有被模拟。 这是因为用于运行单元测试的android.jar文件不包含任何当前代码(这些API仅由设备上的Android系统映像提供)。
相反,所有方法默认都会抛出异常。 这是为了确保你的单元测试你的代码,而不是依赖于Android平台的任何特定的行为(你没有明确地mock,如Mockito)。
如果抛出的异常说你的测试有问题,可以更改行为,以便通过在项目的顶级build.gradle文件中添加以下配置来返回null或零:
android {
...
testOptions {
unitTests.returnDefaultValues = true
}
}
?
注意:将returnDefaultValues属性设置为true应该小心。 null / zero返回值可以在测试中引入回归,这些回调很难调试,并且可能允许失败的测试通过。只能用它作为最后的手段。
运行本地单元测试
要运行您的本地单元测试,请按照下列步骤操作:
1、通过单击工具栏中的“ Sync Project”,确保您的项目与Gradle同步。
2、以下列其中一种方式运行测试:
感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
?
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取?
?