Android测试

标签(空格分隔): Android


单元测试的好处:
Martin Fowler在《重构》里面还解释了为什么单元测试可以节省时间,大意是我们写程序的时候,其实大部分时间不是花在写代码上面,而是花在debug上面,是花在找出问题到底出在哪上面,而单元测试可以最快的发现你的新代码哪里不work,这样你就可以很快的定位到问题所在,然后给以及时的解决,这也可以在很大程度上防止regression


历史测试基础##

Android 测试框架(Android Testing Framework)为 Android 开发环境的一个组成部分,可以用来测试 Android 的各个方面,从单元测试到框架测试到 UI 测试等。

  • Android 测试框架基于 JUnit,因此可以直接使用 JUnit 来测试一些与 Android 平台不是很相关的类,或者使用Android 的JUint 扩展来测试 Android 组件,如果你刚开始接触 Android测试,可以先从AndroidTestCase 写一些通用的测试用例,然后再写较复杂的测试用例。
  • Android JUint 扩展提供了对 Android 特定组件(如
    Activity,Service)的测试支持,这些扩展类提供了一些辅助方法来帮助创建测试使用的“桩”类或方法。
  • SDK 也提供了一个 moneyrunner (一个 python 应用)可以模拟用户按键事件来测试 UI。


    此处输入图片的描述
    此处输入图片的描述

为什么android unit testing不好做##

