欢迎Follow我的GitHub, 关注我的CSDN.

可靠的功能測试, 意味着在不论什么时候, 获取的測试结果均同样, 这就须要模拟(Mock)数据. 測试框架能够使用Android推荐的Espresso. 模拟数据能够使用Dagger2, 一种依赖注入框架.

Dagger2已经成为众多Android开发人员的必备工具, 是一个高速的依赖注入框架,由Square开发。并针对Android做了特别优化, 已经被Google进行Fork开发. 不像其它的依赖注入器, Dagger2没有使用反射, 而是使用预生成代码, 提高运行速度.

单元測试一般会模拟全部依赖, 避免出现不可靠的情况, 而功能測试也能够这样做. 一个经典的样例是怎样模拟稳定的网络数据, 能够使用Dagger2处理这样的情况.

Talk is cheap! 我来解说下怎样实现.

Github下载地址

1. 配置依赖环境

主要:

(1) Lambda表达式支持.

(2) Dagger2依赖注入框架.

(3) RxAndroid响应式编程框架.

(4) Retrofit2网络库框架.

(5) Espresso測试框架.

(6) DataBinding数据绑定支持.

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
} // Lambda表达式
plugins {
id "me.tatarka.retrolambda" version "3.2.4"
} apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt' // 凝视处理 final BUILD_TOOLS_VERSION = '23.0.1' android {
compileSdkVersion 23
buildToolsVersion "${BUILD_TOOLS_VERSION}" defaultConfig {
applicationId "clwang.chunyu.me.wcl_espresso_dagger_demo"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0" testInstrumentationRunner "clwang.chunyu.me.wcl_espresso_dagger_demo.runner.WeatherTestRunner"
} buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
} // 凝视冲突
packagingOptions {
exclude 'META-INF/services/javax.annotation.processing.Processor'
} // 使用Java1.8
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
} // 数据绑定
dataBinding {
enabled = true
}
} final DAGGER_VERSION = '2.0.2'
final RETROFIT_VERSION = '2.0.0-beta2' dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
// Warning:Conflict with dependency 'com.android.support:support-annotations'.
// Resolved versions for app (23.1.1) and test app (23.0.1) differ.
// See http://g.co/androidstudio/app-test-app-conflict for details.
compile "com.android.support:appcompat-v7:${BUILD_TOOLS_VERSION}" // 须要与BuildTools保持一致 compile 'com.jakewharton:butterknife:7.0.1' // 标注 compile "com.google.dagger:dagger:${DAGGER_VERSION}" // dagger2
compile "com.google.dagger:dagger-compiler:${DAGGER_VERSION}" // dagger2 compile 'io.reactivex:rxandroid:1.1.0' // RxAndroid
compile 'io.reactivex:rxjava:1.1.0' // 推荐同一时候载入RxJava compile "com.squareup.retrofit:retrofit:${RETROFIT_VERSION}" // Retrofit网络处理
compile "com.squareup.retrofit:adapter-rxjava:${RETROFIT_VERSION}" // Retrofit的rx解析库
compile "com.squareup.retrofit:converter-gson:${RETROFIT_VERSION}" // Retrofit的gson库
compile 'com.squareup.okhttp:logging-interceptor:2.6.0' // 拦截器 // 測试的编译
androidTestCompile 'com.android.support.test:runner:0.4.1' // Android JUnit Runner
androidTestCompile 'com.android.support.test:rules:0.4.1' // JUnit4 Rules
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' // Espresso core provided 'javax.annotation:jsr250-api:1.0' // Java标注
}

Lambda表达式支持, 优雅整洁代码的关键.

// Lambda表达式
plugins {
id "me.tatarka.retrolambda" version "3.2.4"
} android {
// 使用Java1.8
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

Dagger2依赖注入框架, 实现依赖注入. android-apt使用生成代码的插件.

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
} apply plugin: 'com.neenbedankt.android-apt' // 凝视处理 dependencies {
compile "com.google.dagger:dagger:${DAGGER_VERSION}" // dagger2
compile "com.google.dagger:dagger-compiler:${DAGGER_VERSION}" // dagger2
provided 'javax.annotation:jsr250-api:1.0' // Java标注
}

測试, 在默认配置中加入Runner, 在依赖中加入espresso库.

