Java SEの基盤の統合(XII):ユニットテスト

1つの概要

あなたが知っているかもしれませんが、ソフトウェア開発のテストは、テストの結果に基づいてレポートに配置すると、プログラムがない場合は予想に沿ったもので、(期待される正確性、性能と品質手順などを含む)の期待に沿って実行されているかどうかを確認するために、非常に重要な部分であります問題、、、再びテストし、その後それ以外の製品を、彼らはライン上で、公開しようとする前に、多くの場合、期待に沿った健康プログラムまで繰り返さいくつかの時間を必要とするプロセスを、問題を修正、および、ソフトウェアは責任を負いません。

分類に応じて、テストが最も一般的で、通常は最も重要なことは、開発の段階に応じて分割され、異なる種類に分けることができ、それはテストの4つの主要なタイプに分けることができます:

  • ユニットテスト
  • 統合テスト
  • システムテスト
  • 受け入れテスト

:この記事では、最初の主要なプレゼンテーションでユニットテスト現像剤として、他の3つはあまり馴染みかもしれないが、ユニットテストは非常に精通しなければなりません。

ここではウィキペディア除去ユニットテストからの定義は次のとおりです。

コンピュータプログラミング単体テスト(英語:ユニットテスト)としても知られている、モジュールテストは、ためのものであるプログラムモジュール設計されたソフトウェア最小単位の正しさを検証する作業をテストするために)。これは、最小のプログラムユニットテストコンポーネント・アプリケーションです。手続き型プログラミング、細胞は、単一のプログラム、関数、プロシージャ、等であるオブジェクト指向プログラミングは、基底クラス(スーパー)、抽象クラスまたは派生クラス(サブクラス)メソッドを含む、最小単位です。

ユニットテストの目的は、プログラムの正しさをテストすることであると言うことができ、パフォーマンス、可用性が必要とされていない、多くの場合、ユニットテストは、最も基本的なテストであるユニットがテストに合格することができない場合は、後者の試験は全く必要ないと言われています。

Javaコミュニティは、などのJUnit、Mockito、TestNGの、今、私はJUnitのとMockitoの使用を紹介しますように多くの優れたサードパーティ製のオープンソースのテストフレームワークがあります。

この記事では、テストツールの使用にのみ交渉をソフトウェアテストの理論的な知識を必要としません。

2のJUnit

JUnitは非常によく知られているオープンソースのテストフレームワークであり、さらに多くの非Java開発者は、多かれ少なかれ聞いています。JUnitは今(2018年10月15日)Junit5、いくつかのより多くの機能をリリースしました、そしてサポートされる最小のJavaバージョンはJava8ですが、この記事では、代わりにJUnit4を使用しての、Junit5を使用する予定はありません。JUnit5について変更することをお勧め公式サイトを表示します。

2.1をダウンロードしてインストール

ダウンロード公式サイトのアドレスは、JUnitのジャーパッケージで提供され、パッケージはjarファイルをインポートするために使用することができます。プロジェクトがMavenプロジェクトである場合は、次のように、それはまた、pom.xmlファイルのJUnitの依存関係に追加することができます。

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>
复制代码

味に始めてのJUnit 2.2

開始からJUnit4は、我々はこの方法をテストするための方法であることを示すために、試験する方法に@Testコメントを追加することができます。JUnit3時間では、唯一のtestXXXという名前のモデルの形で提供するテストメソッドであることを「命名規則」メソッド名を使用することができる方法をテストするためには多くの欠点や欠陥を持っているので、我々はそれ以降のバージョンJUnit4を利用することをお勧めします。ここでJUnit4の簡単な使用例は次のとおりです。

public class ApplicationTest {

    private int calculateSum(int a, int b) {
        return a + b;
    }

    @Test
    //这里的方法名只是一种习惯用法,JUnit4并不强制要求必须是testXXX
    public void testCalculate() {
        Assert.assertEquals(10, calculateSum(5, 5));       //通过
        Assert.assertEquals(10, calculateSum(20, -10));    //通过
        Assert.assertEquals(10, calculateSum(0,0));        //不通过,一般不会这样写,这里只是为了演示
        Assert.assertNotEquals(10, calculateSum(10, 10));  //通过
    }
}
复制代码

