Spring bootでweb ユニットテスト編

51
ユニットテスト編
  • date post

    16-Jan-2017
  • Category

    Engineering

  • view

    3.503
  • download

    5

Transcript of Spring bootでweb ユニットテスト編

Page 1: Spring bootでweb ユニットテスト編

ユニットテスト編

Page 2: Spring bootでweb ユニットテスト編

アジェンダ はじめに

ユニットテストの必要性

ユニットテストの適用分野

ユニットテストをするためのプロダクト

Springでのユニットテスト

Resopitoryのユニットテスト

Serviceのユニットテスト

Controllerのユニットテスト(Validate)

Controllerのユニットテスト(画面遷移)

まとめ

Page 3: Spring bootでweb ユニットテスト編

はじめに ここでは、ユニットテストの必要性と、 ユニットテストの書き方を説明する

単体テスト

ユニットテスト

結合テスト

総合テスト・システムテスト

ユーザーテスト・運用テスト

一般的なテストの流れ

ここの話

Page 4: Spring bootでweb ユニットテスト編

ユニットテストの必要性 こんな経験ありませんか?

Page 5: Spring bootでweb ユニットテスト編

ユニットテストの必要性 こんな経験ありませんか?

A:「あれ?この処理がエラーで停止するよ?」

Page 6: Spring bootでweb ユニットテスト編

ユニットテストの必要性 こんな経験ありませんか?

A:「あれ?この処理がエラーで停止するよ?」 B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)

Page 7: Spring bootでweb ユニットテスト編

ユニットテストの必要性 こんな経験ありませんか?

A:「あれ?この処理がエラーで停止するよ?」 B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週)

Page 8: Spring bootでweb ユニットテスト編

ユニットテストの必要性 こんな経験ありませんか?

A:「あれ?この処理がエラーで停止するよ?」 B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週) A:「そのときこの処理のテストしたの?」

Page 9: Spring bootでweb ユニットテスト編

ユニットテストの必要性 こんな経験ありませんか?

A:「あれ?この処理がエラーで停止するよ?」 B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週) A:「そのときこの処理のテストしたの?」 C:「してないですね。1行しか直してないですし」

Page 10: Spring bootでweb ユニットテスト編

ユニットテストの必要性 こんな経験ありませんか?

A:「あれ?この処理がエラーで停止するよ?」 B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週) A:「そのときこの処理のテストしたの?」 C:「してないですね。1行しか直してないですし」 A:「え?」 B:「え?」

Page 11: Spring bootでweb ユニットテスト編

ユニットテストの必要性 こんな経験ありませんか?

A:「あれ?この処理がエラーで停止するよ?」 B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週) A:「そのときこの処理のテストしたの?」 C:「してないですね。1行しか直してないですし」 A:「え?」 B:「え?」 C:「・・・え??」

Page 12: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点1:過去の実績は現在においては不要

B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週)

開発中のプロジェクトにおいて 過去にパスしたテストが 現在もパスするとは限らない

Page 13: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点1:過去の実績は現在においては不要

B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週)

開発中のプロジェクトにおいて 過去にパスしたテストが 現在もパスするとは限らない

テストするためには 開発が終わっていないと 着手できない

Page 14: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点1:過去の実績は現在においては不要

B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週)

開発中のプロジェクトにおいて 過去にパスしたテストが 現在もパスするとは限らない

テストするためには 開発が終わっていないと 着手できない

開発が終わりテストをパスした コードは不具合等がない限り 修正してはならない

Page 15: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点1:過去の実績は現在においては不要

B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週)

開発中のプロジェクトにおいて 過去にパスしたテストが 現在もパスするとは限らない

テストするためには 開発が終わっていないと 着手できない

開発が終わりテストをパスした コードは不具合等がない限り 修正してはならない

理想的とは思えない汚コードの 修正は許されず、担当者が変わるとメンテが不可能になる

余談・・・

Page 16: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点1:過去の実績は現在においては不要の解決策

B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週)

開発中のプロジェクトにおいて 過去にパスしたテストが 現在もパスするとは限らない

パターンを網羅した テスト用のコードを書く