android{
defaultConfig {
testInstrumentationRunner "clwang.chunyu.me.wcl_espresso_dagger_demo.runner.WeatherTestRunner"
}
} dependencies {
testCompile 'junit:junit:4.12' // 測试的编译
androidTestCompile 'com.android.support.test:runner:0.4.1' // Android JUnit Runner
androidTestCompile 'com.android.support.test:rules:0.4.1' // JUnit4 Rules
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' // Espresso core
}

数据绑定

android{
// 数据绑定
dataBinding {
enabled = true
}
}

2. 设置项目

使用数据绑定, 实现了简单的搜索天功能.

/**
* 实现简单的查询天气的功能.
*
* @author wangchenlong
*/
public class MainActivity extends AppCompatActivity { private ActivityMainBinding mBinding; // 数据绑定
private MenuItem mSearchItem; // 菜单项
private Subscription mSubscription; // 订阅 @Inject WeatherApiClient mWeatherApiClient; // 天气client @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((WeatherApplication) getApplication()).getAppComponent().inject(this);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
} @Override public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_activity_main, menu); // 载入文件夹资源
mSearchItem = menu.findItem(R.id.menu_action_search);
tintSearchMenuItem();
initSearchView();
return true;
} // 搜索项着色, 会覆盖基础颜色, 取交集.
private void tintSearchMenuItem() {
int color = ContextCompat.getColor(this, android.R.color.white); // 白色
mSearchItem.getIcon().setColorFilter(color, PorterDuff.Mode.SRC_IN); // 交集
} // 搜索项初始化
private void initSearchView() {
SearchView searchView = (SearchView) MenuItemCompat.getActionView(mSearchItem);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override public boolean onQueryTextSubmit(String query) {
MenuItemCompat.collapseActionView(mSearchItem);
loadWeatherData(query); // 载入查询数据
return true;
} @Override public boolean onQueryTextChange(String newText) {
return false;
}
});
} // 载入天气数据
private void loadWeatherData(String cityName) {
mBinding.progress.setVisibility(View.VISIBLE);
mSubscription = mWeatherApiClient
.getWeatherForCity(cityName)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::bindData, this::bindDataError);
} // 绑定天气数据
private void bindData(WeatherData weatherData) {
mBinding.progress.setVisibility(View.INVISIBLE);
mBinding.weatherLayout.setVisibility(View.VISIBLE);
mBinding.setWeatherData(weatherData);
} // 绑定数据失败
private void bindDataError(Throwable throwable) {
mBinding.progress.setVisibility(View.INVISIBLE);
} @Override
protected void onDestroy() {
if (mSubscription != null) {
mSubscription.unsubscribe();
}
super.onDestroy();
}
}

数据绑定实现数据和显示分离, 解耦项目, 易于管理, 很适合数据展示页面.

在layout中设置数据.

    <data>
<variable
name="weatherData"
type="clwang.chunyu.me.wcl_espresso_dagger_demo.data.WeatherData"/>
</data>

在代码中绑定数据.

mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mBinding.setWeatherData(weatherData);

搜索框的设置.

    @Override public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_activity_main, menu); // 载入文件夹资源
mSearchItem = menu.findItem(R.id.menu_action_search);
tintSearchMenuItem();
initSearchView();
return true;
} // 搜索项着色, 会覆盖基础颜色, 取交集.
private void tintSearchMenuItem() {
int color = ContextCompat.getColor(this, android.R.color.white); // 白色
mSearchItem.getIcon().setColorFilter(color, PorterDuff.Mode.SRC_IN); // 交集
} // 搜索项初始化
private void initSearchView() {
SearchView searchView = (SearchView) MenuItemCompat.getActionView(mSearchItem);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override public boolean onQueryTextSubmit(String query) {
MenuItemCompat.collapseActionView(mSearchItem);
loadWeatherData(query); // 载入查询数据
return true;
} @Override public boolean onQueryTextChange(String newText) {
return false;
}
});
}

3. 功能測试

这一部分, 我会重点解说.

既然使用Dagger2, 那么我们就来配置依赖注入.

三部曲: Module -> Component -> Application

Module, 使用模拟Api类, MockWeatherApiClient.

/**
* 測试App的Module, 提供AppContext, WeatherApiClient的模拟数据.
* <p>
* Created by wangchenlong on 16/1/16.
*/
@Module
public class TestAppModule {
private final Context mContext; public TestAppModule(Context context) {
mContext = context.getApplicationContext();
} @AppScope
@Provides
public Context provideAppContext() {
return mContext;
} @Provides
public WeatherApiClient provideWeatherApiClient() {
return new MockWeatherApiClient();
}
}