@Testアノテーション方法は、例外がメソッドでスローされた場合、それはテストが失敗した場合でも、テストする方法をすべてのメソッドを呼び出します、プログラムの起動時にメソッドをテストするためにあります。アサートは、org.junitパッケージの下のクラスである私たちの使用するための豊富なAPIアサーションを提供し、同等の期待と実際の値をアサートするために使用例のassertEqualsため、assertNullは、引数がnull値であることを主張するために使用しました。:コードの場合には、一つだけの試験方法まで、試験方法は、アサーションは、以下の実質的に戻り値calculateSumが期待するかどうかを確認するために4つの方法、ランチャー、コンソール出力されcalculateSumターゲット方法であります


java.lang.AssertionError: 
Expected :10
Actual   :0
 <Click to see difference>


	at org.junit.Assert.fail(Assert.java:88)
	at org.junit.Assert.failNotEquals(Assert.java:834)
	at org.junit.Assert.assertEquals(Assert.java:645)
	at org.junit.Assert.assertEquals(Assert.java:631)
	at top.yeonon.ApplicationTest.testCalculate(ApplicationTest.java:21)
	.......
复制代码

あなたはAssertionErrorの方法は、問題の場所を特定するための例外と印刷例外スタックをスロー見ることができ、加えて、JUnitはまた、すなわち、簡単なテストレポートを提供します:

java.lang.AssertionError: 
Expected :10
Actual   :0
复制代码

我々はプログラムのカスタムでは、実際の戻り値はcalculateSumで、JUnitのは、私たちに伝えるためにされたいようにそれは、期待値である期待:あなたは値が10で期待していますが、実際の値が0である、それは予想に沿ったものではありませんが、試してみてください問題を修正。

以下は、比較的複雑なの一例である(ちょうど比較例上記のように、実際の開発はそれほど単純ではありません)。

public class AppTest {

    @Test
    public void testAssertEqualAndNotEqual() {
        String name = "yeonon";
        Assert.assertEquals("yeonon", name);
        Assert.assertNotEquals("weiyanyu", name);
    }

    @Test
    public void testArrayEqual() {
        byte[] expected = "trial".getBytes();
        byte[] actual = "trial".getBytes();
        Assert.assertArrayEquals("failure - byte arrays not same", expected, actual);
    }

    @Test
    public void testBoolean() {
        Assert.assertTrue(true);
        Assert.assertFalse(false);
    }

    @Test
    public void testNull() {
        Assert.assertNull(null);
        Assert.assertNotNull(new Object());

    }

    @Test
    public void testThatHashItems() {
        Assert.assertThat(Arrays.asList("one","two","three"), CoreMatchers.hasItems("one","two"));
    }

    @Test
    public void testThatBoth() {
        Assert.assertThat("yeonon",
                CoreMatchers.both(
                        CoreMatchers.containsString("e"))
                        .and(CoreMatchers.containsString("o")));

    }
}

复制代码

実際には、おそらく機能を知っているメソッド名を見て、言っても過言ではない、APIアサートのさまざまなを試してみてください。

:ところで、あなたはあまりにも多くのアサートを感じ、CoreMatchersが疲れて見えたならば、次のような、静的インポートパッケージのインポートパッケージを使用することができます

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
复制代码

JUnitの使用量が理由のように人気のJUnitの理由一つである、直接そう単純で、粗です。もちろん、その後、ポイントはJUnitの機能のみ、より高度な機能についてJUnitのではない、推奨されるためにJUnitの公式サイト、公式ドキュメントをチェックアウトし、文書を書くために良いです。

3 Mockito