if ( 処理(パターンAのデータ) != パターンAの正常結果 ) { エラー } if ( 処理(パターンBのデータ) != パターンBの正常結果 ) { エラー }

Page 17: Spring bootでweb ユニットテスト編

if ( 処理(パターンAのデータ) != パターンAの正常結果 ) { エラー } if ( 処理(パターンBのデータ) != パターンBの正常結果 ) { エラー }

ユニットテストの必要性 問題点1:過去の実績は現在においては不要の解決策

B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週)

テストするためには 開発が終わっていないと 着手できない

テスト用のコードがあれば いつでもテストが出来る

Page 18: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点1:過去の実績は現在においては不要の解決策

B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週)

開発が終わりテストをパスした コードは不具合等がない限り 修正してはならない

テスト用のコードがあれば コードを直しても結果が 変わらないことを担保できる

if ( 処理(パターンAのデータ) != パターンAの正常結果 ) { エラー } if ( 処理(パターンBのデータ) != パターンBの正常結果 ) { エラー }

Page 19: Spring bootでweb ユニットテスト編

if ( 処理(パターンAのデータ) != パターンAの正常結果 ) { エラー } if ( 処理(パターンBのデータ) != パターンBの正常結果 ) { エラー

ユニットテストの必要性 問題点1:過去の実績は現在においては不要の解決策

B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に) C:「あ、共通処理直しました」 (先週)

理想的とは思えない汚コードの 修正は許されず、担当者が変わるとメンテが不可能になる

余談・・・

結果が変わらないことが担保できているので、常に理想的で きれいなコードを保てる

Page 20: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点2:修正の影響範囲の判定は職人技

A:「そのときこの処理のテストしたの?」 C:「してないですね。1行しか直してないですし」

修正量に応じて テストの要/不要が 変わることはない

Page 21: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点2:修正の影響範囲の判定は職人技

A:「そのときこの処理のテストしたの?」 C:「してないですね。1行しか直してないですし」

修正量に応じて テストの要/不要が 変わることはない

修正範囲の影響は呼出階層等を参照して判断するが、 基本的に技術者のスキルによる

Page 22: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点2:修正の影響範囲の判定は職人技

A:「そのときこの処理のテストしたの?」 C:「してないですね。1行しか直してないですし」

修正量に応じて テストの要/不要が 変わることはない

修正範囲の影響は呼出階層等を参照して判断するが、 基本的に技術者のスキルによる

全てのテストをやり直せば 職人技の判断が不要になるが それには時間と人手がかかる

Page 23: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点2:修正の影響範囲の判定は職人技

A:「そのときこの処理のテストしたの?」 C:「してないですね。1行しか直してないですし」

修正量に応じて テストの要/不要が 変わることはない

修正範囲の影響は呼出階層等を参照して判断するが、 基本的に技術者のスキルによる

全てのテストをやり直せば 職人技の判断が不要になるが それには時間と人手がかかる

担当者によってテストの精度にバラツキが出て、リスク回避の為 修正を避けるようになる

Page 24: Spring bootでweb ユニットテスト編

ユニットテストの必要性 問題点2:修正の影響範囲の判定は職人技の解決策

A:「そのときこの処理のテストしたの?」 C:「してないですね。1行しか直してないですし」

修正量に応じて テストの要/不要が 変わることはない

修正範囲の影響は呼出階層等を参照して判断するが、 基本的に技術者のスキルによる

全てのテストをやり直せば 職人技の判断が不要になるが それには時間と人手がかかる

担当者によってテストの精度にバラツキが出て、リスク回避の為 修正を避けるようになる

テストを書けば 回避できる

Page 25: Spring bootでweb ユニットテスト編

ユニットテストの適用分野 適用に向く分野

・共通処理プログラム ・パターンが多い処理プログラム ・ビジネスロジック ・データ更新/取得処理プログラム ・リリースを頻繁に繰り返すプロダクト などなど・・・

適用に向かない分野 ・画面の見た目(パーツの配置) ・画面の情報を更新する処理(スプレッドの更新など) ・一度リリースしたら終了するプロダクト

