MINItest软件架构总结
————helloWen
1. Problem Description
2. Analysis
3. Solution
3.1. 通过读取设备信息来确定测试项信息
3.2 将测试项通过Activity加载并显示出来
3.3 控制各个Activity之间跳转
4. Summary
1. Problem Description
Precondition:
手机在试产过程中需要确认生产的机器硬件是否正常,而MINI软件就是这样一个测试手机硬件基本功能是否正常的精简系统(实际上就是一个内部测试软件)。MINI软件是提供给工厂用来测试硬件是否正常工作(如摄像头是否正常拍照,触摸屏是否灵敏等)的测试软件,它是包含MINI APK(也可叫做MMI)的最精简系统版本软件,贯穿手机整个生产流程,对生产非常重要。
MINI测试软件的主要是包含“AUTO”测试和“MANU”测试,这些测试的规范都由NPI部门主导完成,MINI测试设计要严格按NPI的规范进行设计,这2个测试模式要求如下:- 自动测试模式,按照一定的顺序依次执行各个测试项,一旦有一个测试项FAIL,则中断测试,适用于工厂自动化对所有测试项进行测试。
- 手动测试模式,所有测试项都显示在手动测试列表中,可任意选择测试项进行测试,适用于对单个测试项进行测试。
不同版本对应的mini软件状态要求如下:
- 在MINI软件的状态下,手机开机完成就会自动进入到MMI软件测试的界面。
- 在客户化软件的情况下,我们通过在拨号界面输入暗码可以进入到MMI软件测试的界面。
REPRODUCING PROCEDURES:
目前这个apk,整体架构比较简单,而且耦合性太高,代码有很多冗余, 格式不规范。其整体架构如下图所示:
在整个架构中多用的继承,使得各个case之间其实耦合性很大,而且扩展性不强。因此期望重构过程中多用组合,少用继承,使系统更具弹性。EXPECTED
代码精简&&语法规范&&架构重构
2. Analysis
框架部分主要有如下几个Activity:
- MiniActivity,主界面 activity,可选择2中测试方式,其一,是自动测试(即按着配置文件顺序测试完一项后紧接下一项继续测试);其二,手动测试(用户根据自己选择测试某个单独项)。
- FailedActivity,自动测试失败后,可以选择退出自动测试、忽略错误继续下一项测、重新测试当前项等3个选项。
- ManuListActivity ,当点击手动测试,显示试列表界面。
- ResultActivity ,自动测试完毕后,显示测试结果。
- ExecuteTestActivity ,执行测试项界面,所有测试项执行时均在此activity执行。
- InterruptActivity ,中断activity,当在测试界面时,可以通过长按Back键进入该界面。
整个软件的入口Activity是MiNiActivity,业务流程如下图所示:
通过MiNiActivity来判断用户使用哪种测试方式执行测试,而实际的测试项是在ExecuteTestActivity中完成的。
根据自动测试模式和手动测试模式的不同情况,通过使用观察者模式的思想来设计自动测试模式,使用策略模式的思想来设计手动测试模式。
自动测试的设计流程如下所示
手动测试的设计流程如下所示
在这个测试软件重构过程中,期望把测试的框架和测试的具体case分离开来,这样在以后的维护中可以分开维护,即如果需要增加、修改、删除某些测试case时,其他的case完全不受到影响,而且整个软件框架不需要去改动。对应的架构如下图所示:
- 测试实际的Activity(在项目中是ExecuteTestActivity.java)并不需要改变,只需要替换不同的测试内容(在项目中表现为继承了Test.java的子类),就可以达到显示不同的测试case的目的。对于很多case中界面部分逻辑,将它分离出来作为一个单独的工具类(还有一些其他的工具类,如控制计时器的类、部分控件监听器等),当有需要用到它时,它将作为具体测试内容的一个属性被使用。
- 本测试软件的设计是希望能够适用于不同项目的设备,而不同设备所具备的测试项是具有一定的差别的,所以mini软件中通过读取设备自身系统值来区分需要包含的case。
3. Solution
3.1. 通过读取设备信息来确定测试项信息
首先,将所有的case信息写入到./assets/Base.xml文件中,而Base.xml的结构如下所示:
<root>
<macros>
<macro>.....</macro>
</macros>
<deviceNodes>
<deviceNode>.....</deviceNode>
</deviceNodes>
</root>
子元素macro存储各个case的详细信息,主要包括case的全限定名、是否加入auto测试、是否在eng中显示、显示名、是否在mini版中显示等信息,其结构如下:
<macro name="com.jrdcom.mmitest.test.TraceabilityTest" auto="true" eng="true" listName="TRACEABILITY" mini="true" />
- name: 表示该测试项的全限定名。
- listName: 表示该测试项在MANU的list列表中显示的名字。
- eng:true表示在eng版本出现该测试项,false表示不出现。
- mini:true表示在mini版本中出现该测试项,false表示不出现。
- auto:true表示在AUTO模式出现该测试项,false表示不出现。
子元素deviceNode存储设备中各个驱动节点的信息,主要包括节点绝对路径和对应的变量名,其结构如下:
<deviceNode name="FrontCameraNode" path="/sys/class/deviceinfo/device_info/CamNameF" />
- name: 表示该驱动节点变量名。
- path: 表示该驱动节点绝对路径。
因为各个项目设备的驱动节点路径可能不同,将它从java代码中分离出来方便维护,在导入mini软件时,根据设备实际情况配置好各个驱动节点的路径即可。
配置项目自己的xml文件
./assets/Base.xml
文件存储了所有可能用到的case信息,但是针对具体项目,可能只是需要部分的测试case,所以通过项目自己的xml文件来进行调整。
如./assets/Base.xml
中记录了如下case:<macros>
<macro
name="com.jrdcom.mmitest.test.A"
auto="true"
eng="true"
listName="A"
mini="true" />
<macro
name="com.jrdcom.mmitest.test.B"
auto="true"
eng="false"
listName="B"
mini="true" />
<macro
name="com.jrdcom.mmitest.test.C"
auto="true"
eng="true"
listName="C"
mini="true" />
<macro
name="com.jrdcom.mmitest.test.D"
auto="true"
eng="true"
listName="D"
mini="true" />
<macro
name="com.jrdcom.mmitest.test.E"
auto="true"
eng="true"
listName="E"
mini="true" />
</macros>
现有
project_A
和project_B
2个项目,然后project_A
需要测试项A、B、D(只在自动测试模式出现)、E4个case,而project_B
需要测试项A(在mini测试模式不出现)、B、C(在自动测试模式不出现)、D、E5个case,则需要在project_A
项目设备上创建./assets/[ro.product.device].xml
文件,其文件内容如下:<macros>
<macro
name="com.jrdcom.mmitest.test.C"
auto="false"
eng="false"
listName="C"
mini="false" />
<macro
name="com.jrdcom.mmitest.test.D"
auto="true"
eng="false"
listName="D"
mini="false" />
</macros>
在
project_B
项目设备上创建./assets/[ro.product.device].xml
文件,其文件内容如下:<macros>
<macro
name="com.jrdcom.mmitest.test.A"
auto="true"
eng="true"
listName="A"
mini="false" />
<macro
name="com.jrdcom.mmitest.test.C"
auto="false"
eng="true"
listName="C"
mini="true" />
</macros>
即项目自己的xml文件优先级高于
./assets/Base.xml
优先级,对于相同的macro节点,项目的macro会覆盖./assets/Base.xml
中的macro,从而实现对各个case的调整。将最终的测试项信息存储在ArrayList中
首先,分别读取
./assets/Base.xml
中macro节点信息和./assets/[ro.product.device].xml
文件中macro节点信息,并将macro节点信息存储在PullXmlParser对象的List<DeviceNode> mDeviceNodeList
属性中private PullXmlParser parserXml() {
PullXmlParser pxp = new PullXmlParser(this);
InputStream baseIs = null;
try {
baseIs = getAssets().open(XML_FILE_PATH);
} catch (IOException e) {
Log.i(TAG, "Base.xml is not exist ");
alertWarnDialog("Base.xml");
return pxp;
}
pxp.parserBaseXml(baseIs);
File sdcard = Environment.getExternalStorageDirectory();
File testXml = new File(sdcard,"JrdMMITest.xml");
if(testXml.exists()){
try {
InputStream testIs = new FileInputStream(testXml);
pxp.parserModelXml(testIs);
} catch (FileNotFoundException e) {
Log.i(TAG, "JrdMMITest.xml is not exist ");
Log.i(TAG, e.toString());
}
}else {
String modelXmlPath = getModelXmlPath();
if (!(TextUtils.isEmpty(modelXmlPath))) {
InputStream modelIs = null;
try {
modelIs = getAssets().open(modelXmlPath);
} catch (IOException e) {
Log.i(TAG, modelXmlPath + ".xml is not exist ");
alertWarnDialog(modelXmlPath);
}
pxp.parserModelXml(modelIs);
}
}
然后,合并
./assets/Base.xml
中macro节点信息和./assets/[ro.product.device].xml
文件中macro节点信息。public void parserModelXml(InputStream is) {
// InputStream is = mContext.getClassLoader().getResourceAsStream(
// xmlFilePath);
if (is == null) {
Log.i(TAG, "Couldn't find model xml !");
return;
}
XmlPullParser xp = Xml.newPullParser();
try {
xp.setInput(is, "utf-8");
int type = xp.getEventType();
Macro macro = null;
DeviceNode deviceNode = null;
while (type != XmlPullParser.END_DOCUMENT) {
switch (type) {
case XmlPullParser.START_TAG:
if (XML_MACRO.equals(xp.getName())) {
String name = xp.getAttributeValue(null, XML_KEY_NAME);
String listName = xp.getAttributeValue(null,
XML_KEY_LIST_NAME);
Boolean eng = Boolean.parseBoolean(xp
.getAttributeValue(null, XML_KEY_ENG));
Boolean mini = Boolean.parseBoolean(xp
.getAttributeValue(null, XML_KEY_MINI));
Boolean auto = Boolean.parseBoolean(xp
.getAttributeValue(null, XML_KEY_AUTO));
Log.i(TAG, "name=" + name + ";eng=" + eng + ";mini="
+ mini + ";auto=" + auto);
macro = new Macro(name, listName, eng, mini, auto);
mergeMacroToBase(macro);
} else if (XML_DEVICE_NODE.equals(xp.getName())) {
String name = xp.getAttributeValue(null, XML_KEY_NAME);
String path = xp.getAttributeValue(null, XML_KEY_PATH);
Log.i(TAG, "name=" + name + ";path=" + path);
deviceNode = new DeviceNode(name, path);
mergeDeviceNodeToBase(deviceNode);
}
break;
}
try {
type = xp.next();
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void mergeMacroToBase(Macro macro) {
for (Macro m : mMacroList) {
if (m.getName().equals(macro.getName())) {
int index = mMacroList.indexOf(m);
m.setObject(macro);
mMacroList.set(index, m);
}
}
for (Macro m : mMacroList) {
Log.i(TAG, "mergeMacroToBase: " + m.toString());
}
}
最后,将最终的测试项信息存储在DeviceNodeList类的静态变量deviceNodeList中。
public class DeviceNodeList {
public static final String TAG = "com.jrdcom.mmitest.utils.DeviceNodeList";
private List<DeviceNode> deviceNodeList;
private static Map<String, String> deviceNodeMap;
public DeviceNodeList(List<DeviceNode> deviceNodeList) {
this.deviceNodeList = deviceNodeList;
}
public void init() {
deviceNodeMap = new HashMap<String, String>();
if (!deviceNodeList.isEmpty()) {
for (DeviceNode deviceNode : deviceNodeList) {
Log.i(TAG, deviceNode.toString());
deviceNodeMap.put(deviceNode.getName(), deviceNode.getPath());
}
}
}
public static String getDeviceNodePath(String deviceNodeName) {
return deviceNodeMap.get(deviceNodeName);
}
3.2 将测试项通过Activity加载并显示出来
因为所有的测试项都是继承共同的基类Test.java,所以各个测试项将作为执行Activity(ExecuteTestActivity)的属性,通过执行Activity的生命周期创建出来,其效果如下图所示。
Test.java具体代码如下所示:
public abstract class Test {
private String mName;
private Activity mCurrentActivity;
private volatile boolean preventDoublePass;
private static final String TAG = "com.jrdcom.mmitest.test.Test";
public enum RESULT {
PASS, FAIL, RETEST, NO, NEXT, YES, INTERRUPT
}
public static int MODE = 0;
public static final int AUTO = 1;
public static final int MANU = 2;
public static final int TEST_RETURN = 3;
public static final int FAIL_RETURN = 4;
public static final int RESULT_RETURN = 5;
public static final int INTERRUPT_RETURN = 6;
public Test(String mName) {
this.mName = mName;
preventDoublePass=true;
}
public String getName() {
return mName;
}
public void setName(String mName) {
this.mName = mName;
}
public Activity getContext() {
return mCurrentActivity;
}
public void setContext(Activity mCurrentActivity) {
this.mCurrentActivity = mCurrentActivity;
}
@Override
public String toString() {
return mName;
}
public abstract void onCreate(Bundle savedInstanceState);
public void onStart() {
}
public void onResume() {
}
public void onPause() {
}
public void onStop() {
}
public void onDestroy() {
}
public void onRestart() {
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
}
public void onNewIntent(Intent intent) {
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
return true;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
return true;
}
public boolean isNeedInterrupt(){
return true;
}
public boolean isNeedPreventKeyInManu(){
return false;
}
public void pass() {
synchronized (Test.class)
{
// TODO Auto-generated method stub
if (preventDoublePass)
{
preventDoublePass = false;
Log.v(TAG, "Test'pass is involved,it will fininshed current activity! ");
getContext().setResult(Test.RESULT.PASS.ordinal());
getContext().finish();
}
}
}
public void fail() {
synchronized (Test.class)
{
// TODO Auto-generated method stub
if (preventDoublePass)
{
preventDoublePass = false;
Log.v(TAG, "Test'fail is involved,it will fininshed current activity! ");
Intent intent = new Intent();
intent.putExtra("name", getName());
getContext().setResult(Test.RESULT.FAIL.ordinal(), intent);
getContext().finish();
}
}
}
}
其实Test.java就是根据Activity组件,创建了相对于它生命周期的方法,但是大部分方法都是空实现,而具体逻辑则是由继承Test.java的子类来实现。
ExecuteTestActivity.java的实现如下所示:
public class ExecuteTestActivity extends Activity {
private static final String TAG = "com.jrdcom.mmitest.activity.ExecuteTestActivity";
private Test currentTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int mask = WindowManager.LayoutParams.FLAG_FULLSCREEN;
mask |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
getWindow().setFlags(mask, mask);
currentTest = getCurrentTest();
currentTest.setContext(this);
currentTest.onCreate(savedInstanceState);
}
@Override
protected void onStart() {
super.onStart();
currentTest.onStart();
}
@Override
protected void onResume() {
super.onResume();
currentTest.onResume();
}
@Override
protected void onPause() {
super.onPause();
currentTest.onPause();
}
@Override
protected void onStop() {
super.onStop();
currentTest.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
currentTest.onDestroy();
}
@Override
protected void onRestart() {
super.onRestart();
currentTest.onRestart();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
currentTest.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
currentTest.onNewIntent(intent);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((getCurrentMode() == Test.AUTO) || isNeedPreventKeyInManu()) {
return currentTest.onKeyDown(keyCode, event);
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getCurrentMode() == Test.AUTO) {
if ((event.getEventTime() - event.getDownTime() > 500)
&& isNeedInterrupt()) {
Intent intent = new Intent();
intent.putExtra("name", currentTest.getName());
setResult(Test.RESULT.INTERRUPT.ordinal(), intent);
Log.i(TAG, "AUTO: KEYCODE_BACK to interrupt activity");
finish();
}
return currentTest.onKeyUp(keyCode, event);
}
return super.onKeyUp(keyCode, event);
}
private boolean isNeedInterrupt() {
return currentTest.isNeedInterrupt();
}
private boolean isNeedPreventKeyInManu(){
return currentTest.isNeedPreventKeyInManu();
}
private Test getCurrentTest() {
Intent intent = getIntent();
int position = intent.getIntExtra("position", 0);
Map<String, String> testMap = TestList.getTestList().get(position);
return Utils.getClassFromName(testMap.get("name"), testMap.get("listName"));
}
private int getCurrentMode() {
return getIntent().getIntExtra("mode", Test.MANU);
}
}
在ExecuteTestActivity中,通过getCurrentTest
方法获取测试case对应的Test类对象(如前面描述的A.java、B.java对象),然后通过适配器模式,将具体case的逻辑通过ExecuteTestActivity生命周期展示出来。
3.3 控制各个Activity之间跳转
在整个mini软件中,MiniActivity作为核心的Activity在软件的运行过程中一直存在,由它控制各个Activity之间的跳转,具体情况如下:
- 步骤一: 在进入mini软件后,会显示主界面MiniActivity,在该界面用户可以选择
Auto
测试和Manu
测试,如果点击Auto
测试,则进入步骤四;如果点击Manu
测试,则进入步骤二。 - 步骤二: 进入ManuListActivity界面,并显示所有
eng=ture
的macro节点的case,系统可根据用户点击的case项进入步骤三。 - 步骤三: 开启ExecuteTestActivity,并且根据用户点击的case项,实例化对应的Test对象,将该对象作为参数传入ExecuteTestActivity中运行。
- 步骤四: 判断ArrayList类型属性值mLastTestResult是否为空(每次
Auto
测试结束都会将测试失败结果保存到mLastTestResult中,默认第一次开机情况和上次测试全部通过情况mLastTestResult值为空),如果为空,进入步骤五,否则进入步骤 - 步骤五: 跳转到ExecuteTestActivity中,并设置
requestCode=Test.TEST_RETURN
,将测试项的index作为参数传入ExecuteTestActivity中(第一次进入,默认测试项的index值为0),然后进入步骤六 - 步骤六: 首先,启动ExecuteTestActivity,并且将传入的参数通过
getCurrentTest
方法构造出将要测试case的Test对象,然后通过ExecuteTestActivity将构造出的Test对象展示出来,最后根据用户的点击事件进行返回:
如果用户点击pass按钮,则finish掉ExecuteTestActivity,并设置resultCode=Test.RESULT.PASS
然后进入步骤七;
如果用户点击fail按钮,则则finish掉ExecuteTestActivity,并设置resultCode=Test.RESULT.FAIL
然后进入步骤七。 - 步骤七: 回到MiniActivity中,根据resultCode和requestCode值进行跳转:
如果requestCode=Test.TEST_RETURN
且resultCode=Test.RESULT.PASS
,进入步骤六,测试项的index++,并将它作为参数传入目标Activity。
如果requestCode=Test.TEST_RETURN
且resultCode=Test.RESULT.FAIL
,进入步骤九。
如果requestCode=Test.TEST_RETURN
且resultCode=Test.RESULT.INTERRUPT
,进入步骤八。
如果requestCode=Test.FAIL_RETURN
且resultCode=Test.RESULT.RETEST
,进入进入步骤六,测试项的index依旧保持原来的值,然后将它作为参数传入目标Activity。
如果requestCode=Test.FAIL_RETURN
且resultCode=Test.RESULT.NO
,将测试失败的结果保存到mLastTestResult中。
如果requestCode=Test.FAIL_RETURN
且resultCode=Test.Test.RESULT.NEXT
,进入步骤六,测试项的index++,并将它作为参数传入目标Activity。
如果requestCode=Test.RESULT_RETURN
且resultCode=Test.RESULT.YES
,进入步骤五。
如果requestCode=Test.INTERRUPT_RETURN且resultCode=Test.RESULT.RETEST,进入进入步骤六,测试项的index依旧保持原来的值,然后将它作为参数传入目标Activity。
如果requestCode=Test.INTERRUPT_RETURN
且resultCode=Test.RESULT.FAIL
,进入步骤九。 - 步骤八: 设置
requestCode=Test.INTERRUPT_RETURN
,并启动InterruptActivity,然后根据用户点击事件进行跳转:
如果点击RETEST
按钮,则finish掉InterruptActivity,并设置resultCode=Test.RESULT.RETEST
然后进入步骤七。
如果点击FAIL
按钮,则finish掉InterruptActivity,并设置resultCode=Test.RESULT.FAIL
然后进入步骤七。 - 步骤九: 设置
requestCode=Test.FAIL_RETURN
,并启动 FailedActivity,然后根据用户点击事件进行跳转:
如果点击RETEST
按钮,则finish掉FailedActivity,并设置resultCode=Test.RESULT.RETEST
然后进入步骤七。
如果点击NO
按钮,则finish掉FailedActivity,并设置resultCode=Test.RESULT.NO
然后进入步骤七。
如果点击NEXT
按钮,则finish掉FailedActivity,并设置resultCode=Test.RESULT.NEXT
然后进入步骤七。
MiniActivity中Activity跳转设计如下所示:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case Test.TEST_RETURN:
if (resultCode == Test.RESULT.PASS.ordinal()) {
if (mTestIndex == (TestList.getTestListSize() - 1)) {
if (!mCurrentTestResult.isEmpty()) {
mLastTestResult = mCurrentTestResult;
Log.i(TAG, "pass:mLastTestResult:" + mLastTestResult);
Log.i(TAG, "pass: show result activity");
startResultActivity(mLastTestResult);
} else {
startResultActivity(null);
}
break;
}
runTestAt(++mTestIndex);
} else if (resultCode == Test.RESULT.FAIL.ordinal()) {
String failedTestName = data.getStringExtra("name");
Log.i(TAG, "test is fail, save fail case to latest result, "
+ failedTestName + " is failed !");
saveFailedTestResult(failedTestName);
Log.i(TAG, "test is fail, show fail activity");
startFailedActivity(failedTestName);
} else if (resultCode == Test.RESULT.INTERRUPT.ordinal()) {
String interruptTestName = data.getStringExtra("name");
Log.i(TAG, "start interrupt activity");
startInterruptActivity(interruptTestName);
break;
}
break;
case Test.FAIL_RETURN:
if (resultCode == Test.RESULT.RETEST.ordinal()) {
String failedTestName = data.getStringExtra("name");
Log.i(TAG, "retest, , " + failedTestName
+ " test result should be delete !");
deleteFailedTestResult(failedTestName);
runTestAt(mTestIndex);
} else if (resultCode == Test.RESULT.NO.ordinal()) {
if (!mCurrentTestResult.isEmpty()) {
mLastTestResult = mCurrentTestResult;
if (mTestIndex != (TestList.getTestListSize() - 1)) {
String interruptResult = getInterruptTestResult();
mLastTestResult.add(interruptResult);
isNeedSaveToSP = true;
}
Log.i(TAG, "no:mLastTestResult:" + mLastTestResult);
}
} else if (resultCode == Test.RESULT.NEXT.ordinal()) {
if (mTestIndex == (TestList.getTestListSize() - 1)) {
if (!mCurrentTestResult.isEmpty()) {
mLastTestResult = mCurrentTestResult;
Log.i(TAG, "next:mLastTestResult:" + mLastTestResult);
Log.i(TAG, "next: show result activity");
startResultActivity(mLastTestResult);
}
break;
}
runTestAt(++mTestIndex);
}
break;
case Test.RESULT_RETURN:
if (resultCode == Test.RESULT.YES.ordinal()) {
mLastTestResult = null;
startAutoTest();
}
break;
case Test.INTERRUPT_RETURN:
if (resultCode == Test.RESULT.RETEST.ordinal()) {
runTestAt(mTestIndex);
} else if (resultCode == Test.RESULT.FAIL.ordinal()) {
String failedTestName = data.getStringExtra("name");
Log.i(TAG,
"interrupt: test is fail, save fail case to latest result, "
+ failedTestName + " is failed !");
saveFailedTestResult(failedTestName);
Log.i(TAG, "test is fail, show fail activity");
startFailedActivity(failedTestName);
}
break;
}
}
4. Summary
本次主要是对重构后mini软件进行总结,相对与前面一个本版,主要是对框架和具体测试case进行了分离,这样软件框架和具体测试case可以分开维护,即维护框架的人员只需要关注框架本身而不用了解具体测试case的内部逻辑,维护具体测试case人员只要知道框架提供的接口,就可以开发出和框架兼容的测试case。并在新的框架中使用了多种设计模式,优化了代码结构,类继承最多只到3层,更多的使用组合模式来连接不同的逻辑模块,相较前面的版本,可扩展性也有了较大的提高。但是发现重构后mini软件仍然有很多不足的地方,需要后面继续对其进行优化。