Mockitoは、その最大の特徴は「モック」、つまりシミュレーションで、非常に強力なテストフレームワークです。非常に重要なキーポイントは、テストコードの依存性を伴うテストを行うには、実際の環境をシミュレートしようとしていない場合はユニットテストを試してみることです。Mockitoはこれを行うことができ、彼はパッケージモックオブジェクトとして使用され、モックオブジェクトは、彼らの行動は、私たちが望むように見えるように構成することができることは、設定可能です。

例えば、一般的なWeb開発では、バックエンドは、すなわち、制御層の同級生、今回の学生のレベルを制御する責任MVCは、制御層を書かれているかもしれないが、モデル層は、学生が書くことがないために責任がある、3層に分割されますモデル層を待つ必要はありませんので、テスト機能制御層をしたい、あなたは(インターフェースとは定義されていますが、まだ機能が実装されていないと仮定して)シミュレーションモデルモック層を使用することができ、その後、テスト、完成学生のための責任があります。

3.1をダウンロードしてインストール

そして、JUnitのように、あなたは、プロジェクトがMavenプロジェクトである場合、あなたはpom.xmlファイルに以下の依存関係を追加することができ、jarファイルパッケージをダウンロードして、プロジェクトをインポートすることができます。

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.21.0</version>
    <scope>test</scope>
</dependency>
复制代码

実際の使用ではなく、JUnitの依存関係を追加する必要がある場合。(しかし、JUnitのに依存しないことmockitoは、このプロジェクトは、JUnitの上にのみ依存しています)

使用するのは簡単3.2

ここだけの簡単な例、次のように:

public class ApplicationTest {

    //有返回值方法
    public int calcSum(int a, int b) {
        return 1;
    }

    //无返回值方法
    public void noReturn() {

    }

    @Test
    //设置单个返回值
    public void testOneReturn() {
        ApplicationTest test = mock(ApplicationTest.class);
        when(test.calcSum(10, 10)).thenReturn(10);
        assertEquals(10,test.calcSum(10, 10));
    }

    @Test
    //设置多个返回值,按顺序校验
    public void testMultiReturn() {
        ApplicationTest test = mock(ApplicationTest.class);
        when(test.calcSum(10, 10)).thenReturn(10).thenReturn(20);
        assertEquals(10, test.calcSum(10, 10));
        assertEquals(20, test.calcSum(10, 10));
    }

    @Test
    //根据输入参数不同来定义不同的返回值
    public void testMethodParam() {
        ApplicationTest test = mock(ApplicationTest.class);
        when(test.calcSum(0,0)).thenReturn(1);
        when(test.calcSum(1,1)).thenReturn(0);
        assertEquals(1, test.calcSum(0, 0));
        assertEquals(0, test.calcSum(1, 1));
    }

    @Test
    //返回值不依赖输入
    public void testNotMethodParam() {
        ApplicationTest test = mock(ApplicationTest.class);
        when(test.calcSum(anyInt(),anyInt())).thenReturn(-1);
        assertEquals(-1, test.calcSum(10, 10));
        assertEquals(-1, test.calcSum(100, -100));
    }

    @Test
    //根据返回值的类型来决定输出
    public void testReturnTypeOfMethodParam() {
        ApplicationTest test = mock(ApplicationTest.class);
        when(test.calcSum(isA(Integer.class), isA(Integer.class))).thenReturn(-100);
        assertEquals(-100, test.calcSum(100, 100));
        assertEquals(-100, test.calcSum(111,111));
    }

    @Test
    //行为验证,主要用于验证方法是否被调用
    public void testBehavior() {
        ApplicationTest test = mock(ApplicationTest.class);
        test.calcSum(10, 10);
        test.calcSum(10, 10);
        //times(2)表示被调用两次
        verify(test, times(2)).calcSum(10, 10);
    }
}

复制代码

まず第一に、我々はモックオブジェクト内に構築されているあらゆる方法で、つまり

ApplicationTest test = mock(ApplicationTest.class);
复制代码

