第三十五章:SpringBoot與單元測(cè)試的小秘密

單元測(cè)試對(duì)于開發(fā)人員來(lái)說(shuō)是非常熟悉的,我們每天的工作也都是圍繞著開發(fā)與測(cè)試進(jìn)行的,在最早的時(shí)候測(cè)試都是采用工具Debug模式進(jìn)行調(diào)試程序,后來(lái)Junit的誕生也讓程序測(cè)試發(fā)生了很大的變化。我們今天來(lái)講解下基于SpringBoot結(jié)合Junit怎么來(lái)完成單元測(cè)試

免費(fèi)教程專題

恒宇少年在博客整理三套免費(fèi)學(xué)習(xí)教程專題,由于文章偏多特意添加了閱讀指南,新文章以及之前的文章都會(huì)在專題內(nèi)陸續(xù)填充,希望可以幫助大家解惑更多知識(shí)點(diǎn)。

本章目的

基于SpringBoot平臺(tái)整合Junit分別完成客戶端服務(wù)端單元測(cè)試

SpringBoot 企業(yè)級(jí)核心技術(shù)學(xué)習(xí)專題


專題 專題名稱 專題描述
001 Spring Boot 核心技術(shù) 講解SpringBoot一些企業(yè)級(jí)層面的核心組件
002 Spring Boot 核心技術(shù)章節(jié)源碼 Spring Boot 核心技術(shù)簡(jiǎn)書每一篇文章碼云對(duì)應(yīng)源碼
003 Spring Cloud 核心技術(shù) 對(duì)Spring Cloud核心技術(shù)全面講解
004 Spring Cloud 核心技術(shù)章節(jié)源碼 Spring Cloud 核心技術(shù)簡(jiǎn)書每一篇文章對(duì)應(yīng)源碼
005 QueryDSL 核心技術(shù) 全面講解QueryDSL核心技術(shù)以及基于SpringBoot整合SpringDataJPA
006 SpringDataJPA 核心技術(shù) 全面講解SpringDataJPA核心技術(shù)
007 SpringBoot核心技術(shù)學(xué)習(xí)目錄 SpringBoot系統(tǒng)的學(xué)習(xí)目錄,敬請(qǐng)關(guān)注點(diǎn)贊!!!

構(gòu)建項(xiàng)目

我們首先使用idea工具創(chuàng)建一個(gè)SpringBoot項(xiàng)目,并且添加相關(guān)Web、MySQL、JPA依賴,具體pom.xml配置依賴內(nèi)容如下所示:

.../省略其他配置
<dependencies>
        <!--web依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--data jpa依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--druid數(shù)據(jù)源依賴-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.31</version>
        </dependency>
        <!--lombok依賴-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--MySQL依賴-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--springboot程序測(cè)試依賴,創(chuàng)建項(xiàng)目默認(rèn)添加-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>
.../省略其他配置

配置數(shù)據(jù)庫(kù)

我們本章的內(nèi)容需要訪問(wèn)數(shù)據(jù)庫(kù),我們先在src/main/resources下添加application.yml配置文件,對(duì)應(yīng)添加數(shù)據(jù)庫(kù)配置信息如下所示:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8
    username: root
    password: 123456
    #最大活躍數(shù)
    maxActive: 20
    #初始化數(shù)量
    initialSize: 1
    #最大連接等待超時(shí)時(shí)間
    maxWait: 60000
    #打開PSCache,并且指定每個(gè)連接PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    #通過(guò)connectionProperties屬性來(lái)打開mergeSql功能;慢SQL記錄
    #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    minIdle: 1
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: select 1 from dual
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    #配置監(jiān)控統(tǒng)計(jì)攔截的filters,去掉后監(jiān)控界面sql將無(wú)法統(tǒng)計(jì),'wall'用于防火墻
    filters: stat, wall, log4j
  jpa:
    properties:
      hibernate:
        show_sql: true
        format_sql: true

以上配置都是比較常用到,這里不做多解釋了,如果不明白可以去本文底部SpringBoot學(xué)習(xí)目錄文章內(nèi)找尋對(duì)應(yīng)的章節(jié)。

構(gòu)建實(shí)體

對(duì)應(yīng)數(shù)據(jù)庫(kù)內(nèi)的數(shù)據(jù)表來(lái)創(chuàng)建一個(gè)商品基本信息實(shí)體,實(shí)體內(nèi)容如下所示:

package com.yuqiyu.chapter35.bean;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * 商品基本信息實(shí)體
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/13
 * Time:22:20
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
@Entity
@Table(name = "good_infos")
public class GoodInfoEntity implements Serializable
{
    //商品編號(hào)
    @Id
    @Column(name = "tg_id")
    @GeneratedValue
    private Integer tgId;