我们知道安卓的app需要运行在delvik上面,我们开发Android app是在JVM上面,在开发之前我们需要下载各个API-level的SDK的,下载的每个SDK都有一个android.jar的包,这些可以在你的android_sdk_home/platforms/下面看到。当我们开发一个项目的时候,我们需要指定一个API-level,其实就是将对应的android.jar 加到这个项目的build path里面去。这样我们的项目就可以编译打包了。然而现在的问题是,我们的代码必须运行在emulator或者是device上面,说白了,就是我们的IDE和SDK只提供了开发和编译一个项目的环境,并没有提供运行这个项目的环境,原因是因为android.jar里面的class实现是不完整的,它们只是一些stub,如果你打开android.jar下面的代码去看看,你会发现所有的方法都只有一行实现:
throw RuntimeException("stub!!”);
  而运行unit test,说白了还是个运行的过程,所以如果你的unit test代码里面有android相关的代码的话,那运行的时候将会抛出RuntimeException("stub!!”)。为了解决这个问题,现在业界提出了很多不同的程序架构,比如MVP、MVVM等等,这些架构的优势之一,就是将其中一层抽出来,变成pure Java实现,这样做unit testing就不会遇到上面这个问题了,因为其中没有android相关的代码。
好奇的童鞋可能会问了,既然android.jar的实现是不完整的,那为什么我们可以编译这个项目呢?那是因为编译代码的过程并没有真正的运行这些代码,它只会检查你的接口有没有定义,以及其他的一些语法是不是正确。举个简单的例子:

public class Test {
    public static void main(String[] argv) {
        testMethod();
    }
    public static void testMethod() {
        throw RuntimeException("stub!!”);
    }
}

上面的代码你同样可以编译通过,但你运行的时候,就会抛出异常RuntimeException("stub!!”)。当我们的项目运行在emulator或者是device上面的时候,android.jar被替换成了emulator或者是device上面的系统的实现,那上面的实现是真正实现了那些方法的,所以运行起来没有问题。
话说回来,MVP、MVVM这些架构模式虽然解决了部分问题,可以测试项目中不含android相关的类的代码,然而一个项目中还是有很大部分是android相关的代码的,所以上面那种解决方案,其实是放弃了其中一大块代码的unit test。
  当然,话说回来,android还是提供了他自己的testing framework,叫instrumentation,但是这套框架还是绕不开刚刚提到的问题,他们必须跑在emulator或者是device上面。这是个很慢的过程,因为要打包、dexing、上传到机器、运行起来界面。。。这个相信大家都有体会,尤其是项目大了以后,运行一次甚至需要一两分钟,项目小的话至少也要十几秒或几十秒。以这个速度是没有办法做unit test的。
那么怎么样即可以给android相关的代码做测试,又可以很快的运行这些测试呢?


Android 的测试框架相关的 API 主要定义在三个包中:

  • android.test 用于编写 Android 测试用例
  • android.test.mock 定义了方便测试用的测试“桩”类
  • android.test.suitebuilder 运行测试用例的 Test Runner 类 Android 测试 API 是基于
    JUnit 扩展而来,并添加了与 Android 平台相关的测试 API。

JUnit###

你可以直接使用 JUnit 中相关 API 编写一些和平台无关的测试用例(基于 TestCase), Android 测试 API 中提供了一个 TestCase 的子类 AndroidTestCase ,可以用来编写一些 Android 相关的对象的测试用例,AndroidTestCase 支持一些和平台相关的 setup,teardown 以及 setup 方法。

你也可以直接使用 JUnit 的 Assert 方法 显示测试结果,这些 Assert 方法可以通过比较预期的值和实际的值,如果不同可以排除异常。Android 测试 API 扩展了一些 Assert 方法用于支持和 Android 平台相关的比较。

要注意的是,Android 测试 API 支持 JUnit 3 代码风格,而不支持 JUnit 4 代码风格,也只能使用 InstrumentationTestRunner 来运行测试用例。

Instrumentation###

在通常情况下(普通的 Android 应用),Android 的 activity,Service 等的生命周期是由 Android 操作系统来控制的。 比如一个 Activity 的生命周期开始于 onCreate (由某个 Intent 激活),然后是 onResume. 可以参见 Android 简明开发教程五:Activities。 应用程序本身无法直接控制这些生命周期状态的切换。但使用 Instrumatation API 时你可以直接调用这些方法。
可以独立控制 Android 组件(Activity,Service 等)的生命周期,并可以控制 Android 如何调用一个应用。也可以支持强制某个应用和另一个已经在运作的应用运行在同一个进程中

Test case 相关类###

Android 提供了多个由 Testcase 或 Assert 派生而来的子类以支持 Android 平台相关的 setup,teardown 和其它辅助方法。

  • AndroidTestCase 为一 Android 平台下通用的测试类,它支持所有 JUnit 的 Assert 方法和标准的
    setUp 和 tearDown 方法,并可以用来测试 Android permission 。
setUp()测试初始化,Runner在运行任何其它测试方法之前自动执行setUp()方法,可以在这里建立测试数据集
tearDown() 會在測試完成後執行,多半用於測試資料的移除與資源回收等工作。
  • 组件相关的测试类如测试 activity, Content provider ,Service 相关的测试类,Android
    没有提供单独的用来测试 BroadcastReceiver 的测试类,而是可以通过发送 Intent 对象来检测 Broadcast
    Receiver 的反应结果来测试 BroadcastReceiver。
  • ApplicationTestCase 可以用来测试 Application 对象。
  • InstrumentationTestCase 如果你要使用 Instrumentation API,那么你必须使用 InstrumentationTestCase 或其子类。

建议再开始下面的内容之前先看看这篇文章

1、在AndroidStudio中使用原生的androidUnit测试的具体操作:##

首先因为上面的提到的基础有点难懂,所以建议看一下中文官方文档,还有这篇博客

此处输入图片的描述
此处输入图片的描述

Android提供了上面的多个测试类,可以允许我们对于单个方法、Activity、Service、Application等多个对象进行测试,

参考博客
1、可以自己新建一个测试目录,把测试目录放到你想要的地方,也可以把单元测试类创建在与Android Studio默认的ApplicationTest类相同的路径下面;以前的AndroidStudio版本或者是ADT的话要自己新建,具体新建操作请见博客一博客二博客三;但是最近的AndroidStudio版本已经自动为我们生成用于测试安卓的AndroidTest目录与java的JUnnit目录,所以可以省去自己动手新建目录

2、所有的测试方法必须以”test”开头,这样Android Studio才能自动的找到所有你想要进行单元测试的方法。

3、配置gradle:其实这一步配置可以省略,因为Android Studio本身就支持Android单元测试
进入app对应的gradle里,在android里的defaultConfig加上
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner";
在dependencies 里加上
testCompile 'junit:junit:4.12'//用于java的单元测试
//以下的两个用于安卓的测试
androidTestCompile 'com.android.support.test:runner:0.2'
androidTestCompile 'com.android.support.test:rules:0.2'

4、UI测试
当测试拥有UI的Activity时,被测试的Activity在UI线程中运行。然而,测试程序会在程序自己的进程中,单独的一个线程内运行。这意味着,我们的测试程序可以获得UI线程的对象,但是如果它尝试改变UI线程对象的值,会得到WrongThreadException错误。我们可以让测试类继承于ActivityInstrumentationTestCase2,这样的话,可以与Android系统通信,发送键盘输入及点击事件到UI中。就可以测试带有UI交互的activity了,安全地将Intent注入到Activity。因为要与android系统通信,所以要有运行环境,在虚拟机或者真机!!

5、单元测试
单元测试一般不适合测试与系统有复杂交互的UI。我们应该使用如同测试UI组件所描述的ActivityInstrumentationTestCase2来对这类UI交互进行测试。使用单元测试时,我们的测试类应该继承自ActiviUnitTestCase,ActiviUnitTestCase类提供对于单个Activity进行分离测试的支持,继承ActiviUnitTestCase的Activity不会被Android自动启动。要单独启动Activity,我们需要显式的调用startActivity()方法,并传递一个Intent来启动我们的目标Activity。不需要用到android运行环境

6、功能测试
功能测试包括验证单个应用中的各个组件是否与使用者期望的那样(与其它组件)协同工作。比如,我们可以创建一个功能测试验证在用户执行UI交互时Activity是否正确启动目标Activity。
要为Activity创建功能测,我们的测试类应该对ActivityInstrumentationTestCase2进行扩展。

7、Applicatipn测试
可以使用ApplicationTestCase,具体请看博客

8、测试Service
由于Service是在后台运行的,所以测试Service不能用instrumentation框架,继承ServiceTestCase的测试类可以对service进行针对性测试。
  ServiceTestcase不会初始化测试环境直到你调用ServiceTestCase.startService()或者ServiceTestCase.bindService. 这样的话,你就可以在Service启动之前可以设置测试环境,创建你需要模拟的对象等等。比如你可以配置service的context和application对象。
  setApplication()方法和setContext(Context)方法允许你在Service启动之前设置模拟的Context 和模拟的Application.关于这些模拟的对象。具体请看博客


2、使用谷歌的测试框架:Testing Support Library包括ndroidJUnitRunner(JUnit 4)、Espresso、UI Automator###

配置如下:

必须的
android {
    defaultConfig {
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}
不是必须的,根据需求选择三者中的一种或者全选
dependencies {
  androidTestCompile 'com.android.support.test:runner:0.4'
  // Set this dependency to use JUnit 4 rules
  androidTestCompile 'com.android.support.test:rules:0.4'
  // Set this dependency to build and run Espresso tests
  androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
  // Set this dependency to build and run UI Automator tests
  androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
}

为什么Android应用需要测试##见博客

Google+ 团队的 Android UI 测试##

Android 测试主要分为3个类型:

  • 单元测试(Unit Test)
    区分UI代码和功能代码在Android开发中尤其困难。因为有时Activity既有Controller的功能,又有View的功能。Robolectric是一个很优秀的Android测试框架,它提供了一个Android框架的stub,这样测试运行时实际上是在JVM上运行,而不是在Android平台
(比如Robotium和Instrumentation都是在Android平台运行测试)

,从而提高了速度。

  • 封闭UI测试 (Hermetic UI Test)
    这个测试方法使得测试不需要外部依赖和网络请求。这样做的主要目的是提高测试速度,减少测试时的外部影响,毕竟网络调用是相对很慢的。Espresso可以用来模拟用户的UI操作。
  • Monkey Test
    Monkey Test 就好像一只猴子在测试app一样,没有任何规律的在你的app上胡按。计算机运行monkey test的时候,每秒钟能做出几千个UI动作(可以配置这个频率),比如点击和拖拽。所以这个测试可以算是一个压力测试,用来检测ANR。

Android UI 自动化测试框架——UI Automator##

参考文章
谷歌推荐的这是一种较为落后的测试UI的方法,因为真的要在真系或者虚拟机上跑起来,这个过程会看到UI的变化过程。而且这个东西并不是真的是自动化,而是你先写好了界面的交互代码,然后执行的时候用代码去代替用户的交互手势而已。

Espresso——Google引入了新的UI测试框架##

Espresso,使得测试Android应用的UI变得更加容易
  Espresso测试有个很强大的地方是它在多个测试操作中是线程安全的。Espresso会等待当前进程的消息队列中的UI事件,并且在任何一个测试操作中会等待其中的AsyncTask结束才会执行下一个测试。这能够解决程序中大部分的线程同步问题。参考博客
  
  onView用于定位视图,perform用于产生事件,check用于检测checkpoint。
  
具体例子请看:博客
Espresso可以直接跨Activity检查,不需要知道跳转关系。不用像以前的原生的androidUnit测试一样,要控制好activity跳转关系
更多详细的用法请见谷歌的官方英语文档更多详细的例子请见谷歌官方英语文档

Roboletric——直接在JVM运行的强大的安卓测试框架##

Robolectric在其所提供的测试框架中,完全模拟了Android SDK的jar文件(不会再有恼人的stub异常),它使得我们的测试可以运行于JVM之上(速度得到大幅度的提升),因此我们可以用它对Android应用进行测试驱动开发。Roblectric同时实现了Android中对XML的解析,模拟了View,Layout,以及资源的加载,它使得Android的环境对于开发人员来说更像是一个黑盒,从而使开发人员不用大量使用mock,就可以方便的对资源状态和Android相关的代码进行测试。

Robolectric使用了javassist在运行时动态修改Android.jar中类的byte code,Robolectric会在JVM加载Android.jar包的时候,重写其中类的方法。Roblectroic会让这些方法有返回值(null或是0) 而不是抛出异常 ,或者将这些方法调用转向Shadow Objects来模拟Android SDK的实现。Shadow Objects是Robolectric在运行时插入到Android.jar包相应的类中的,它们会实际处理方法的调用,并记录相应的状态,以备在assert的时候进行查询。如图所示。Robolectric提供了大量的Shadow Objects,覆盖了测试开发过程中绝大多数逻辑功能的需要 。

robolectric,他们的做法是通过实现一套JVM能运行的Android代码,然后在unit test运行的时候去截取android相关的代码调用,然后转到他们的他们实现的代码去执行这个调用的过程。举个例子说明一下,比如android里面有个类叫TextView,他们实现了一个类叫ShadowTextView。这个类基本上实现了TextView的所有公共接口,假设你在unit test里面写到
String text = textView.getText().toString();。在这个unit test运行的时候,Robolectric会自动判断你调用了Android相关的代码textView.getText(),然后这个调用过程在底层截取了,转到ShadowTextView的getText实现。而ShadowTextView是真正实现了getText这个方法的,所以这个过程便可以正常执行。
除了实现Android里面的类的现有接口,Robolectric还做了另外一件事情,极大地方便了unit testing的工作。那就是他们给每个Shadow类额外增加了很多接口,可以读取对应的Android类的一些状态。比如我们知道ImageView有一个方法叫setImageResource(resourceId),然而并没有一个对应的getter方法叫getImageResourceId(),这样你是没有办法测试这个ImageView是不是显示了你想要的image。而在Robolectric实现的对应的ShadowImageView里面,则提供了getImageResourceId()这个接口。你可以用来测试它是不是正确的显示了你想要的Image.

参考博客一博客二
更详细的博客见GitBook


monkey测试##

测试操作:###

此处输入图片的描述
此处输入图片的描述

  
  下面的操作一般是在shell端启动即在真机或者模拟器启动,所以下面的所有操作的前提是先输入adb shell 进入终端机。在测试的过程中无论怎么按屏幕或者按键也无法停止monkey测试,这能等测试完成!!
1、在输出日志信息的时候,如果只是 输入命令:monkey 300的话,日志信息之会出现在命令行,不会保存到文件,如果想保存到文件的话,可以参考上图做法。注意monkey后面可以加一些option参数

参数:-v:输出日内容志的多少,如果是-v-v-v的话,输出的内容会更加的详细

参数:-p 指定的包名:用于选择那个应用测试。可以同时指定多个应用:-p 指定的包名 -p 指定的包名 -p 指定的包名。所以要先找到对应的包名,可以使用pm list packages列出所有的包名。
这里有一点要注意的是:-p只能测试在桌面上有图标的应用,即在应用的对应的Main activity的配置文件加了

<category android:name="android.intent.category.LAUNCHER" />

这句话的应用,所以当要测试一些没有在桌面有图标的应用时,只能用下面的-c

参数:-c 指定的包名 用于测试在桌面没有图标的应用。

其实monkey有白名单与黑名单,白名单的作用相当于参数-p,控制哪些应用用于测试,黑名单则相反。但那黑名单与白名单不能同时设置
黑名单:--pkg-blacklist-file PACKAGE_BLACKLIST_FILE
白名单:--pkg-whitelist-file PACKAGE_WHILELIST_FILE
具体使用时,可以先用pm list packages列出所有的包名,然后再将要用的白名单或者黑名单的报名复制到一个TXT的文本,然后将这个文本文件一般保存到/data/local/tmp/目录下,然后就可以大具体的命令:monkey --pkg-blacklist-file 
/data/local/tmp/文本文件名(以黑名单为例)

参数:-s 随机种子数 用随机种子数来标记这次的测试,以便下次可以重现这次的测试序列,复原测试场景。

参数:--throttle 延时毫秒数 可以让测试时的各个事件之间有延时,以模仿人的操作延时,人的操作延时一般为200毫秒至300毫秒之间

参数:--randomize-throttle 延时毫秒数 这样的范围就变成0至设置的延时毫秒数

更多的参数请上网搜索,除了控制测试的参数之外,还可以控制测试时的事件。如:touch事件、activity切换是--pptappswitch 百分比加上这个事件参数之后可以控制测试的过程中只打开activity,但是却不执行touch或者点击事件 、点击事件。

调试操作:###

参数:--ignore-crashes 忽略程序崩溃或任何失控异常,使monkey还会继续执行下去

参数:--ignore-timeouts 忽略ANR,例如在等待输入等阻塞事件时monkey会跳过这些阻塞事件,继续执行

参数:-ignore-security-execptions 忽略权限许可错误

参数:--ignore-native-crashes 忽略闪退现象

monkey结果分类:###

此处输入图片的描述
此处输入图片的描述

一般的测试结果显示如下:首先是


此处输入图片的描述
此处输入图片的描述

然后是各个事件


此处输入图片的描述
此处输入图片的描述

这里插入一句各个事件具体的含义解释如下
此处输入图片的描述
此处输入图片的描述

此处输入图片的描述
此处输入图片的描述

此处输入图片的描述
此处输入图片的描述

此处输入图片的描述
此处输入图片的描述

然后是


此处输入图片的描述
此处输入图片的描述

分析异常结果###

ANR有两个原因,一个是界面5秒钟没有响应或者发送一个广播在10秒钟之内没有被处理


此处输入图片的描述
此处输入图片的描述

ANR有关如下图


此处输入图片的描述
此处输入图片的描述

其中比较重要的是 anr traces他是保存在/data/anr/traces.txt中,里面有ANR和Crash的原因;我们要用cd命令进入到里面才能查看

常见异常


此处输入图片的描述
此处输入图片的描述

测试策略###

先自己预先列一份应用最可能吹西安问题的对症下药测试

Monkey脚本编写与检查内存泄露###

为什么要自己写monkey脚本测试呢?
  因为自带的monkey是随机的测试,没有规律性,虽然可以控制各个事件的比例,但是可能达不到我们需要的逻辑顺序与要求。所以我,们可以自己写一个脚本来固定测试逻辑,比如说打开浏览器然后输入网址,然后推出浏览器。但那时个人觉得这样做的话还不如用以前的方法如android原生的单元测试,谷歌的Espresso或者第三方框架Robolectric来测试。
  
重要步骤如下:
1、首先除了要测试获取的包名还要获取类名,我们可以先用之前的pm list packages列出所有的包名,然后挑选要测试的包名;还可以要用另外的方法
第一种:用adb shell 进入android终端之后使用dumpsys activity | find "mfocusedActivity"就可以找到当前显示在屏幕的前台应用对应的activity,一般是第一个activity所以当前显示程序的包名,而后面的activity就是该应用的入口activity类名。
第二种:用adb shell 进入android终端之后使用dumpsys package 程序对应的包名(这个包名要自己找出来,可以参照上面的做法),然后在显示的信息中找到对应的报名后,再找出有ndroid.intent.category.LAUNCHER的activity,那就是我们要找的activity类名

2、查询monkey的API,编写脚本(具体的API可以自己上网查询)
下面的例子是
编写头文件
打开浏览器
清空网址
输入极客学院的网址
确认,载入网址
退出浏览器


此处输入图片的描述
此处输入图片的描述

然后保存到TXT
文件后再命令行monkey -f 脚本路径+脚本文件名 运行
路径目录一般用/data/local/tmp/下,(有点像黑白名单)

monkey服务器###

此处输入图片的描述
此处输入图片的描述

可以同与终端手机或者模拟器通过telnet
连接的电脑控制终端测试,具体看极客学院的视频

monkey检查内存泄漏###

可以用monkey测试后,然后用MAT检查内存泄漏

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容