工事が完了したら、あなたは、いくつかの設定を行う方法が使用されているtestOneReturn取ることができたときに(...)。ThenReturn(...)引数は、たとえば、メソッド呼び出しのとき、モックオブジェクトを設定する方法、 test.calcSum(10、10)、threnReturnパラメータはコールの戻り値が提供されます。だから、とき(test.calcSum(10、10))thenReturn(10);このコード行は、 "test.calcSum(10,10)を呼び出すとき、それは10に戻るべき" を意味し、その後のassertEquals(10、テストを呼び出します.calcSum(10、10));正しいことを確認します。

ここでは、少し奇妙かもしれないが、いずれにせよ、コードcalcSumは返す必要があります-1魚を、ああ、この行が、それをテストすることが可能であるかどうかということ?答えはエネルギーです!このメソッドの呼び出しで(...)。ThenReturn(...)を行うように設定されているときに我々は、それは、戻り値は、当社のカスタムとしてここで定義されている使用しているので、どんなにcalcSumは限り我々はときの規定に従っているように、達成する方法であります(実施例test.calcSum(10、10)である)コールの形式で値を設定し、それは)(thenReturnペアを返さなければなりません。

他の方法には言っていない、とtestOneReturnは、()はほとんどなく、理解しやすいはずです、コメントをしました。

4 SpringBootテスト

4.1簡単なデモ

次のようにSpringBootテストモジュールは、春ブーツは、唯一のBootテストの春への依存関係を追加する必要があり、テスト項目の時に依存しているのJUnit、Mockitoを、含まれています。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>根据官网发布的版本进行选择,记得避免版本冲突</version>
    <scope>test</scope>
</dependency>
复制代码

3つのMVCコードの標準的な春ブーツは、私がテストクラスでは非常に単純な、簡単な外観を省略しました。

package top.yeonon.springtest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import top.yeonon.springtest.controller.UserController;
import top.yeonon.springtest.repository.UserRepository;
import top.yeonon.springtest.service.UserService;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
 * @Author yeonon
 * @date 2018/10/15 0015 18:21
 **/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

    private MockMvc mockMvc;

    @Autowired
    private UserController userController;

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    @Before
    public void setUp() {
        //构造mockMvc
        mockMvc = MockMvcBuilders.standaloneSetup(userController, userService, userRepository).build();
    }

    @Test
    public void testUserService() throws Exception {
        RequestBuilder request = null;

        //1. 注册用户

        request = post("/users")
                .param("username", "yeonon")
                .param("password", "admin")
                .contentType(MediaType.APPLICATION_JSON_UTF8);

        mockMvc.perform(request)
                .andExpect(status().is(200))
                .andExpect(content().string("注册成功"));  //在业务代码中,如果成功就会返回“注册成功”;

        //2. 根据id获取用户
        request = get("/users/1");
        mockMvc.perform(request)
                .andExpect(status().is(200))
                .andExpect(content().string("{\"id\":1,\"username\":\"yeonon\",\"password\":\"admin\"}"));

        //3. 修改用户信息
        request = put("/users")
                .param("username", "weiyanyu")
                .param("password", "aaa")
                .param("id", "1");
        mockMvc.perform(request)
                .andExpect(status().is(200))
                .andExpect(content().string("更新成功"));

        //4. 再次获取信息
        request = get("/users/1");
        mockMvc.perform(request)
                .andExpect(status().is(200))
                .andExpect(content().string("{\"id\":1,\"username\":\"weiyanyu\",\"password\":\"aaa\"}"));

        //5. 删除用户
        request = delete("/users/1");
        mockMvc.perform(request)
                .andExpect(status().is(200))
                .andExpect(content().string("删除成功"));

    }
}

复制代码

mockMvc春は、クラスパッケージで、名前からわかるようにMVCのシミュレーションで、実際には、それがあります。全体の試験プロセスは、以下のステップに分けることができます。

  1. 建設mockMvcは、オブジェクト、および豆を(あなたが不完全渡した場合、故障につながる可能性が豆nullポインタ例外を注入するために報告しなければならない)、対応するMockMvcBuildersを通過します。
  2. RequestBuilderオブジェクトはMockMvcRequestBuilders.get()、MockMvcRequestBuilders.post()方法等によって得ることができるます。
  3. RequestBuilderオブジェクトは、特定の行動を表すResultActionsオブジェクトを返すmockMvc.perform()メソッドを、渡されました。
  4. ResultActions APIは例えばandExpect、andDo行わ検証の結果を返すためにオブジェクトによって提供される、andReturnたいです。受信したパラメータandExpect ResultMatcherはオブジェクトのタイプであり、多くの方法があり、このような状況、コンテンツなど、MockMvcResultMatchersに私達の使用のために使用することができます。

