Android自動(dòng)化測(cè)試--學(xué)習(xí)淺談

前言

想想接觸Android也有三年多的時(shí)間了证薇,實(shí)際開(kāi)發(fā)也有兩年的時(shí)間了,好像也很少接觸到Android自動(dòng)化測(cè)試匆篓,雖然偶有聽(tīng)說(shuō)浑度,但也沒(méi)有認(rèn)真的學(xué)習(xí)過(guò)。相信很多朋友跟我也有一樣的經(jīng)歷鸦概,對(duì)自動(dòng)化測(cè)試不了解箩张,加上項(xiàng)目沒(méi)有要求,認(rèn)為自動(dòng)化測(cè)試價(jià)值不高窗市,完全是浪費(fèi)時(shí)間先慷。但實(shí)際的情況并不是這樣,前段時(shí)間聽(tīng)一個(gè)朋友講了些Android自動(dòng)化測(cè)試咨察,給了我很深的印象熟掂。正因?yàn)檫@樣的契機(jī),所以前段時(shí)間也花時(shí)間學(xué)了Android基本的自動(dòng)化測(cè)試扎拣。趁最近剛好有空整理了一下自己的學(xué)習(xí)心得。

大家可以看一下這篇文章素跺,可能會(huì)說(shuō)服你:為什么要進(jìn)行煩人的單元測(cè)試二蓝?

Android Testing Support Library

在2015年Google I/O大會(huì)上,Google放出了一個(gè)Android Testing Support Library指厌,該庫(kù)提供了大量用于測(cè)試 Android 應(yīng)用的框架刊愚。此庫(kù)提供了一組 API,讓您可以為應(yīng)用快速構(gòu)建何運(yùn)行測(cè)試代碼踩验,包括單元測(cè)試 JUnit 4 和功能性用戶(hù)界面 (UI) 測(cè)試鸥诽。我們可以從Android Studio IDE或命令行運(yùn)行使用這些 API 創(chuàng)建的測(cè)試。 在測(cè)試庫(kù)中包含AndroidJUnitRunner類(lèi)是一個(gè)JUnit測(cè)試運(yùn)行器箕憾,可讓我們?cè)?Android 設(shè)備上運(yùn)行 JUnit 3 或 JUnit 4 樣式測(cè)試類(lèi)牡借,包括使用Espresso和UI Automator測(cè)試框架的設(shè)備。測(cè)試運(yùn)行器可以將測(cè)試軟件包和要測(cè)試的應(yīng)用加載到設(shè)備袭异、運(yùn)行測(cè)試并報(bào)告測(cè)試結(jié)果钠龙。所以后面會(huì)講到的單元測(cè)試和UI測(cè)試的詳細(xì)使用,都是基于Android Testing Support Library。

單元測(cè)試 JUnit 4

我們?cè)趯?shí)際項(xiàng)目開(kāi)發(fā)中的時(shí)候碴里,都是需要寫(xiě)成千上萬(wàn)個(gè)方法或函數(shù)沈矿,這些函數(shù)的功能可能很強(qiáng)大,也可能是很小一個(gè)功能咬腋,但我們?cè)诔绦蛑惺褂脮r(shí)都是需要經(jīng)過(guò)測(cè)試的羹膳,保證這一部分功能是正確的。所以說(shuō)根竿,每編寫(xiě)完一個(gè)函數(shù)之后陵像,都應(yīng)該對(duì)這個(gè)函數(shù)的方方面面進(jìn)行測(cè)試,這樣的測(cè)試我們稱(chēng)之為單元測(cè)試犀填。傳統(tǒng)的編程方式蠢壹,進(jìn)行單元測(cè)試是一件很麻煩的事情,我們需要在該程序中調(diào)用你需要測(cè)試的方法九巡,并且仔細(xì)觀(guān)察運(yùn)行結(jié)果图贸,看看是否有錯(cuò)。正因?yàn)槿绱寺闊┟峁悖跃陀辛撕芏鄦卧獪y(cè)試框架疏日,JUnit 4就是其中一種。