あくまで例として・・・

Page 26: Spring bootでweb ユニットテスト編

ユニットテストの適用分野 継続的インテグレーション(CI:continuous integration)

開発者 ソース管理

CIツール ステージングサーバー

1.コミット

2.pull 6.結果の通知

3.ビルド 4.ユニットテストの実施

5.デプロイ

Page 27: Spring bootでweb ユニットテスト編

ユニットテストをするためのプロダクト 以下の様な代表的なプロダクトが存在する

プロダクト 説明

JUnit Java用のユニットテストのパイオニア

NUnit JUnitをベースにした .NET用のプロダクト

Visual Studio Unit Testing Framework

Visual Studio付属のプロダクト

DbUnit データベース操作に特化したJava用のプロダクト

以降ではJUnitを使用して、 Springで書いたJavaソースの ユニットテストを実施する

Page 28: Spring bootでweb ユニットテスト編

Springでのユニットテスト どこでなにをテストするかがポイント

どこ なにを

Controller(画面制御) 全ての入力項目に対して、全てのパターンの入力値を与えた妥当性(バリデート)チェック

画面遷移

Service(ビジネスロジック) パターンを網羅したロジックのチェック

Repository(データの入出力) データ取得の確認

データ保存の確認

見た目の確認や負荷テスト等は JUnitではテストしない

Page 29: Spring bootでweb ユニットテスト編

Resopitoryのユニットテスト テスト用のクラスを準備をする

/** ProductRepositoryのUT */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = App.class) public class ProductRepositoryTest { /** 事前処理 */ @Before public void 事前処理( ) throws Exception { } /** 事後処理 */ @After public void 事後処理( ) throws Exception { } }

JUnitでSpringを動かすオマジナイ

Springを起動するクラスを記述

テストの事前処理

テストの事後処理

Page 30: Spring bootでweb ユニットテスト編

Resopitoryのユニットテスト テスト対象のRepositoryを宣言