これは、Webテストを完了します。特別な処理をせずに戻るには中国のコントローラーがあるので、もしここで、このテスト環境では、デフォルトのエンコーディングはUTF-8(ISO-XXXのように、特定のは忘れて)問題をコーディングする方法ではありません、それは間違っている可能性があります。一つの解決策は、コントローラのプロパティを変更することで次のように、@RequestMappingを生成します。

@DeleteMapping(value = "{id}",produces = "application/json;charset=UTF-8")
public String deleteUser(@PathVariable("id") Long id) {
    return userService.deleteUser(id);
}
复制代码

4.2 H2メモリデータベース

小さなテストプロジェクトは、実際には、H2データベースを使用しました。h2は、Java言語の開発データベースで、アプリケーションに直接組み込み、およびアプリケーションがパッケージ化され、解放することができ、プラットフォームに依存しない、それはまた、メモリモードをサポートし、それはテスト環境に適しています。一般的に利便性のために、すべての操作がない持続性のために、メモリモードでのメモリで実行され、.sqlファイルは、プロジェクトのH2にロードされ、その後、メモリモデルのテストを使用され、テスト環境で使用する場合、汚れたデータベースの本番環境を心配するので、必要はありません。

春ブーツはまた、我々は唯一、次のように、プロジェクトに関連する依存H2を追加して使用することができます設定の少量を行う必要があり、H2をサポートしています:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.196</version>
</dependency>
复制代码

以下のような構成は以下のとおりです。

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:h2test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=admin
spring.datasource.password=admin

spring.jpa.database=h2
spring.jpa.hibernate.ddl-auto=update

spring.h2.console.enabled=true
spring.h2.console.path=/console

复制代码
  1. データソース、従来の4つの構成を理解するのは難しい、と言うことではありません。
  2. spring.jpa.database。プロジェクトは、JPAを使用しているので、ので、ここでJPAは、ターゲット・データベース・タイプの外観はh2とされる設定します。
  3. spring.h2.console.enabled。コンソールH2を有効にするかどうか、H2は、Webコンソール、ユーザーフレンドリーなCRUDデータを提供します。
  4. spring.h2.console.path。コンソールにポート/コンソール:// localhostを:、上記の構成とパスコンソールは、ブラウザのアドレスバーにhttpで入力することができ、時間を使い、/コンソールです。

次のようにプログラムを起動した後、ブラウザのH2コンソールにURLを入力します。

iaBLm4.png

設定を行った後、以下に示すように、コンソールインターフェイスを入力して接続をクリックし、ユーザー名とパスワードを入力します。

iaBO0J.png

SQL文は、データベースを操作するための仕様に準拠して入力することができますスペースでは、あなたは私たちがH2で、作成することを支援するJPAあるT_USERデータベーステーブルがある左のサイドバーを見ることができ、テーブルのデフォルト名は大文字であるが、中あなたは小文字を使用することができ、SQL文を書き、H2は、私たちが大文字に変換するのに役立ちます。次のように:

iaDC6O.png

最初のH2のデータベースについてあなたはJDBCに精通している場合、その後、H2の動作の詳細についてあまり心配しないように、また、JDBC準拠のH2のインターフェイスとして。

5 TDD

TDDすなわち、テスト駆動開発(テスト駆動開発)。名前はその意味を理解するのは簡単ではないかもしれないが、測定されたテスト駆動開発とは何でしょうか?なぜ開発を開始するためにテストを使うのか?開発型をテストするには?我々は、次の3つの質問の短いTDDに焦点を当てます。