本地單元測(cè)試 Local Unit Tests

這種測(cè)試運(yùn)行在本地開(kāi)發(fā)環(huán)境的Java虛擬機(jī)上撒汉,也不需要連接Android設(shè)備或者模擬器沟优,因此并無(wú)法獲得Android相關(guān)的API,所以只能測(cè)試只使用Java API的一些功能睬辐。

測(cè)試類(lèi)代碼編寫(xiě)也很簡(jiǎn)單挠阁,主要通過(guò)一些注解來(lái)標(biāo)示,同時(shí)可以通過(guò)assertXXXX來(lái)斷言結(jié)果

public class ExampleUnitTest {
  @Test
  public void addition_isCorrect() throws Exception {
      assertEquals(4, 4);
  }
}

Junit 4注解

  • @Before標(biāo)注setup方法溯饵,每個(gè)單元測(cè)試用例方法調(diào)用之前都會(huì)調(diào)用
  • @After標(biāo)注teardown方法侵俗,每個(gè)單元測(cè)試用例方法調(diào)用之后都會(huì)調(diào)用
  • @Test標(biāo)注的每個(gè)方法都是一個(gè)測(cè)試用例
  • @BeforeClass標(biāo)注的靜態(tài)方法,在當(dāng)前測(cè)試類(lèi)所有用例方法執(zhí)行之前執(zhí)行
  • @AfterClass標(biāo)注的靜態(tài)方法丰刊,在當(dāng)前測(cè)試類(lèi)所有用例方法執(zhí)行之后執(zhí)行
  • @Test(timeout=)為測(cè)試用例指定超時(shí)時(shí)間

斷言
Junit提供了一系列斷言來(lái)判斷是pass還是fail

  • assertTrue(condition):condition為真pass隘谣,否則fail
  • assertFalse(condition):condition為假pass,否則fail
  • fail():直接fail
  • assertEquals(expected, actual):expected equal actual pass啄巧,否則fail
  • assertSame(expected, actual):expected == actual pass寻歧,否則fail

設(shè)備單元測(cè)試 Instrumented Unit Tests

這種測(cè)試方式需要連接Android設(shè)備或模擬器≈绕停可以利用Android框架API码泛,比如測(cè)試需要訪(fǎng)問(wèn)設(shè)備信息(如目標(biāo)應(yīng)用程序的上下文中)或如果他們需要一個(gè)Android 相關(guān)的API(如Parcelable或SharedPreferences對(duì)象)。在使用上也很簡(jiǎn)單澄耍,相比本地單元測(cè)試該測(cè)試類(lèi)必須以 @RunWith(AndroidJUnit4.class) 注解作為前綴弟晚。

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();
        assertEquals("top.qingningshe.test", appContext.getPackageName());
    }
}

相比Local Unit Tests 多了訪(fǎng)問(wèn)設(shè)備信息忘衍、測(cè)試篩選

訪(fǎng)問(wèn)設(shè)備信息

我們可以使用 InstrumentationRegistry
類(lèi)訪(fǎng)問(wèn)與測(cè)試運(yùn)行相關(guān)的信息。此類(lèi)包括 Instrumentation對(duì)象卿城、目標(biāo)應(yīng)用Context對(duì)象枚钓、測(cè)試應(yīng)用Context對(duì)象,以及傳遞到測(cè)試中的命令行參數(shù)瑟押。

測(cè)試篩選

  • @RequiresDevice:指定測(cè)試僅在物理設(shè)備而不在模擬器上運(yùn)行搀捷。
  • @SdkSupress:禁止在低于給定級(jí)別的 Android API 級(jí)別上運(yùn)行測(cè)試。例如多望,要禁止在低于 18 的所有 API 級(jí)別上運(yùn)行測(cè)試嫩舟,請(qǐng)使用注解 @SDKSupress(minSdkVersion=18)。
  • @SmallTest怀偷、@MediumTest和@LargeTest:指定測(cè)試的運(yùn)行時(shí)長(zhǎng)以及運(yùn)行頻率家厌。