Component, 注入MainActivityTest.

/**
* 測试组件, 加入TestAppModule
* <p>
* Created by wangchenlong on 16/1/16.
*/
@AppScope
@Component(modules = TestAppModule.class)
public interface TestAppComponent extends AppComponent {
void inject(MainActivityTest test);
}

Application, 继承非測试的Application(WeatherApplication), 设置測试组件, 重写获取组件的方法(getAppComponent).

/**
* 測试天气应用
* <p>
* Created by wangchenlong on 16/1/16.
*/
public class TestWeatherApplication extends WeatherApplication {
private TestAppComponent mTestAppComponent; @Override public void onCreate() {
super.onCreate();
mTestAppComponent = DaggerTestAppComponent.builder()
.testAppModule(new TestAppModule(this))
.build();
} // 组件
@Override
public TestAppComponent getAppComponent() {
return mTestAppComponent;
}
}

Mock数据类, 使用模拟数据创建Gson类, 延迟发送至监听接口.

/**
* 模拟天气Apiclient
*/
public class MockWeatherApiClient implements WeatherApiClient {
@Override public Observable<WeatherData> getWeatherForCity(String cityName) {
// 获得模拟数据
WeatherData weatherData = new Gson().fromJson(TestData.MUNICH_WEATHER_DATA_JSON, WeatherData.class);
return Observable.just(weatherData).delay(1, TimeUnit.SECONDS); // 延迟时间
}
}

注冊Application至TestRunner.

/**
* 更换Application, 设置TestRunner
*/
public class WeatherTestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
String testApplicationClassName = TestWeatherApplication.class.getCanonicalName();
return super.newApplication(cl, testApplicationClassName, context);
}
}

測试主类