5.1テスト駆動開発とは何ですか

同様のコンセプトに事前連絡がない場合は、テストのほとんどの人々の理解は次のようになりますように最初のコードを書き、テストが完了した後、その後、試験の目的は、プログラムの正しさ、パフォーマンス、可用性、スケーラビリティの品質を検証することである、と。テスト駆動開発は、対照的に、TDDの提唱者は、その後、テストプログラムを書くテストプログラムは、限り、テストケースとしてはるかに少ないそれにコードをリファクタリングする際に考慮すべき良いものを書くことができるように、テストが成功する満たすためにコードを書くことですただ、コードがテストに合格することができます。

5.2なぜテスト駆動開発

最初の試験後のTDDと従来の方法は、少なくとも以下のいくつかの利点と比較して開発します。

  • 開発者の負担を軽減し、開発者は、唯一の要件の様々なもつれた混乱を必要としない、テストケースを通じてコードを記述する必要があります。
  • これは、需要の変化への強い適応性を持っていますが、需要の変化は、唯一のニーズに応じてテストを変更し、再度書き込みまたは発生する「ペニー賢明、ポンド愚かな」を回避するために、テストケースを適応させるためにコードを変更する必要がある場合。
  • 代わりに半分の需要を見つけるためだけに書かれているコードを書くの、需要を整理するために私達を促すことが明らか、筆記試験の前進を要求することにつながる、明らかではない「リワーク。」
  • 事前に書かれたテストは、長い時間と多くのエネルギーを要するものの、準備、いわゆる高効率は、仕事を速くかもしれないが、彼らはそれだけの価値はある消費します。あなたは、事前にテストを書き、そして最終的には独自の手動テストを必要としない場合は、手動テスト彼らはアプリケーションの起動に時間がかかる一方で、前後に様々なジャンプの間のインタフェースは、実際には、時間がそれは自動テストを書くために以前よりもはるかにかかります。

駆動開発をテストする方法5.3

実際には、かすかには上記にこれを述べたが、明確なアイデアやステップを与えていない、次はTDDの基本的な流れは以下のとおりです。

  1. テストケースを書きます
  2. テストプログラムを実行し、この時間は、試験に合格するべきではありません
  3. コードを記述し、目標は、テストケースを通じてコードを作ることです。
  4. テストに合格するまで、等々。この時点でテストプログラムを再実行したり、テストに合格していない場合は、ステップ3に戻って、と。

問題は、なぜテストプログラムにそれを実行し、ステップ2は、(なし書き込み、特定のコードため)に失敗することが明らかにそうで、ありますか?失敗の理由が多く、必ずしもされているのでそこに生じたテスト環境の問題が考えられますので、エラーレポートを見るために、一度それを実行し、テスト環境の問題ならば、最初のテストを修復しようと、何の書き込み特定のコードが発生しないがあるので、テスト環境の問題で開発した場合、環境、または、それは(テストが失敗するので、各テストは、テスト環境を発行しますので)テストに合格したプログラムを書くための方法を不可能に関係なく発生することがあります。

私はTDDの理解ですので実際には、TDDは、多くの、多くの利点がそれよりもはるかに多く存在している、いくつかの欠点があり、あまりないが、通常は怠惰なので、最初の書き込みテストの習慣を開発していない、そう言うことはありませんここでの「議論を開始」である自分自身の学習、上の関連する情報のための推奨検索。

6まとめ

本論文では、ユニットテストの概念を導入し、ついでに、Mockitoを使いやすくし、その後も少し練習を行うために春ブーツプロジェクトと組み合わせる2つのテストフレームワークJUnitのを、導入、我々は読者を助けるために願っています。最後に、TDD(テスト駆動開発)への簡単な紹介、TDDは、効果的に開発効率と製品の品質を向上させることができますが、詳細な調査をしたい場合、TDDは、実際にお勧めする、科学も、アジャイル開発におけるコア技術である、ここで見て。

おすすめ

転載: juejin.im/post/5d6e808d5188252e96191e51