UI測(cè)試

Espresso

Espresso 測(cè)試框架提供了一組 API 來(lái)構(gòu)建 UI 測(cè)試,用于測(cè)試應(yīng)用中的用戶(hù)流椎工。利用這些 API饭于,您可以編寫(xiě)簡(jiǎn)潔、運(yùn)行可靠的自動(dòng)化 UI 測(cè)試维蒙。Espresso 非常適合編寫(xiě)白盒自動(dòng)化測(cè)試掰吕,其中測(cè)試代碼將利用所測(cè)試應(yīng)用的實(shí)現(xiàn)代碼詳情。

Espresso 測(cè)試框架的主要功能包括:

  • 靈活的 API颅痊,用于目標(biāo)應(yīng)用中的視圖和適配器匹配殖熟。
  • 一組豐富的操作 API,用于自動(dòng)化 UI 交互斑响。
  • UI 線(xiàn)程同步菱属,用于提升測(cè)試可靠性。

要求 Android 2.2(API 級(jí)別 8)或更高版本舰罚。

視圖匹配

利用Espresso.onView()方法纽门,您可以訪(fǎng)問(wèn)目標(biāo)應(yīng)用中的 UI 組件并與之交互。此方法接受Matcher參數(shù)并搜索視圖層次結(jié)構(gòu)沸停,以找到符合給定條件的相應(yīng)View實(shí)例。您可以通過(guò)指定以下條件來(lái)優(yōu)化搜索:

  • 視圖的類(lèi)名稱(chēng) onView(withClassName());
  • 視圖的內(nèi)容描述 onView(withContentDescription());
  • 視圖的ID onView(withId());
  • 在視圖中顯示的文本 onView(withText());

更多的可以查看ViewMatchers昭卓。如果搜索成功愤钾,onView()方法將返回一個(gè)引用,讓您可以執(zhí)行用戶(hù)操作并基于目標(biāo)視圖對(duì)斷言進(jìn)行測(cè)試候醒。

適配器匹配

AdapterView布局中能颁,布局在運(yùn)行時(shí)由子視圖動(dòng)態(tài)填充。如果目標(biāo)視圖位于某個(gè)布局內(nèi)部倒淫,而該布局是從AdapterView(例如ListViewGridView)派生出的子類(lèi)伙菊,則onView()方法可能無(wú)法工作,因?yàn)橹挥胁季忠晥D的子集會(huì)加載到當(dāng)前視圖層次結(jié)構(gòu)中。因此镜硕,需要使用Espresso.onData()方法訪(fǎng)問(wèn)目標(biāo)視圖元素运翼。Espresso.onData()方法將返回一個(gè)引用,讓您可以執(zhí)行用戶(hù)操作并根據(jù)AdapterView中的元素對(duì)斷言進(jìn)行測(cè)試兴枯。

//點(diǎn)擊spinner
onView(withId(R.id.spinner)).perform(click());
//點(diǎn)擊adpaterviewer中類(lèi)型為String 并且內(nèi)容為test的文本血淌,
onData(allOf(is(instanceOf(String.class)),is("test"))).perform(click());

操作API

在上面的一段代碼中,我們用到了perform(click())财剖,那么除了click()方法還有其他功能強(qiáng)大的方法可以供我們使用悠夯,下面列舉一些常用的方法:

  • click():返回一個(gè)點(diǎn)擊動(dòng)作,Espresso利用這個(gè)方法執(zhí)行一次點(diǎn)擊操作,就和我們自己手動(dòng)點(diǎn)擊按鈕一樣躺坟。
  • clearText():返回一個(gè)清除指定view中的文本action沦补,在測(cè)試EditText時(shí)用的比較多。
  • swipeLeft():返回一個(gè)從右往左滑動(dòng)的action,這個(gè)在測(cè)試ViewPager時(shí)特別有用咪橙。
  • swipeRight():返回一個(gè)從左往右滑動(dòng)的action,這個(gè)在測(cè)試ViewPager時(shí)特別有用夕膀。
  • swipeDown():返回一個(gè)從上往下滑動(dòng)的action。
  • swipeUp():返回一個(gè)從下往上滑動(dòng)的action匣摘。
  • closeSoftKeyboard():返回一個(gè)關(guān)閉輸入鍵盤(pán)的action店诗。
  • pressBack():返回一個(gè)點(diǎn)擊手機(jī)上返回鍵的action。
  • doubleClick():返回一個(gè)雙擊action
  • longClick():返回一個(gè)長(zhǎng)按action

