MINItest软件架构总结

————helloWen


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. 通过读取设备信息来确定测试项信息

  1. 首先,将所有的case信息写入到./assets/Base.xml文件中,而Base.xml的结构如下所示:

    1. <root>
    2. <macros>
    3. <macro>.....</macro>
    4. </macros>
    5. <deviceNodes>
    6. <deviceNode>.....</deviceNode>
    7. </deviceNodes>
    8. </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软件时,根据设备实际情况配置好各个驱动节点的路径即可。

  2. 配置项目自己的xml文件

    ./assets/Base.xml文件存储了所有可能用到的case信息,但是针对具体项目,可能只是需要部分的测试case,所以通过项目自己的xml文件来进行调整。
    ./assets/Base.xml中记录了如下case:

    1. <macros>
    2. <macro
    3. name="com.jrdcom.mmitest.test.A"
    4. auto="true"
    5. eng="true"
    6. listName="A"
    7. mini="true" />
    8. <macro
    9. name="com.jrdcom.mmitest.test.B"
    10. auto="true"
    11. eng="false"
    12. listName="B"
    13. mini="true" />
    14. <macro
    15. name="com.jrdcom.mmitest.test.C"
    16. auto="true"
    17. eng="true"
    18. listName="C"
    19. mini="true" />
    20. <macro
    21. name="com.jrdcom.mmitest.test.D"
    22. auto="true"
    23. eng="true"
    24. listName="D"
    25. mini="true" />
    26. <macro
    27. name="com.jrdcom.mmitest.test.E"
    28. auto="true"
    29. eng="true"
    30. listName="E"
    31. mini="true" />
    32. </macros>

    现有project_Aproject_B2个项目,然后project_A需要测试项A、B、D(只在自动测试模式出现)、E4个case,而project_B需要测试项A(在mini测试模式不出现)、B、C(在自动测试模式不出现)、D、E5个case,则需要在project_A项目设备上创建 ./assets/[ro.product.device].xml文件,其文件内容如下:

    1. <macros>
    2. <macro
    3. name="com.jrdcom.mmitest.test.C"
    4. auto="false"
    5. eng="false"
    6. listName="C"
    7. mini="false" />
    8. <macro
    9. name="com.jrdcom.mmitest.test.D"
    10. auto="true"
    11. eng="false"
    12. listName="D"
    13. mini="false" />
    14. </macros>

    project_B项目设备上创建 ./assets/[ro.product.device].xml文件,其文件内容如下:

    1. <macros>
    2. <macro
    3. name="com.jrdcom.mmitest.test.A"
    4. auto="true"
    5. eng="true"
    6. listName="A"
    7. mini="false" />
    8. <macro
    9. name="com.jrdcom.mmitest.test.C"
    10. auto="false"
    11. eng="true"
    12. listName="C"
    13. mini="true" />
    14. </macros>

    即项目自己的xml文件优先级高于./assets/Base.xml优先级,对于相同的macro节点,项目的macro会覆盖./assets/Base.xml中的macro,从而实现对各个case的调整。

  3. 将最终的测试项信息存储在ArrayList中

    • 首先,分别读取./assets/Base.xml中macro节点信息和./assets/[ro.product.device].xml文件中macro节点信息,并将macro节点信息存储在PullXmlParser对象的List<DeviceNode> mDeviceNodeList属性中

      1. private PullXmlParser parserXml() {
      2. PullXmlParser pxp = new PullXmlParser(this);
      3. InputStream baseIs = null;
      4. try {
      5. baseIs = getAssets().open(XML_FILE_PATH);
      6. } catch (IOException e) {
      7. Log.i(TAG, "Base.xml is not exist ");
      8. alertWarnDialog("Base.xml");
      9. return pxp;
      10. }
      11. pxp.parserBaseXml(baseIs);
      12. File sdcard = Environment.getExternalStorageDirectory();
      13. File testXml = new File(sdcard,"JrdMMITest.xml");
      14. if(testXml.exists()){
      15. try {
      16. InputStream testIs = new FileInputStream(testXml);
      17. pxp.parserModelXml(testIs);
      18. } catch (FileNotFoundException e) {
      19. Log.i(TAG, "JrdMMITest.xml is not exist ");
      20. Log.i(TAG, e.toString());
      21. }
      22. }else {
      23. String modelXmlPath = getModelXmlPath();
      24. if (!(TextUtils.isEmpty(modelXmlPath))) {
      25. InputStream modelIs = null;
      26. try {
      27. modelIs = getAssets().open(modelXmlPath);
      28. } catch (IOException e) {
      29. Log.i(TAG, modelXmlPath + ".xml is not exist ");
      30. alertWarnDialog(modelXmlPath);
      31. }
      32. pxp.parserModelXml(modelIs);
      33. }
      34. }
    • 然后,合并./assets/Base.xml中macro节点信息和./assets/[ro.product.device].xml文件中macro节点信息。

      1. public void parserModelXml(InputStream is) {
      2. // InputStream is = mContext.getClassLoader().getResourceAsStream(
      3. // xmlFilePath);
      4. if (is == null) {
      5. Log.i(TAG, "Couldn't find model xml !");
      6. return;
      7. }
      8. XmlPullParser xp = Xml.newPullParser();
      9. try {
      10. xp.setInput(is, "utf-8");
      11. int type = xp.getEventType();
      12. Macro macro = null;
      13. DeviceNode deviceNode = null;
      14. while (type != XmlPullParser.END_DOCUMENT) {
      15. switch (type) {
      16. case XmlPullParser.START_TAG:
      17. if (XML_MACRO.equals(xp.getName())) {
      18. String name = xp.getAttributeValue(null, XML_KEY_NAME);
      19. String listName = xp.getAttributeValue(null,
      20. XML_KEY_LIST_NAME);
      21. Boolean eng = Boolean.parseBoolean(xp
      22. .getAttributeValue(null, XML_KEY_ENG));
      23. Boolean mini = Boolean.parseBoolean(xp
      24. .getAttributeValue(null, XML_KEY_MINI));
      25. Boolean auto = Boolean.parseBoolean(xp
      26. .getAttributeValue(null, XML_KEY_AUTO));
      27. Log.i(TAG, "name=" + name + ";eng=" + eng + ";mini="
      28. + mini + ";auto=" + auto);
      29. macro = new Macro(name, listName, eng, mini, auto);
      30. mergeMacroToBase(macro);
      31. } else if (XML_DEVICE_NODE.equals(xp.getName())) {
      32. String name = xp.getAttributeValue(null, XML_KEY_NAME);
      33. String path = xp.getAttributeValue(null, XML_KEY_PATH);
      34. Log.i(TAG, "name=" + name + ";path=" + path);
      35. deviceNode = new DeviceNode(name, path);
      36. mergeDeviceNodeToBase(deviceNode);
      37. }
      38. break;
      39. }
      40. try {
      41. type = xp.next();
      42. } catch (IOException e) {
      43. e.printStackTrace();
      44. }
      45. }
      46. } catch (XmlPullParserException e) {
      47. e.printStackTrace();
      48. } finally {
      49. try {
      50. is.close();
      51. } catch (IOException e) {
      52. e.printStackTrace();
      53. }
      54. }
      55. }
      56. private void mergeMacroToBase(Macro macro) {
      57. for (Macro m : mMacroList) {
      58. if (m.getName().equals(macro.getName())) {
      59. int index = mMacroList.indexOf(m);
      60. m.setObject(macro);
      61. mMacroList.set(index, m);
      62. }
      63. }
      64. for (Macro m : mMacroList) {
      65. Log.i(TAG, "mergeMacroToBase: " + m.toString());
      66. }
      67. }
    • 最后,将最终的测试项信息存储在DeviceNodeList类的静态变量deviceNodeList中。

      1. public class DeviceNodeList {
      2. public static final String TAG = "com.jrdcom.mmitest.utils.DeviceNodeList";
      3. private List<DeviceNode> deviceNodeList;
      4. private static Map<String, String> deviceNodeMap;
      5. public DeviceNodeList(List<DeviceNode> deviceNodeList) {
      6. this.deviceNodeList = deviceNodeList;
      7. }
      8. public void init() {
      9. deviceNodeMap = new HashMap<String, String>();
      10. if (!deviceNodeList.isEmpty()) {
      11. for (DeviceNode deviceNode : deviceNodeList) {
      12. Log.i(TAG, deviceNode.toString());
      13. deviceNodeMap.put(deviceNode.getName(), deviceNode.getPath());
      14. }
      15. }
      16. }
      17. public static String getDeviceNodePath(String deviceNodeName) {
      18. return deviceNodeMap.get(deviceNodeName);
      19. }

3.2 将测试项通过Activity加载并显示出来

因为所有的测试项都是继承共同的基类Test.java,所以各个测试项将作为执行Activity(ExecuteTestActivity)的属性,通过执行Activity的生命周期创建出来,其效果如下图所示。

Test.java具体代码如下所示:

  1. public abstract class Test {
  2. private String mName;
  3. private Activity mCurrentActivity;
  4. private volatile boolean preventDoublePass;
  5. private static final String TAG = "com.jrdcom.mmitest.test.Test";
  6. public enum RESULT {
  7. PASS, FAIL, RETEST, NO, NEXT, YES, INTERRUPT
  8. }
  9. public static int MODE = 0;
  10. public static final int AUTO = 1;
  11. public static final int MANU = 2;
  12. public static final int TEST_RETURN = 3;
  13. public static final int FAIL_RETURN = 4;
  14. public static final int RESULT_RETURN = 5;
  15. public static final int INTERRUPT_RETURN = 6;
  16. public Test(String mName) {
  17. this.mName = mName;
  18. preventDoublePass=true;
  19. }
  20. public String getName() {
  21. return mName;
  22. }
  23. public void setName(String mName) {
  24. this.mName = mName;
  25. }
  26. public Activity getContext() {
  27. return mCurrentActivity;
  28. }
  29. public void setContext(Activity mCurrentActivity) {
  30. this.mCurrentActivity = mCurrentActivity;
  31. }
  32. @Override
  33. public String toString() {
  34. return mName;
  35. }
  36. public abstract void onCreate(Bundle savedInstanceState);
  37. public void onStart() {
  38. }
  39. public void onResume() {
  40. }
  41. public void onPause() {
  42. }
  43. public void onStop() {
  44. }
  45. public void onDestroy() {
  46. }
  47. public void onRestart() {
  48. }
  49. public void onActivityResult(int requestCode, int resultCode, Intent data) {
  50. }
  51. public void onNewIntent(Intent intent) {
  52. }
  53. public boolean onKeyDown(int keyCode, KeyEvent event) {
  54. return true;
  55. }
  56. public boolean onKeyUp(int keyCode, KeyEvent event) {
  57. return true;
  58. }
  59. public boolean isNeedInterrupt(){
  60. return true;
  61. }
  62. public boolean isNeedPreventKeyInManu(){
  63. return false;
  64. }
  65. public void pass() {
  66. synchronized (Test.class)
  67. {
  68. // TODO Auto-generated method stub
  69. if (preventDoublePass)
  70. {
  71. preventDoublePass = false;
  72. Log.v(TAG, "Test'pass is involved,it will fininshed current activity! ");
  73. getContext().setResult(Test.RESULT.PASS.ordinal());
  74. getContext().finish();
  75. }
  76. }
  77. }
  78. public void fail() {
  79. synchronized (Test.class)
  80. {
  81. // TODO Auto-generated method stub
  82. if (preventDoublePass)
  83. {
  84. preventDoublePass = false;
  85. Log.v(TAG, "Test'fail is involved,it will fininshed current activity! ");
  86. Intent intent = new Intent();
  87. intent.putExtra("name", getName());
  88. getContext().setResult(Test.RESULT.FAIL.ordinal(), intent);
  89. getContext().finish();
  90. }
  91. }
  92. }
  93. }

其实Test.java就是根据Activity组件,创建了相对于它生命周期的方法,但是大部分方法都是空实现,而具体逻辑则是由继承Test.java的子类来实现。

ExecuteTestActivity.java的实现如下所示:

  1. public class ExecuteTestActivity extends Activity {
  2. private static final String TAG = "com.jrdcom.mmitest.activity.ExecuteTestActivity";
  3. private Test currentTest;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. int mask = WindowManager.LayoutParams.FLAG_FULLSCREEN;
  8. mask |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
  9. getWindow().setFlags(mask, mask);
  10. currentTest = getCurrentTest();
  11. currentTest.setContext(this);
  12. currentTest.onCreate(savedInstanceState);
  13. }
  14. @Override
  15. protected void onStart() {
  16. super.onStart();
  17. currentTest.onStart();
  18. }
  19. @Override
  20. protected void onResume() {
  21. super.onResume();
  22. currentTest.onResume();
  23. }
  24. @Override
  25. protected void onPause() {
  26. super.onPause();
  27. currentTest.onPause();
  28. }
  29. @Override
  30. protected void onStop() {
  31. super.onStop();
  32. currentTest.onStop();
  33. }
  34. @Override
  35. protected void onDestroy() {
  36. super.onDestroy();
  37. currentTest.onDestroy();
  38. }
  39. @Override
  40. protected void onRestart() {
  41. super.onRestart();
  42. currentTest.onRestart();
  43. }
  44. @Override
  45. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  46. super.onActivityResult(requestCode, resultCode, data);
  47. currentTest.onActivityResult(requestCode, resultCode, data);
  48. }
  49. @Override
  50. protected void onNewIntent(Intent intent) {
  51. super.onNewIntent(intent);
  52. currentTest.onNewIntent(intent);
  53. }
  54. @Override
  55. public boolean onKeyDown(int keyCode, KeyEvent event) {
  56. if ((getCurrentMode() == Test.AUTO) || isNeedPreventKeyInManu()) {
  57. return currentTest.onKeyDown(keyCode, event);
  58. }
  59. return super.onKeyDown(keyCode, event);
  60. }
  61. @Override
  62. public boolean onKeyUp(int keyCode, KeyEvent event) {
  63. if (getCurrentMode() == Test.AUTO) {
  64. if ((event.getEventTime() - event.getDownTime() > 500)
  65. && isNeedInterrupt()) {
  66. Intent intent = new Intent();
  67. intent.putExtra("name", currentTest.getName());
  68. setResult(Test.RESULT.INTERRUPT.ordinal(), intent);
  69. Log.i(TAG, "AUTO: KEYCODE_BACK to interrupt activity");
  70. finish();
  71. }
  72. return currentTest.onKeyUp(keyCode, event);
  73. }
  74. return super.onKeyUp(keyCode, event);
  75. }
  76. private boolean isNeedInterrupt() {
  77. return currentTest.isNeedInterrupt();
  78. }
  79. private boolean isNeedPreventKeyInManu(){
  80. return currentTest.isNeedPreventKeyInManu();
  81. }
  82. private Test getCurrentTest() {
  83. Intent intent = getIntent();
  84. int position = intent.getIntExtra("position", 0);
  85. Map<String, String> testMap = TestList.getTestList().get(position);
  86. return Utils.getClassFromName(testMap.get("name"), testMap.get("listName"));
  87. }
  88. private int getCurrentMode() {
  89. return getIntent().getIntExtra("mode", Test.MANU);
  90. }
  91. }

