Stockeye 持续集成之路 —— NotificationStockQuote 开发实录

关键字 :Jenkins、Gradle、Jacoco、Android Studio、JUnit 和 Robolectri(只适用Android API Level 16、 17、18)

预构

  • 故事点(Agile - Scrum):用户可拉下通知框以查看实时股票信息
  • 界面草图(见下图)
  • 软件设计模式 :观察者模式(Notification 为观察者,数据更新服务为目标,可能有多个不同类型观察者,考虑引进中介者模式以减轻服务管理依赖负担)
  • 类:NotificationStockQuote,接口 -> IObserverNotificationStockQuote,数据结构 -> 二维数组(界面布局采取Listview)

项目概览

项目 build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.13.2'
classpath 'org.robolectric:robolectric-gradle-plugin:0.13.+'
}
}
allprojects {
repositories {
jcenter()
}
}

app. build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
apply plugin: 'com.android.application'
android {
compileSdkVersion 18
buildToolsVersion '19.1.0'
defaultConfig {
applicationId "com.msolo.stockeye"
minSdkVersion 16
targetSdkVersion 18
versionCode 1
versionName "1.0"
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'LICENSE.txt'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:support-v4:19+'
androidTestCompile 'org.hamcrest:hamcrest-integration:1.1'
androidTestCompile 'org.hamcrest:hamcrest-core:1.1'
androidTestCompile 'org.hamcrest:hamcrest-library:1.1'
androidTestCompile('junit:junit:4.+') {
exclude module: 'hamcrest-core'
}
androidTestCompile('org.robolectric:robolectric:2.3') {
exclude module: 'classworlds'
exclude module: 'commons-logging'
exclude module: 'httpclient'
exclude module: 'maven-artifact'
exclude module: 'maven-artifact-manager'
exclude module: 'maven-error-diagnostics'
exclude module: 'maven-model'
exclude module: 'maven-project'
exclude module: 'maven-settings'
exclude module: 'plexus-container-default'
exclude module: 'plexus-interpolation'
exclude module: 'plexus-utils'
exclude module: 'support-v4'
exclude module: 'wagon-file'
exclude module: 'wagon-http-lightweight'
exclude module: 'wagon-provider-api'
}
}
apply plugin: 'robolectric'

目录结构

1
2
3
4
5
6
7
8
| build.gradle
| src
| |- androidTest
| |- java/com/msolo/stockeye/MainActivityTest.java
| |- java/com/msolo/stockeye/StockeyeApplication.java
| |- main
| |- java/com/msolo/stockeye/MainActivity.java
| |- java/com/msolo/stockeye/StockeyeApplication.java

TDD 示范程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.msolo.stockeye;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
public class MainActivityTest {
@Test
public void testActivityDisplayHelloWorldSuccessful() {
String hello = new MainActivity().getResources().getString(R.string.hello_world);
assertEquals("Hello World!", hello);
}
}

示范测试

E:\android_workspaces\TDDStockeye>d:gradle-2.1\bin\gradle test
:app:preBuild
:app:preDebugBuild
:app:checkDebugManifest
:app:prepareDebugDependencies
:app:compileDebugAidl UP-TO-DATE
:app:compileDebugRenderscript UP-TO-DATE
:app:generateDebugBuildConfig UP-TO-DATE
:app:generateDebugAssets UP-TO-DATE
:app:mergeDebugAssets UP-TO-DATE
:app:generateDebugResValues UP-TO-DATE
:app:generateDebugResources UP-TO-DATE
:app:mergeDebugResources UP-TO-DATE
:app:processDebugManifest UP-TO-DATE
:app:processDebugResources UP-TO-DATE
:app:generateDebugSources UP-TO-DATE
:app:compileDebugJava UP-TO-DATE
:app:compileTestDebugJava
:app:processTestDebugResources UP-TO-DATE
:app:testDebugClasses
:app:testDebug
:app:test

BUILD SUCCESSFUL

Total time: 11.647 secs

测试结果:file:///E:/android_workspaces/TDDStockeye/app/build/test-report/debug/index.html


NotificationStockQuote TDD

NotificationStockQuote 测试计划

  1. 单例类 (代号:Annn,实际中不必这么繁琐!)
  2. 初始化(代号:Bnnn,获取 NOTIFICATION_SERVICE,)
  3. To do…

第01次(A001):测试默认构造器为私有,【注:TDD 快速测试示范意义】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// NotificationStockQuoteTest.java
@Test
public void testSingletonOfNotificationStockQuote() {
notificationStockQuote = new NotificationStockQuote();
NotificationStockQuote notificationStockQuoteAnother = new NotificationStockQuote();
assertNotSame(notificationStockQuoteAnother, notificationStockQuote);
}
// NotificationStockQuote.java
public class NotificationStockQuote {
private static NotificationStockQuote sInstance = null;
private NotificationStockQuote() {}
public static NotificationStockQuote getInstance() {
return new NotificationStockQuote();
}
}
  • 结果预期:失败;NotificationStockQuote 类默认构造器可以在外部被调用。

第02次(A002):测试默认构造器为私有,并且构造生成的对象为单例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// NotificationStockQuoteTest.java
@Test
public void testSingletonOfNotificationStockQuote() {
NotificationStockQuote notificationStockQuoteAnother = NotificationStockQuote.getInstance();
notificationStockQuote = NotificationStockQuote.getInstance();
assertSame(notificationStockQuoteAnother, notificationStockQuote);
}
// NotificationStockQuote.java
public class NotificationStockQuote {
private static NotificationStockQuote sInstance = null;
private NotificationStockQuote() {}
public static NotificationStockQuote getInstance() {
return new NotificationStockQuote();
}
}
  • file:///E:/android_workspaces/TDDStockeye/app/build/test-report/debug/index.html
  • 结果预期:失败;NotificationStockQuote 类默认构造器私有,但返回的对象不是单例。

第03次(A003):测试默认构造器为私有,并且构造生成的对象为单例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// NotificationStockQuoteTest.java
@Before
public void setUp() {
notificationStockQuote = NotificationStockQuote.getInstance();
}
@Test
public void testSingletonOfNotificationStockQuote() {
NotificationStockQuote notificationStockQuoteAnother = NotificationStockQuote.getInstance();
assertSame(notificationStockQuoteAnother, notificationStockQuote);
}
// NotificationStockQuote.java
public class NotificationStockQuote {
private static NotificationStockQuote sInstance = null;
private NotificationStockQuote() {}
public static NotificationStockQuote getInstance() {
if (null == sInstance) {
sInstance = new NotificationStockQuote();
}
return sInstance;
}
}
  • 结果预期:成功;NotificationStockQuote 类默认构造器私有,返回的对象是单例。

第04次(B001):测试 NotificationStockQuote 完成对 Notification 服务管理对象的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// NotificationStockQuoteTest.java
@Test
public void testInit() {
NotificationManager notificationManager = (NotificationManager) Robolectric.application.getSystemService(Context.NOTIFICATION_SERVICE);
notificationStockQuote.init();
NotificationStockQuote.HelperTestNotificationStockQuote helper = notificationStockQuote.new HelperTestNotificationStockQuote();
assertNotNull(helper.getNotificationManager());
assertSame(notificationManager, helper.getNotificationManager());
}
// NotificationStockQuote.java
public void init() {
}
/**
*
* This is an inner class for helping test NotificationStockQuote,
* and it should be delete in production.
*
* Date | Name | Change
*--------+--------------------------------------------------
* 141119 | mSolo | REQ #001: add method getNotificationManager().
*
*/
public class HelperTestNotificationStockQuote {
public HelperTestNotificationStockQuote() {}
public NotificationManager getNotificationManager() {
return notificationManager;
}
}
  • 结果预期:失败;NotificationStockQuote 对象并没有完成对 Notification 服务管理对象的初始化。


第05次(B002):测试 NotificationStockQuote 完成对 Notification 对象的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
public void testInit() {
NotificationManager notificationManager = (NotificationManager) Robolectric.application.getSystemService(Context.NOTIFICATION_SERVICE);
//notificationStockQuote.init();
MimicNotificationStockQuote mimic = new MimicNotificationStockQuote();
mimic.init();
assertNotNull(mimic.notificationManager);
assertSame(notificationManager, mimic.notificationManager);
}
/**
*
* This is an inner class for mimic NotificationStockQuote.
*
* Date | Name | Change
*--------+--------------------------------------------------
* 141119 | mSolo | REQ #001: mimic method init().
*
*/
private class MimicNotificationStockQuote {
public NotificationManager notificationManager = null;
public MimicNotificationStockQuote() {}
public void init() {
notificationManager = (NotificationManager)Robolectric.application.getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
}
}
// NotificationStockQuote.java
public void init() {
notificationManager = (NotificationManager) StockeyeApplication.getAppContext().getSystemService(Context.NOTIFICATION_SERVICE);
}
  • 结果预期:成功;NotificationStockQuote 对象完成对 Notification 服务的初始化。【注:不是很严格的测试】

未完待续