稍微修改昨天的測試標的,增加答對與答錯次數的紀錄,再用單元測試來做檢查。
BitOperatorActivity
public class BitOperatorActivity extends Activity implements OnClickListener { private static final String TAG = "BitOperatorActivity"; private static final String BIT_OR = "|"; private static final String BIT_NOT = "^"; private static final String BIT_AND = "&"; private List<String> operatorList = new ArrayList<String>(); private String aOperand; private String bOperand; private String operator; private TextView aTv; private TextView bTv; private TextView operatorTv; private EditText cOperand; private TextView sTv; private TextView fTv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.aTv = (TextView) this.findViewById(R.id.a); this.bTv = (TextView) this.findViewById(R.id.b); this.operatorTv = (TextView) this.findViewById(R.id.operator); this.cOperand = (EditText) this.findViewById(R.id.c); this.sTv = (TextView) this.findViewById(R.id.successCnt); this.fTv = (TextView) this.findViewById(R.id.failureCnt); ((Button) this.findViewById(R.id.btn)).setOnClickListener(this); this.operatorList.add(BitOperatorActivity.BIT_AND); this.operatorList.add(BitOperatorActivity.BIT_NOT); this.operatorList.add(BitOperatorActivity.BIT_OR); } @Override protected void onResume() { super.onResume(); this.newGame(); } private void newGame() { int a = (int) (100 * Math.random()); int b = (int) (100 * Math.random()); this.aOperand = Integer.toBinaryString(a); this.bOperand = Integer.toBinaryString(b); Collections.shuffle(this.operatorList); this.operator = this.operatorList.get(0); this.aTv.setText(this.aOperand); this.bTv.setText(this.bOperand); this.operatorTv.setText(this.operator); this.cOperand.setText(""); } /** for test only */ public String getOperator() { return this.operator; } public String bitOperate(String a, String b) { int aInt = Integer.parseInt(a, 2); int bInt = Integer.parseInt(b, 2); int cInt; if (this.operator.equals(BIT_AND)) { Log.d(TAG, "And operator"); cInt = aInt & bInt; } else if (this.operator.equals(BIT_NOT)) { Log.d(TAG, "Not operator"); cInt = aInt ^ bInt; } else if (this.operator.equals(BIT_OR)) { Log.d(TAG, "Or operator"); cInt = aInt | bInt; } else { Log.e(TAG, "Unknown operator: " + this.operator); Toast.makeText(this, "程式錯誤!", Toast.LENGTH_LONG).show(); return null; } return Integer.toString(cInt, 2); } @Override public void onClick(View v) { Log.d(TAG, "onclick..."); switch (v.getId()) { case R.id.btn: String answer = this.cOperand.getText().toString(); String rightAnswer = this.bitOperate(this.aOperand, this.bOperand); Log.d(TAG, "Right answer: " + rightAnswer); if (rightAnswer.equals(answer)) { Log.d(TAG, "You are right!"); Toast.makeText(this, "你答對了!", Toast.LENGTH_LONG).show(); this.sTv.setText(String.valueOf(Integer.parseInt(this.sTv.getText().toString()) + 1)); this.newGame(); } else { Log.d(TAG, "You are wrong! Your answer is " + answer + "."); Toast.makeText(this, "哇咧!你答錯了,再試一次!", Toast.LENGTH_LONG).show(); this.fTv.setText(String.valueOf(Integer.parseInt(this.fTv.getText().toString()) + 1)); } break; } } }main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" > <TextView android:id="@+id/success" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="成功:" /> <TextView android:id="@+id/successCnt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0" /> <TextView android:id="@+id/failure" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=", 失敗:" /> <TextView android:id="@+id/failureCnt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0" /> </LinearLayout> <TextView android:id="@+id/a" android:layout_width="60px" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="right" /> <TextView android:id="@+id/operator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="center" android:text="&" /> <TextView android:id="@+id/b" android:layout_width="60px" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="right" /> <TextView android:layout_width="wrap_content" android:layout_height="30px" android:layout_gravity="center_horizontal" android:gravity="center" android:text="等於" /> <EditText android:id="@+id/c" android:layout_width="100px" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="right" /> <Button android:id="@+id/btn" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:text="GO" /> </LinearLayout>
再來就是兩個 UI 單元測試的重點:
Activity.runOnUiThread()
UI 的動作要在 UI Thread 裡執行,不管是改值或觸發動作,不可以在單元測試的 Thread 裡執行,但是取值就沒關係。
Instrumentation.waitForIdleSync()
runOnUiThread() 不會 hold 住目前的 thread,所以會立即往下執行,因此得呼叫 waitForIdleSync() 來等待 UI Thread 執行完成,否則會發生不可預期的狀況。
BitOperatorActivityTest
public class BitOperatorActivityTest extends ActivityInstrumentationTestCase2<BitOperatorActivity> { private TextView aTv; private TextView bTv; private TextView operatorTv; private TextView sTv; private TextView fTv; private EditText cOperand; private Button goBtn; public BitOperatorActivityTest() { super("idv.neil.bitOperator", BitOperatorActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); // 是用測試標的的 idv.neil.bitOperator.R,不是測試 project 的 R this.aTv = (TextView) this.getActivity().findViewById(R.id.a); this.bTv = (TextView) this.getActivity().findViewById(R.id.b); this.operatorTv = (TextView) this.getActivity().findViewById( R.id.operator); this.sTv = (TextView) this.getActivity().findViewById(R.id.successCnt); this.fTv = (TextView) this.getActivity().findViewById(R.id.failureCnt); this.cOperand = (EditText) this.getActivity().findViewById(R.id.c); this.goBtn = (Button) this.getActivity().findViewById(R.id.btn); } @Override protected void tearDown() throws Exception { super.tearDown(); } public void testBitOperate() { BitOperatorActivity act = this.getActivity(); int aInt = 48; int bInt = 16; String a = Integer.toBinaryString(aInt); String b = Integer.toBinaryString(bInt); String c = act.bitOperate(a, b); int cInt = Integer.parseInt(c, 2); String operator = act.getOperator(); assertNotNull(operator); int answer = 0; if ("&".equals(operator)) { answer = aInt & bInt; } else if ("|".equals(operator)) { answer = aInt | bInt; } else if ("^".equals(operator)) { answer = aInt ^ bInt; } else { fail("Wrong operator - " + operator); } assertEquals(answer, cInt); } public void testCount() { // 答對 this.clickButton(this.getAnswer()); // 執行完成了,取值判斷 assertEquals("1", this.sTv.getText().toString()); assertEquals("0", this.fTv.getText().toString()); // 答錯 this.clickButton(this.getAnswer() + 1); // 執行完成了,取值判斷 assertEquals("1", this.sTv.getText().toString()); assertEquals("1", this.fTv.getText().toString()); // 又答錯 this.clickButton(this.getAnswer() - 1); // 執行完成了,取值判斷 assertEquals("1", this.sTv.getText().toString()); assertEquals("2", this.fTv.getText().toString()); // 總算答對 this.clickButton(this.getAnswer()); // 執行完成了,取值判斷 assertEquals("2", this.sTv.getText().toString()); assertEquals("2", this.fTv.getText().toString()); } private int getAnswer() { String a = this.aTv.getText().toString(); String b = this.bTv.getText().toString(); int aInt = Integer.parseInt(a, 2); int bInt = Integer.parseInt(b, 2); String operator = this.operatorTv.getText().toString(); final int c; if ("&".equals(operator)) { c = aInt & bInt; } else if ("|".equals(operator)) { c = aInt | bInt; } else if ("^".equals(operator)) { c = aInt ^ bInt; } else { fail("Wrong operator - " + operator); c = 0; } return c; } private void clickButton(final int c) { // UI 的動作要在 UI Thread 裡執行 // 不管是改值或觸發動作 this.getActivity().runOnUiThread(new Runnable() { @Override public void run() { cOperand.setText(Integer.toString(c, 2)); goBtn.performClick(); } }); // 等待 UI Thread 執行完成 this.getInstrumentation().waitForIdleSync(); } }完工。
Android Emulator Log:
Console Log,今天的 console log 比較短是因為 Android Emulator 已經啟動了:
沒有留言:
張貼留言