/** ProductRepositoryのUT */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = App.class) public class ProductRepositoryTest { @Autowired private ProductRepository productRepository;

テスト対象のRepositoryを コンテナから取得

Page 31: Spring bootでweb ユニットテスト編

Resopitoryのユニットテスト サンプルデータの準備

/** 事前処理 */ @Before public void 事前処理() throws Exception { Product product1 = new Product(); product1.productCode = 1; product1.productName = "ガム"; product1.price = 100; productRepository.save(product1); Product product2 = new Product(); product2.productCode = 2; product2.productName = "アメ"; product2.price = 120; productRepository.save(product2); }

テスト用データを 事前処理で用意する (これ自体が保存のテストと言えなくもない)

Page 32: Spring bootでweb ユニットテスト編

Resopitoryのユニットテスト テストの実行

/** findOneメソッドのテスト */ @Test public void findOneメソッドのテスト( ) { Product product = productRepository.findOne(1); assertEquals ( product.productName, "ガム“ ); }

テスト用メソッドだと宣言する

事前処理で用意した データを取得する

評価メソッドで結果を評価する product.productNameが 「ガム」ではない場合、 例外が発生しテスト失敗となる

※あくまでサンプルとして既定の処理を呼び出している。 本来は自身で作成した処理を呼んでテストする。

Page 33: Spring bootでweb ユニットテスト編

Resopitoryのユニットテスト 代表的な評価メソッド

メソッド 説明

assertEquals ( A , B ) A と B が同じ値か評価する

assertTrue( A ) assertFalse( A )

A が true か false か評価する

assertNull( A ) assertNotNull( A )

A が null か nullではない か評価する

fail( ) 強制的にエラーとする

Page 34: Spring bootでweb ユニットテスト編

Serviceのユニットテスト テスト用のクラスを準備をする

/** ProductServiceのUT */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = App.class) public class ProductServiceTest { /** 事前処理 */ @Before public void 事前処理( ) throws Exception { } /** 事後処理 */ @After public void 事後処理( ) throws Exception { } }

JUnitでSpringを動かすオマジナイ

Springを起動するクラスを記述

テストの事前処理

テストの事後処理

Page 35: Spring bootでweb ユニットテスト編

Serviceのユニットテスト テスト対象のServiceを宣言

/** ProductServiceのUT */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = App.class) public class ProductServiceTest { @Autowired private ProductService productService ;

テスト対象のServiceを コンテナから取得

Page 36: Spring bootでweb ユニットテスト編

Serviceのユニットテスト 事前準備

/** 事前処理 */ @Before public void 事前処理() throws Exception { }

何かあれば記述する

Page 37: Spring bootでweb ユニットテスト編

Serviceのユニットテスト テストの実行

/** プロダクト一覧を取得できるかのテスト */ @Test public void プロダクト一覧を取得できるかのテスト() { List<Product> list = productService.getProductList(); }

テスト用メソッドだと宣言する

テスト対象の ビジネスロジックを呼ぶ

評価メソッドで結果を評価する (このサンプルでは未実装) リストの件数や内容を評価する コードを記述する

Page 38: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(Validate) テスト用のクラスを準備をする

/** ProductFormのUT */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = App.class) public class ProductFormTest { /** 事前処理 */ @Before public void 事前処理( ) throws Exception { } /** 事後処理 */ @After public void 事後処理( ) throws Exception { } }

JUnitでSpringを動かすオマジナイ

Springを起動するクラスを記述

テストの事前処理

テストの事後処理

Page 39: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(Validate) テスト用のバリデータを宣言

/** ProductFormのUT */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = App.class) public class ProductFormTest { /** バリデータ */ private Validator validator;

妥当性検証を実行する バリデータを宣言

Page 40: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(Validate) 事前準備

/** 事前処理 */ @Before public void 事前処理() throws Exception { validator = Validation.buildDefaultValidatorFactory( ) .getValidator( ); }

バリデータを初期化

Page 41: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(Validate) テスト対象のフォーム

/** 画面の値を保持するForm */ public class ProductForm { @NotNull private Integer productCode; @NotNull @Length(max=10) private String productName; @NotNull private Integer price;

入力必須

入力必須

最大文字数は10文字まで

入力必須

Page 42: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(Validate) テストの実行

/** productNameの妥当性テスト */ @Test public void productNameの妥当性テスト( ) throws Exception { ProductForm productForm = new ProductForm( ); productForm.setProductCode( 1 ); productForm.setProductName( "ポテトチップスうすしお“ ); productForm.setPrice( 100 ); // バリデート Set<ConstraintViolation<ProductForm>> violations = validator.validate( productForm ); // 検証 assertEquals( violations.size( ), 1 ); for (ConstraintViolation<ProductForm> v: violations) { assertTrue( v.getConstraintDescriptor( ).getAnnotation( ) instanceof Length);

テスト用メソッドだと宣言する

テスト対象の フォームを初期化

productNameのみが 11文字でエラーになるはず

Page 43: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(Validate) テストの実行

ProductForm productForm = new ProductForm( ); productForm.setProductCode( 1 ); productForm.setProductName( "ポテトチップスうすしお“ ); productForm.setPrice( 100 ); // バリデート Set<ConstraintViolation<ProductForm>> violations = validator.validate( productForm ); // 検証 assertEquals( violations.size( ), 1 ); for (ConstraintViolation<ProductForm> v: violations) { assertTrue( v.getConstraintDescriptor( ).getAnnotation( ) instanceof Length); } }

バリデートの実行

エラーの数は 1 つかチェック

エラーの種類が Length かチェック

Page 44: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(画面遷移) テスト用のクラスを準備をする

/** ProductControllerのUT */ @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @SpringApplicationConfiguration(classes = App.class) public class ProductControllerTest { /** 事前処理 */ @Before public void 事前処理( ) throws Exception { } /** 事後処理 */ @After public void 事後処理( ) throws Exception { } }

JUnitでSpringを動かすオマジナイ (Webアプリ用宣言を追加)

Springを起動するクラスを記述

テストの事前処理

テストの事後処理

Page 45: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(画面遷移) テスト用のコンテキストとMVCのモックを宣言

/** ProductControllerのUT */ @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @SpringApplicationConfiguration(classes = App.class) public class ProductControllerTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc;

アプリケーションの設定等々を管理するコンテキスト

リクエストとレスポンスと、 それに付随する情報の モックオブジェクト

Page 46: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(画面遷移) 事前準備

/** 事前処理 */ @Before public void 事前処理() throws Exception { mockMvc = webAppContextSetup(wac).build(); }

モックを初期化

Page 47: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(画面遷移) テストの実行(入力画面の初期表示の)

/**入力画面の初期表示のテスト */ @Test public void 入力画面の初期表示のテスト( ) throws Exception { mockMvc.perform(get("/product/input")) .andExpect(status().isOk()) .andExpect(model().hasNoErrors()); }

テスト用メソッドだと宣言する

URL「/public/input」にGETメソッドでアクセスする

結果のHTTPステータスが OK(200)か評価する

Model(フォームの内容)に エラー情報がないか評価する

Page 48: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(画面遷移) テストの実行(入力画面から確認画面に遷移)

/** 入力画面から確認画面に遷移するテスト */ @Test public void 入力画面から確認画面に遷移するテスト() throws Exception { ResultActions resultActions = mockMvc.perform(post("/product/confirm") .contentType(MediaType.APPLICATION_FORM .param("productCode", "1") .param("productName", "ガム") .param("price", "100") ); // レスポンスの検証 resultActions.andExpect(status().isOk()) .andExpect(model().hasNoErrors());

テスト用メソッドだと宣言する

URL「/public/confirm」にPOSTメソッドでアクセスする

入力画面で入力した値として 各種パラメータを付加する

Page 49: Spring bootでweb ユニットテスト編

Controllerのユニットテスト(画面遷移) テストの実行(入力画面から確認画面に遷移)

// レスポンスの検証 resultActions.andExpect(status().isOk()) .andExpect(model().hasNoErrors()); // モデルの内容の検証 ModelMap modelMap = resultActions.andReturn( ) .getModelAndView( ).getModelMap( ); ProductForm productForm = (ProductForm)modelMap .get("productForm"); assertEquals(productForm.getProductCode(), new Integer(1)); assertEquals(productForm.getProductName(), "ガム"); assertEquals(productForm.getPrice(), new Integer(100)); }

モデルからフォームを取得

リクエスト時に付加したパラメータが フォームから取得できるか評価する

結果のHTTPステータスが OK(200)か評価する Model(フォームの内容)に エラー情報がないか評価する

Page 50: Spring bootでweb ユニットテスト編

まとめ ユニットテストで単体テストを補完する

→ 画面を開いて境界値テスト等するより向いている

ユニットテストを過信しない → ユニットテストをすれば単体テストをしなくて良いわけではない

ユニットテストでテストのコストは下がらない → コードを書くので画面を開いてテストするよりコストはかかる → 時にはテストコードのテストも必要

テストをするのが目的で、テストを書くのが目的ではない → コーディングが楽しいからと言って、目的を失ってはいけない → テスト中毒は程々に

本体のコードを修正したらテストコードを直すのはマスト → テストが通らないテストコードはメンテナンスする意識を一気に失う → そのためテストコードのメンテナンスは怠らない(ビルドエラーは論外)

Page 51: Spring bootでweb ユニットテスト編

まとめ 参考

■Spring MVC 3.2のSpring MVC Testを触った - コンピュータクワガタ http://kuwalab.hatenablog.jp/entry/20130402/p1 ■ Spring3(というかJSR-303)でBeanValidationのテスト « ひよっこ。 https://prepro.wordpress.com/2011/01/15/spring3%E3%81%A8%E3%81%84%E3%81%86%E3%81%8Bjsr-303%E3%81%A7beanvalidation%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88/ ■ JSR 303 Bean Validationで遊んでみるよ! - Yamkazu's Blog http://yamkazu.hatenablog.com/entry/20110206/1296985545 ■私のBeanValidationの使い方(Java EE Advent Calendar 2013) — 裏紙 http://backpaper0.github.io/2013/12/03/javaee_advent_calendar_2013.html