在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_RETURNresultCode=Test.RESULT.PASS,进入步骤六,测试项的index++,并将它作为参数传入目标Activity。
    如果requestCode=Test.TEST_RETURNresultCode=Test.RESULT.FAIL,进入步骤九。
    如果requestCode=Test.TEST_RETURNresultCode=Test.RESULT.INTERRUPT,进入步骤八。
    如果requestCode=Test.FAIL_RETURNresultCode=Test.RESULT.RETEST,进入进入步骤六,测试项的index依旧保持原来的值,然后将它作为参数传入目标Activity。
    如果requestCode=Test.FAIL_RETURNresultCode=Test.RESULT.NO,将测试失败的结果保存到mLastTestResult中。
    如果requestCode=Test.FAIL_RETURNresultCode=Test.Test.RESULT.NEXT,进入步骤六,测试项的index++,并将它作为参数传入目标Activity。
    如果requestCode=Test.RESULT_RETURNresultCode=Test.RESULT.YES,进入步骤五。
    如果requestCode=Test.INTERRUPT_RETURN且resultCode=Test.RESULT.RETEST,进入进入步骤六,测试项的index依旧保持原来的值,然后将它作为参数传入目标Activity。
    如果requestCode=Test.INTERRUPT_RETURNresultCode=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跳转设计如下所示:

  1. @Override
  2. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  3. switch (requestCode) {
  4. case Test.TEST_RETURN:
  5. if (resultCode == Test.RESULT.PASS.ordinal()) {
  6. if (mTestIndex == (TestList.getTestListSize() - 1)) {
  7. if (!mCurrentTestResult.isEmpty()) {
  8. mLastTestResult = mCurrentTestResult;
  9. Log.i(TAG, "pass:mLastTestResult:" + mLastTestResult);
  10. Log.i(TAG, "pass: show result activity");
  11. startResultActivity(mLastTestResult);
  12. } else {
  13. startResultActivity(null);
  14. }
  15. break;
  16. }
  17. runTestAt(++mTestIndex);
  18. } else if (resultCode == Test.RESULT.FAIL.ordinal()) {
  19. String failedTestName = data.getStringExtra("name");
  20. Log.i(TAG, "test is fail, save fail case to latest result, "
  21. + failedTestName + " is failed !");
  22. saveFailedTestResult(failedTestName);
  23. Log.i(TAG, "test is fail, show fail activity");
  24. startFailedActivity(failedTestName);
  25. } else if (resultCode == Test.RESULT.INTERRUPT.ordinal()) {
  26. String interruptTestName = data.getStringExtra("name");
  27. Log.i(TAG, "start interrupt activity");
  28. startInterruptActivity(interruptTestName);
  29. break;
  30. }
  31. break;
  32. case Test.FAIL_RETURN:
  33. if (resultCode == Test.RESULT.RETEST.ordinal()) {
  34. String failedTestName = data.getStringExtra("name");
  35. Log.i(TAG, "retest, , " + failedTestName
  36. + " test result should be delete !");
  37. deleteFailedTestResult(failedTestName);
  38. runTestAt(mTestIndex);
  39. } else if (resultCode == Test.RESULT.NO.ordinal()) {
  40. if (!mCurrentTestResult.isEmpty()) {
  41. mLastTestResult = mCurrentTestResult;
  42. if (mTestIndex != (TestList.getTestListSize() - 1)) {
  43. String interruptResult = getInterruptTestResult();
  44. mLastTestResult.add(interruptResult);
  45. isNeedSaveToSP = true;
  46. }
  47. Log.i(TAG, "no:mLastTestResult:" + mLastTestResult);
  48. }
  49. } else if (resultCode == Test.RESULT.NEXT.ordinal()) {
  50. if (mTestIndex == (TestList.getTestListSize() - 1)) {
  51. if (!mCurrentTestResult.isEmpty()) {
  52. mLastTestResult = mCurrentTestResult;
  53. Log.i(TAG, "next:mLastTestResult:" + mLastTestResult);
  54. Log.i(TAG, "next: show result activity");
  55. startResultActivity(mLastTestResult);
  56. }
  57. break;
  58. }
  59. runTestAt(++mTestIndex);
  60. }
  61. break;
  62. case Test.RESULT_RETURN:
  63. if (resultCode == Test.RESULT.YES.ordinal()) {
  64. mLastTestResult = null;
  65. startAutoTest();
  66. }
  67. break;
  68. case Test.INTERRUPT_RETURN:
  69. if (resultCode == Test.RESULT.RETEST.ordinal()) {
  70. runTestAt(mTestIndex);
  71. } else if (resultCode == Test.RESULT.FAIL.ordinal()) {
  72. String failedTestName = data.getStringExtra("name");
  73. Log.i(TAG,
  74. "interrupt: test is fail, save fail case to latest result, "
  75. + failedTestName + " is failed !");
  76. saveFailedTestResult(failedTestName);
  77. Log.i(TAG, "test is fail, show fail activity");
  78. startFailedActivity(failedTestName);
  79. }
  80. break;
  81. }
  82. }

4. Summary

本次主要是对重构后mini软件进行总结,相对与前面一个本版,主要是对框架和具体测试case进行了分离,这样软件框架和具体测试case可以分开维护,即维护框架的人员只需要关注框架本身而不用了解具体测试case的内部逻辑,维护具体测试case人员只要知道框架提供的接口,就可以开发出和框架兼容的测试case。并在新的框架中使用了多种设计模式,优化了代码结构,类继承最多只到3层,更多的使用组合模式来连接不同的逻辑模块,相较前面的版本,可扩展性也有了较大的提高。但是发现重构后mini软件仍然有很多不足的地方,需要后面继续对其进行优化。