更多的可以查看ViewActions音榜。

校驗(yàn)結(jié)果

調(diào)用ViewInteraction.check()DataInteraction.check()方法庞瘸,可以判斷UI元素的狀態(tài),如果斷言失敗赠叼,會(huì)拋出AssertionFailedError異常擦囊。

  • doesNotExist:斷言某一個(gè)view不存在。
  • matches:斷言某個(gè)view存在嘴办,且符合一列的匹配瞬场。
  • selectedDescendentsMatch:斷言指定的子元素存在,且他們的狀態(tài)符合一些列的匹配涧郊。
onView(withId(R.id.textview)).check(matches(withText("test")));

UI 線(xiàn)程同步

Espresso 的核心是它可以與待測(cè)應(yīng)用無(wú)縫同步測(cè)試操作的能力贯被。默認(rèn)情況下,Espresso 會(huì)等待當(dāng)前消息隊(duì)列中的 UI 事件執(zhí)行(默認(rèn)是 AsyncTask)完畢再進(jìn)行下一個(gè)測(cè)試操作妆艘。這應(yīng)該能解決大部分應(yīng)用與測(cè)試同步的問(wèn)題彤灶。然而,應(yīng)用中有一些執(zhí)行后臺(tái)操作的對(duì)象(比如與網(wǎng)絡(luò)服務(wù)交互)通過(guò)非標(biāo)準(zhǔn)方式實(shí)現(xiàn)批旺;例如:直接創(chuàng)建和管理線(xiàn)程幌陕,以及使用自定義服務(wù)。慶幸的是 Espresso 仍然可以同步測(cè)試操作與你的自定義資源汽煮。

以下是我們需要完成的:

  • 實(shí)現(xiàn) ?IdlingResource 接口并暴露給測(cè)試搏熄。
  • 通過(guò)調(diào)用ResourceCallback..onTransitionToIdle()通知Espresso棚唆。

需要注意的是 IdlingResource 接口是在待測(cè)應(yīng)用中實(shí)現(xiàn)的,所以你需要添加依賴(lài):

 compile 'com.android.support.test.espresso:espresso-idling-resource:2.2.2'

下面我們看看官方的例子是如何實(shí)現(xiàn)的

public final class CountingIdlingResource implements IdlingResource {
private static final String TAG = "CountingIdlingResource";
private final String resourceName;
private final AtomicInteger counter = new AtomicInteger(0);
private final boolean debugCounting;
// written from main thread, read from any thread.
private volatile ResourceCallback resourceCallback;
// read/written from any thread - used for debugging messages.
private volatile long becameBusyAt = 0;
private volatile long becameIdleAt = 0;
/**
 * Creates a CountingIdlingResource without debug tracing.
 *
 * @param resourceName the resource name this resource should report to Espresso.
 */
public CountingIdlingResource(String resourceName) {
  this(resourceName, false);
}
/**
 * Creates a CountingIdlingResource.
 *
 * @param resourceName the resource name this resource should report to Espresso.
 * @param debugCounting if true increment & decrement calls will print trace information to logs.
 */
public CountingIdlingResource(String resourceName, boolean   debugCounting) {
  this.resourceName = checkNotNull(resourceName);
  this.debugCounting = debugCounting;
}
@Override
public String getName() {
  return resourceName;
}
@Override
public boolean isIdleNow() {
  return counter.get() == 0;
}
@Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
  this.resourceCallback = resourceCallback;
}
/**
 * Increments the count of in-flight transactions to the resource being monitored.
 *
 * This method can be called from any thread.
 */