    //商品類型編號(hào)
    @Column(name = "tg_type_id")
    private Integer typeId;

    //商品標(biāo)題
    @Column(name = "tg_title")
    private String title;

    //商品價(jià)格
    @Column(name = "tg_price")
    private double price;

    //商品排序
    @Column(name = "tg_order")
    private int order;
}

構(gòu)建JPA

基于商品基本信息實(shí)體類創(chuàng)建一個(gè)JPA接口,該接口繼承JpaRepository接口完成框架通過(guò)反向代理模式進(jìn)行生成實(shí)現(xiàn)類,自定義JPA接口內(nèi)容如下所示:

package com.yuqiyu.chapter35.jpa;

import com.yuqiyu.chapter35.bean.GoodInfoEntity;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * 商品jpa
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/13
 * Time:22:23
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
public interface GoodInfoJPA
    extends JpaRepository<GoodInfoEntity,Integer>
{
}

構(gòu)建測(cè)試控制器

下面我們開始為單元測(cè)試來(lái)做準(zhǔn)備工作,先來(lái)創(chuàng)建一個(gè)SpringMVC控制器來(lái)處理請(qǐng)求,代碼如下所示:

package com.yuqiyu.chapter35.controller;

import com.yuqiyu.chapter35.bean.GoodInfoEntity;
import com.yuqiyu.chapter35.jpa.GoodInfoJPA;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * ===============================
 * Created with Eclipse.
 * User:于起宇
 * Date:2017/9/13
 * Time:18:37
 * 簡(jiǎn)書:http://www.lxweimin.com/u/092df3f77bca
 * ================================
 */
@RestController
public class TestController
{
    //商品基本信息數(shù)據(jù)接口
    @Autowired
    private GoodInfoJPA goodInfoJPA;

    /**
     * 查詢首頁(yè)內(nèi)容
     * @return
     */
    @RequestMapping(value = "/index")
    public String index(String name)
    {
        return "this is index page" + name;
    }

    /**
     * 查詢?nèi)可唐?     * @return
     */
    @RequestMapping(value = "/all")
    public List<GoodInfoEntity> selectAll()
    {
        return goodInfoJPA.findAll();
    }

    /**
     * 查詢商品詳情
     * @param goodId
     * @return
     */
    @RequestMapping(value = "/detail",method = RequestMethod.GET)
    public GoodInfoEntity selectOne(Integer goodId)
    {
        return goodInfoJPA.findOne(goodId);
    }
}

我們?cè)跍y(cè)試控制內(nèi)注入了GoodInfoJPA,獲得了操作商品基本信息的數(shù)據(jù)接口代理實(shí)例,我們可以通過(guò)該代理實(shí)例去做一些數(shù)據(jù)庫(kù)操作,如上代碼selectAlldetail方法所示。
在測(cè)試控制器內(nèi)添加了三個(gè)測(cè)試MVC方法,我們接下來(lái)開始編寫單元測(cè)試代碼。

編寫單元測(cè)試

在我們使用idea開發(fā)工具構(gòu)建完成SpringBoot項(xiàng)目后,會(huì)自動(dòng)為我們添加spring-boot-starter-test依賴到pom.xml配置文件內(nèi),當(dāng)然也為我們自動(dòng)創(chuàng)建了一個(gè)測(cè)試類,該類內(nèi)一開始是沒(méi)有過(guò)多的代碼的。
下面我們開始基于該測(cè)試類進(jìn)行添加邏輯,代碼如下所示:

....//省略依賴導(dǎo)包
/**
 * 單元測(cè)試
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter35ApplicationTests {
    /**
     * 模擬mvc測(cè)試對(duì)象
     */
    private MockMvc mockMvc;

    /**
     * web項(xiàng)目上下文
     */
    @Autowired
    private WebApplicationContext webApplicationContext;

    /**
     * 商品業(yè)務(wù)數(shù)據(jù)接口
     */
    @Autowired
    private GoodInfoJPA goodInfoJPA;

    /**
     * 所有測(cè)試方法執(zhí)行之前執(zhí)行該方法
     */
    @Before
    public void before() {
        //獲取mockmvc對(duì)象實(shí)例
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
}

在上面測(cè)試代碼中我們從上面開始講解下,其中@RunWith這里就不多做解釋了,我們最比較常用到的就是這個(gè)注解。

@SpringBootTest這個(gè)注解這里要強(qiáng)調(diào)下,這是SpringBoot項(xiàng)目測(cè)試的核心注解,標(biāo)識(shí)該測(cè)試類以SpringBoot方式運(yùn)行,該注解的源碼如下所示:

...//省略導(dǎo)包
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
public @interface SpringBootTest {
    @AliasFor("properties")
    String[] value() default {};

    @AliasFor("value")
    String[] properties() default {};

    Class<?>[] classes() default {};

    SpringBootTest.WebEnvironment webEnvironment() default SpringBootTest.WebEnvironment.MOCK;

    public static enum WebEnvironment {
        MOCK(false),
        RANDOM_PORT(true),
        DEFINED_PORT(true),
        NONE(false);

        private final boolean embedded;

        private WebEnvironment(boolean embedded) {
            this.embedded = embedded;
        }

        public boolean isEmbedded() {
            return this.embedded;
        }
    }
}

我們可以看到在@SpringBootTest注解源碼中最為重要的就是@BootstrapWith,該注解才是配置了測(cè)試類的啟動(dòng)方式,以及啟動(dòng)時(shí)使用實(shí)現(xiàn)類的類型。

測(cè)試index請(qǐng)求

MockMvc這個(gè)類是一個(gè)被final修飾的類型,該類無(wú)法被繼承使用。這個(gè)類是Spring為我們提供模擬SpringMVC請(qǐng)求的實(shí)例類,該類則是由MockMvcBuilders通過(guò)WebApplicationContext實(shí)例進(jìn)行創(chuàng)建的,初始化MockMvc實(shí)例我們可以看下before方法邏輯。到現(xiàn)在為止我們才是萬(wàn)事俱備就差編寫單元測(cè)試邏輯了,我們首先來(lái)編寫訪問(wèn)/index請(qǐng)求路徑的測(cè)試,具體測(cè)試代碼如下所示:

    /**
     * 測(cè)試訪問(wèn)/index地址
     * @throws Exception
     */
    @Test
    public void testIndex() throws Exception {
        MvcResult mvcResult = mockMvc
                .perform(// 1
                        MockMvcRequestBuilders.get("/index") // 2
                        .param("name","admin") // 3
                )
                .andReturn();// 4

        int status = mvcResult.getResponse().getStatus(); // 5
        String responseString = mvcResult.getResponse().getContentAsString(); // 6

        Assert.assertEquals("請(qǐng)求錯(cuò)誤", 200, status); // 7
        Assert.assertEquals("返回結(jié)果不一致", "this is index pageadmin", responseString); // 8
    }

MockMvc解析

我在上面代碼中進(jìn)行了標(biāo)記,我們按照標(biāo)記進(jìn)行講解,這樣會(huì)更明白一些:
1 perform方法其實(shí)只是為了構(gòu)建一個(gè)請(qǐng)求,并且返回ResultActions實(shí)例,該實(shí)例則是可以獲取到請(qǐng)求的返回內(nèi)容。
2 MockMvcRequestBuilders該抽象類則是可以構(gòu)建多種請(qǐng)求方式,如:PostGetPutDelete等常用的請(qǐng)求方式,其中參數(shù)則是我們需要請(qǐng)求的本項(xiàng)目的相對(duì)路徑,/則是項(xiàng)目請(qǐng)求的根路徑。
3 param方法用于在發(fā)送請(qǐng)求時(shí)攜帶參數(shù),當(dāng)然除了該方法還有很多其他的方法,大家可以根據(jù)實(shí)際請(qǐng)求情況選擇調(diào)用。
4 andReturn方法則是在發(fā)送請(qǐng)求后需要獲取放回時(shí)調(diào)用,該方法返回MvcResult對(duì)象,該對(duì)象可以獲取到返回的視圖名稱、返回的Response狀態(tài)、獲取攔截請(qǐng)求的攔截器集合等。
5 我們?cè)谶@里就是使用到了第4步內(nèi)的MvcResult對(duì)象實(shí)例獲取的MockHttpServletResponse對(duì)象從而才得到的Status狀態(tài)碼。
6 同樣也是使用MvcResult實(shí)例獲取的MockHttpServletResponse對(duì)象從而得到的請(qǐng)求返回的字符串內(nèi)容。【可以查看rest返回的json數(shù)據(jù)】
7 使用Junit內(nèi)部驗(yàn)證類Assert判斷返回的狀態(tài)碼是否正常為200
8 判斷返回的字符串是否與我們預(yù)計(jì)的一樣。

測(cè)試商品詳情

直接上代碼吧,跟上面的代碼幾乎一致,如下所示:

/**
     * 測(cè)試查詢?cè)斍?     * @throws Exception
     */
    @Test
    public void testDetail() throws Exception
    {
        MvcResult mvcResult = mockMvc
                .perform(
                        MockMvcRequestBuilders.get("/detail")
                        .param("goodId","2")
                )
                .andReturn(); // 5

        //輸出經(jīng)歷的攔截器
        HandlerInterceptor[] interceptors = mvcResult.getInterceptors();
        System.out.println(interceptors[0].getClass().getName());

        int status = mvcResult.getResponse().getStatus(); // 6
        String responseString = mvcResult.getResponse().getContentAsString(); // 7
        System.out.println("返回內(nèi)容:"+responseString);
        Assert.assertEquals("return status not equals 200", 200, status); // 8
    }

上面唯一一個(gè)部分需要解釋下,在上面測(cè)試方法內(nèi)輸出了請(qǐng)求經(jīng)歷的攔截器,如果我們配置了多個(gè)攔截器這里會(huì)根據(jù)先后順序?qū)懭氲綌r截器數(shù)組內(nèi),其他的MockMvc測(cè)試方法以及參數(shù)跟上面測(cè)試方法一致。

測(cè)試添加

在測(cè)試類聲明定義全局字段時(shí),我們注入了GoodInfoJPA實(shí)例,當(dāng)然單元測(cè)試也不僅僅是客戶端也就是使用MockMvc方式進(jìn)行的,我們也可以直接調(diào)用JPAService進(jìn)行直接測(cè)試。下面我們來(lái)測(cè)試下商品基本信息的添加,代碼如下所示:

/**
     * 測(cè)試添加商品基本信息
     */
    @Test
    public void testInsert()
    {
        /**
         * 商品基本信息實(shí)體
         */
        GoodInfoEntity goodInfoEntity = new GoodInfoEntity();
        goodInfoEntity.setTitle("西紅柿");
        goodInfoEntity.setOrder(2);
        goodInfoEntity.setPrice(5.82);
        goodInfoEntity.setTypeId(1);
        goodInfoJPA.save(goodInfoEntity);
        /**
         * 測(cè)試是否添加成功
         * 驗(yàn)證主鍵是否存在
         */
        Assert.assertNotNull(goodInfoEntity.getTgId());
    }

在上面代碼中并沒(méi)有什么特殊的部分,是我們?cè)谑褂?code>Data JPA時(shí)用到的save方法用于執(zhí)行添加,在添加完成后驗(yàn)證主鍵的值是否存在,NotNull時(shí)證明添加成功。

測(cè)試刪除

與添加差別不大,代碼如下所示:

    /**
     * 測(cè)試刪除商品基本信息
     */
    @Test
    public void testDelete()
    {
        //根據(jù)主鍵刪除
        goodInfoJPA.delete(3);
        
        //驗(yàn)證數(shù)據(jù)庫(kù)是否已經(jīng)刪除
        Assert.assertNull(goodInfoJPA.findOne(3));
    }

在上面代碼中,我們根據(jù)主鍵的值進(jìn)行刪除商品的基本信息,執(zhí)行刪除完成后調(diào)用selectOne方法查看數(shù)據(jù)庫(kù)內(nèi)是否已經(jīng)不存在該條數(shù)據(jù)了。

總結(jié)

本章主要介紹了基于SpringBoot平臺(tái)的兩種單元測(cè)試方式,一種是在服務(wù)端采用Spring注入方式將需要測(cè)試的JPA或者Service注入到測(cè)試類中,然后調(diào)用方法即可。另外一種則是在客戶端采用MockMvc方式測(cè)試Web請(qǐng)求,根據(jù)傳遞的不用參數(shù)以及請(qǐng)求返回對(duì)象反饋信息進(jìn)行驗(yàn)證測(cè)試。

本章代碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter

作者個(gè)人 博客
使用開源框架 ApiBoot 助你成為Api接口服務(wù)架構(gòu)師

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,087評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,207評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,603評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,813評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,364評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,110評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,305評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,532評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,033評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,268評(píng)論 2 375

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,809評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,901評(píng)論 6 342
  • Android單元測(cè)試介紹 處于高速迭代開發(fā)中的Android項(xiàng)目往往需要除黑盒測(cè)試外更加可靠的質(zhì)量保障,這正是單...
    東經(jīng)315度閱讀 3,143評(píng)論 6 37
  • <奇葩說(shuō)>第一季從海選我就開始看。賺到了! 看到奇葩說(shuō)斗轉(zhuǎn)星移,看到米未光榮成立。這個(gè)一個(gè)非常嚴(yán)肅的辯論節(jié)目,嚴(yán)肅...
    Amber_Fun閱讀 352評(píng)論 0 1
  • 文 | 原創(chuàng) ⒈ 閨蜜是個(gè)剪輯師,今天她在微博上給我@了一個(gè)視頻,那是一段快樂(lè)男生唱歌的片段,打開聽了。 唱的很...
    李大仁的茱麗葉閱讀 319評(píng)論 5 2