/**
* 測试的Activity
* <p>
* Created by wangchenlong on 16/1/16.
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class MainActivityTest { private static final String CITY_NAME = "Beijing"; // 由于我们使用測试接口, 设置不论什么都能够. @Rule public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class); @Inject WeatherApiClient weatherApiClient; @Before
public void setUp() {
((TestWeatherApplication) activityTestRule.getActivity().getApplication()).getAppComponent().inject(this);
} @Test
public void correctWeatherDataDisplayed() {
WeatherData weatherData = weatherApiClient.getWeatherForCity(CITY_NAME).toBlocking().first(); onView(withId(R.id.menu_action_search)).perform(click());
onView(withId(android.support.v7.appcompat.R.id.search_src_text)).perform(replaceText(CITY_NAME));
onView(withId(android.support.v7.appcompat.R.id.search_src_text)).perform(pressKey(KeyEvent.KEYCODE_ENTER)); onView(withId(R.id.city_name)).check(matches(withText(weatherData.getCityName())));
onView(withId(R.id.weather_date)).check(matches(withText(weatherData.getWeatherDate())));
onView(withId(R.id.weather_state)).check(matches(withText(weatherData.getWeatherState())));
onView(withId(R.id.weather_description)).check(matches(withText(weatherData.getWeatherDescription())));
onView(withId(R.id.temperature)).check(matches(withText(weatherData.getTemperatureCelsius())));
onView(withId(R.id.humidity)).check(matches(withText(weatherData.getHumidity())));
}
}

ActivityTestRule设置MainActivity.class測试类.

setup设置依赖注入, 注入TestWeatherApplication的组件.

使用WeatherApiClient的数据, 模拟类的功能. 由于数据是预设的, 不论有无网络, 都能够进行可靠的功能測试.

运行測试, 右键点击MainActivityTest, 使用Run ‘MainActivityTest’.

OK, that’s all! Enjoy it!

可靠的功能測试--Espresso和Dagger2的更多相关文章

  1. ESP8266学习笔记1:怎样在安信可全功能測试板上实现ESP-01的编译下载和调试

    近期调试用到了安信可的ESP-01模块,最终打通了编译下载调试的整个通道,有一些细节须要记录,方便兴许的开发工作. 转载请注明:http://blog.csdn.net/sadshen/article ...

  2. GMGDC专訪戴亦斌:具体解释QAMAster全面測试服务6大功能

    GMGDC专訪戴亦斌:具体解释QAMAster全面測试服务6大功能 2014/10/10 · Testin · 业界资讯 在9月24-25日第三届全球移动游戏开发人员大会上,Testin云測COO戴亦 ...

  3. Java web測试分为6个部分

    1.功能測试 2.性能測试(包含负载/压力測试)3.用户界面測试 4. 兼容性測试 5.  安全測试  6.接口測试   1 功能測试 1.1 链接測试 链接測试可分为三个方面. 首先,測试全部链接是 ...

  4. Mock+Proxy在SDK项目的自己主动化測试实战

    项目背景 广告SDK项目是为应用程序APP开发者提供移动广告平台接入的API程序集合,其形态就是一个植入宿主APP的jar包.提供的功能主要有以下几点: - 为APP请求广告内容 - 用户行为打点 - ...

  5. Android自己主动化測试解决方式

    如今,已经有大量的Android自己主动化測试架构或工具可供我们使用,当中包含:Activity Instrumentation, MonkeyRunner, Robotium, 以及Robolect ...

  6. Android单元測试之JUnit

    随着近期几年測试方面的工作慢慢火热起来.常常看见有招聘測试project师的招聘信息.在Java中有单元測试这么一个JUnit 方式,Android眼下主要编写的语言是Java,所以在Android开 ...

  7. Android自己主动化測试之Monkeyrunner用法及实例

    眼下android SDK里自带的现成的測试工具有monkey 和 monkeyrunner两个.大家别看这俩兄弟名字相像,但事实上是完全然全不同的两个工具,应用在不同的測试领域.总的来说,monke ...

  8. Web安全測试二步走

    Web安全測试时一个比較复杂的过程,软件測试人员能够在当中做一些简单的測试,例如以下: Web安全測试也应该遵循尽早測试的原则,在进行功能測试的时候(就应该运行以下的測试Checklist安全測试场景 ...

  9. 移动App測试实战:顶级互联网企业软件測试和质量提升最佳实践

    这篇是计算机类的优质预售推荐>>>><移动App測试实战:顶级互联网企业软件測试和质量提升最佳实践> 国内顶级互联网公司測试实战经验总结.阿里.腾讯.京东.携程.百 ...

随机推荐

  1. cordova / Ionic 开发问题汇总

    cordova / Ionic 开发问题汇总 1. 导入工程的"The import android cannot be resolved"错误解决方法 2. MainActivi ...

  2. 【转】javascript中值传递,地址传递,引用传递的问题(使用js创建list对象时会用到)

    function initEditModal_SI(node) { if (node.siArray == undefined) { node.siArray = new Object(); } va ...

  3. servlet常用操作

      servlet常用操作 CreateTime--2017年9月7日09:36:43 Author:Marydon 1.获取当前应用程序对象 需要导入: import javax.servlet.S ...

  4. 细说HTML元素的隐藏和显示

    CSS文档对HTML的显示和隐藏有2个属性可供选择: 1.display 2.visiblity 这2个有什么区别呢? display: display版本:CSS1/CSS2 兼容性:IE4+ NS ...

  5. Linux 系统使用 iso 镜像文件或光盘配置本地YUM 源的最简单方式

    1.分配光驱 选择本地的iso系统镜像文件,或者在光驱中放入系统安装盘.之后,在桌面可以看到RHEL-7.2-Server的光盘图标. 2.查看光驱挂载的位置 使用df -h 命令可以看到光驱或镜像文 ...

  6. C#:确保绑定到同一数据源的多个控件保持同步

    下面的代码示例演示如何使用 BindingSource 组件,将三个控件(两个文本框控件和一个 DataGridView 控件)绑定到 DataSet 中的同一列.该示例演示如何处理BindingCo ...

  7. PHP5.4新特性

    PHP5.4 此次更新的关键新特性,包括:新增traits,更精简的Array数组语法,供测试使用的内建webserver,可以闭包使用的$this指针,实例化类成员访问, PHP 5.4.0 性能大 ...

  8. 自定义Microsoft Visual Studio 代码模板,增加公司和个人信息

    C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ItemTemplates\CSharp目录里面有各种新建模板分类: 修 ...

  9. iOS - App 上架审核被原因拒总结

    1.未遵守苹果 iOS APP 数据储存指导方针 如果你的 App 有离线数据下载功能,尤其需要关注这一点.因为离线数据一般占用存储空间比较大,可以被重新下载和重建,但是用户往往希望系统存储空间紧时也 ...

  10. 关于Andorid的RecyclerView在V7包下找不到的解决办法

      关于Andorid的RecyclerView在V7包下找不到的解决办法 最近在学习使用RecyclerView替换现有的ListView,看了几篇文章.当准备自己动手实现的时候发现,V7包下找不到 ...