public void increment() {
  int counterVal = counter.getAndIncrement();
  if (0 == counterVal) {
    becameBusyAt = SystemClock.uptimeMillis();
  }
  if (debugCounting) {
    Log.i(TAG, "Resource: " + resourceName + " in-use-count incremented to: " + (counterVal + 1));
  }
}
/**
 *   Decrements the count of in-flight transactions to the resource being monitored.
 *
 * If this operation results in the counter falling below 0 - an exception is raised.
 *
 * @throws IllegalStateException if the counter is below 0.
 */
public void decrement() {
  int counterVal = counter.decrementAndGet();
  if (counterVal == 0) {
    // we've gone from non-zero to zero. That means we're idle now! Tell espresso.
    if (null != resourceCallback) {
      resourceCallback.onTransitionToIdle();
    }
    becameIdleAt = SystemClock.uptimeMillis();
  }
  if (debugCounting) {
    if (counterVal == 0) {
      Log.i(TAG, "Resource: " + resourceName + " went idle! (Time spent not idle: " +
        (becameIdleAt - becameBusyAt) + ")");
    } else {
      Log.i(TAG, "Resource: " + resourceName + " in-use-count decremented to: " + counterVal);
    }
  }
  checkState(counterVal > -1, "Counter has been corrupted!");
}
/**
 * Prints the current state of this resource to the logcat at info level.
 */
public void dumpStateToLogs() {
  StringBuilder message = new StringBuilder("Resource: ")
      .append(resourceName)
      .append(" inflight transaction count: ")
      .append(counter.get());
  if (0 == becameBusyAt) {
    Log.i(TAG, message.append(" and has never been busy!").toString());
  } else {
    message.append(" and was last busy at: ")
        .append(becameBusyAt);
    if (0 == becameIdleAt) {
      Log.w(TAG, message.append(" AND NEVER WENT IDLE!").toString());
    } else {
      message.append(" and last went idle at: ")
          .append(becameIdleAt);
      Log.i(TAG, message.toString());
      }
    }
  }
}

在耗時(shí)線(xiàn)程中調(diào)用

//耗時(shí)操作開(kāi)始調(diào)用
helloWorldServerIdlingResource.increment();
{  
      //做一些耗時(shí)操作
}
//結(jié)束后調(diào)用
helloWorldServerIdlingResource.decrement();

</br>


UI Automator

UI Automator 測(cè)試框架提供了一組 API 來(lái)構(gòu)建 UI 測(cè)試心例,用于在用戶(hù)應(yīng)用和系統(tǒng)應(yīng)用中執(zhí)行交互宵凌。利用 UI Automator API,您可以執(zhí)行在測(cè)試設(shè)備中打開(kāi)“設(shè)置”菜單或應(yīng)用啟動(dòng)器等操作契邀。UI Automator 測(cè)試框架非常適合編寫(xiě)黑盒自動(dòng)化測(cè)試摆寄,其中的測(cè)試代碼不依賴(lài)于目標(biāo)應(yīng)用的內(nèi)部實(shí)現(xiàn)詳情。

UI Automator 測(cè)試框架的主要功能包括:

  • 用于檢查布局層次結(jié)構(gòu)的查看器坯门。
  • 在目標(biāo)設(shè)備上檢索狀態(tài)信息并執(zhí)行操作的 API微饥。
  • 支持跨應(yīng)用 UI 測(cè)試的 API。

要求 Android 4.3(API 級(jí)別 18)或更高版本古戴。

UI Automator 查看器

uiautomatorviewer 工具提供了一個(gè)方便的 GUI欠橘,可以?huà)呙韬头治?Android 設(shè)備上當(dāng)前顯示的 UI 組件。您可以使用此工具檢查布局層次結(jié)構(gòu)现恼,并查看在設(shè)備前臺(tái)顯示的 UI 組件屬性肃续。利用此信息,您可以使用 UI Automator(例如叉袍,通過(guò)創(chuàng)建與特定可見(jiàn)屬性匹配的 UI 選擇器)創(chuàng)建控制更加精確的測(cè)試始锚。

uiautomatorviewer 工具位于 <android-sdk>/tools/ 目錄中。

UI Automator API

  • UiDevice
    :用于在目標(biāo)應(yīng)用運(yùn)行的設(shè)備上訪(fǎng)問(wèn)和執(zhí)行操作喳逛。您可以調(diào)用其方法來(lái)訪(fǎng)問(wèn)設(shè)備屬性瞧捌,如當(dāng)前屏幕方向或顯示尺寸,按“返回”润文、“主屏幕”或“菜單”按鈕等姐呐。
  • UiCollection
    :枚舉容器的 UI 元素以便計(jì)算子元素個(gè)數(shù),或者通過(guò)可見(jiàn)的文本或內(nèi)容描述屬性來(lái)指代子元素典蝌。
  • UiObject
    :表示設(shè)備上可見(jiàn)的 UI 元素曙砂。
  • UiScrollable
    :為在可滾動(dòng) UI 容器中搜索項(xiàng)目提供支持。
  • UiSelector
    :表示在設(shè)備上查詢(xún)一個(gè)或多個(gè)目標(biāo) UI 元素骏掀。
  • Configurator
    :允許您設(shè)置運(yùn)行 UI Automator 測(cè)試所需的關(guān)鍵參數(shù)鸠澈。
// 初始化 UiDevice
mDevice = UiDevice.getInstance(getInstrumentation());

// 按下home鍵
mDevice.pressHome();

//在當(dāng)前主界面,查找一個(gè)叫test的元素
UiObject allAppsButton = mDevice.findObject(new UiSelector().description("test"));

// 找到后點(diǎn)擊它
allAppsButton.click();

更多詳細(xì)的使用會(huì)在后面實(shí)際使用中講到截驮。

壓力測(cè)試 Monkey

Monkey是Android中的一個(gè)命令行工具笑陈,可以運(yùn)行在模擬器里或?qū)嶋H設(shè)備中。它向系統(tǒng)發(fā)送偽隨機(jī)的用戶(hù)事件流(如按鍵輸入侧纯、觸摸屏輸入新锈、手勢(shì)輸入等)甲脏,實(shí)現(xiàn)對(duì)正在開(kāi)發(fā)的應(yīng)用程序進(jìn)行壓力測(cè)試眶熬。Monkey測(cè)試是一種為了測(cè)試軟件的穩(wěn)定性妹笆、健壯性的快速有效的方法。

Monkey的特征

  • 測(cè)試的對(duì)象僅為應(yīng)用程序包娜氏,有一定的局限性拳缠。
  • Monky測(cè)試使用的事件流數(shù)據(jù)流是隨機(jī)的,不能進(jìn)行自定義贸弥。
  • 可對(duì)MonkeyTest的對(duì)象窟坐,事件數(shù)量,類(lèi)型绵疲,頻率等進(jìn)行設(shè)置哲鸳。

Monkey使用

adb shell monkey [options] <event-count>

options這個(gè)是配置monkey的設(shè)置,例如指定啟動(dòng)那個(gè)包盔憨,不指定將會(huì)隨機(jī)啟動(dòng)所有程序徙菠。event-count這個(gè)是讓monkey發(fā)送多少次事件。

adb shell monkey -p com.android.test -v 5000

這就是一個(gè)簡(jiǎn)單的測(cè)試郁岩,向com.android.test包對(duì)應(yīng)的程序發(fā)送5000次隨機(jī)的事件婿奔,-p指定了測(cè)試的包名,-v指定了發(fā)送的隨機(jī)事件次數(shù)问慎。

monkey命令行可用參數(shù)

Monkey停止條件

  • 如果限定了Monkey運(yùn)行在一個(gè)或幾個(gè)特定的包上萍摊,那么它會(huì)監(jiān)測(cè)試圖轉(zhuǎn)到其它包的操作,并對(duì)其進(jìn)行阻止如叼。
  • 如果應(yīng)用程序崩潰或接收到任何失控異常冰木,Monkey將停止并報(bào)錯(cuò)。
  • 如果應(yīng)用程序產(chǎn)生了應(yīng)用程序不響應(yīng)(ANR)的錯(cuò)誤薇正,Monkey將會(huì)停止并報(bào)錯(cuò)片酝。

以上就是我學(xué)習(xí)了解的一些測(cè)試,當(dāng)然Android的自動(dòng)化測(cè)試還有很多其他的框架挖腰,個(gè)人能力有限雕沿,這里我就只記錄了我自己了解的一些方式。詳細(xì)的使用方式猴仑,可以查看下面的文章:
Android自動(dòng)化測(cè)試--Local Unit Tests使用
Android自動(dòng)化測(cè)試--Instrumented Unit Tests使用
Android自動(dòng)化測(cè)試--Espresso使用
Android自動(dòng)化測(cè)試--UI Automator使用
Android自動(dòng)化測(cè)試--Monkey使用

如果你覺(jué)得有用审轮,請(qǐng)?jiān)贕ithub不吝給我一個(gè)Star,非常感謝辽俗。


寫(xiě)在最后的話(huà):個(gè)人能力有限疾渣,歡迎大家在下面吐槽。喜歡的話(huà)就為我點(diǎn)一個(gè)贊吧崖飘。也歡迎 Fork Me On Github 榴捡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市朱浴,隨后出現(xiàn)的幾起案子吊圾,更是在濱河造成了極大的恐慌达椰,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件项乒,死亡現(xiàn)場(chǎng)離奇詭異啰劲,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)檀何,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)蝇裤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人频鉴,你說(shuō)我怎么就攤上這事栓辜。” “怎么了垛孔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵啃憎,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我似炎,道長(zhǎng)辛萍,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任羡藐,我火速辦了婚禮贩毕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仆嗦。我一直安慰自己辉阶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布瘩扼。 她就那樣靜靜地躺著谆甜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪集绰。 梳的紋絲不亂的頭發(fā)上规辱,一...
    開(kāi)封第一講書(shū)人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音栽燕,去河邊找鬼罕袋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛碍岔,可吹牛的內(nèi)容都是我干的浴讯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蔼啦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼榆纽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奈籽,失蹤者是張志新(化名)和其女友劉穎亮元,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體唠摹,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年奉瘤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勾拉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盗温,死狀恐怖藕赞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卖局,我是刑警寧澤斧蜕,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站砚偶,受9級(jí)特大地震影響批销,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜染坯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一均芽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧单鹿,春花似錦掀宋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至儒喊,卻和暖如春镣奋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怀愧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工唆途, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掸驱。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓肛搬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親毕贼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子温赔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,302評(píng)論 25 707
  • Instrumentation介紹 Instrumentation是個(gè)什么東西? Instrumentation測(cè)...
    打不死的小強(qiáng)qz閱讀 7,793評(píng)論 2 39
  • 標(biāo)簽(空格分隔): Android 單元測(cè)試的好處:Martin Fowler在《重構(gòu)》里面還解釋了為什么單元測(cè)試...
    背影殺手不太冷閱讀 5,827評(píng)論 3 25
  • 星期二鬼癣。雨陶贼。 郵政包裹后續(xù)-------又找了一次商家客服啤贩,還是沒(méi)等到答案,晚上氣勢(shì)洶洶地找了維權(quán)中心拜秧,最后還是再...
    紅蜻蜓閱讀 184評(píng)論 0 0
  • 條條道路通羅馬痹屹,財(cái)務(wù)自由之路也有千萬(wàn)條,只要達(dá)到“老有所養(yǎng)枉氮、病有所醫(yī)志衍、親有所護(hù)”的基礎(chǔ)條件,加上每月收入大于每月支...
    富蘭克劉閱讀 1,393評(píng)論 3 29