狠狠撸

狠狠撸Share a Scribd company logo
ユニットテスト编
アジェンダ
? はじめに
? ユニットテストの必要性
? ユニットテストの適用分野
? ユニットテストをするためのプロダクト
? Springでのユニットテスト
? Resopitoryのユニットテスト
? Serviceのユニットテスト
? Controllerのユニットテスト(Validate)
? Controllerのユニットテスト(画面遷移)
? まとめ
はじめに
? ここでは、ユニットテストの必要性と、
ユニットテストの書き方を説明する
単体テスト
ユニットテスト
結合テスト
総合テスト?システムテスト
ユーザーテスト?運用テスト
一般的なテストの流れ
ここの話
ユニットテストの必要性
? こんな経験ありませんか?
ユニットテストの必要性
? こんな経験ありませんか?
A:「あれ?この処理がエラーで停止するよ?」
ユニットテストの必要性
? こんな経験ありませんか?
A:「あれ?この処理がエラーで停止するよ?」
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
ユニットテストの必要性
? こんな経験ありませんか?
A:「あれ?この処理がエラーで停止するよ?」
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
ユニットテストの必要性
? こんな経験ありませんか?
A:「あれ?この処理がエラーで停止するよ?」
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
A:「そのときこの処理のテストしたの?」
ユニットテストの必要性
? こんな経験ありませんか?
A:「あれ?この処理がエラーで停止するよ?」
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
A:「そのときこの処理のテストしたの?」
C:「してないですね。1行しか直してないですし」
ユニットテストの必要性
? こんな経験ありませんか?
A:「あれ?この処理がエラーで停止するよ?」
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
A:「そのときこの処理のテストしたの?」
C:「してないですね。1行しか直してないですし」
A:「え?」 叠:「え?」
ユニットテストの必要性
? こんな経験ありませんか?
A:「あれ?この処理がエラーで停止するよ?」
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
A:「そのときこの処理のテストしたの?」
C:「してないですね。1行しか直してないですし」
A:「え?」 叠:「え?」
C:「???え??」
ユニットテストの必要性
? 問題点1:過去の実績は現在においては不要
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
開発中のプロジェクトにおいて
過去にパスしたテストが
現在もパスするとは限らない
ユニットテストの必要性
? 問題点1:過去の実績は現在においては不要
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
開発中のプロジェクトにおいて
過去にパスしたテストが
現在もパスするとは限らない
テストするためには
開発が終わっていないと
着手できない
ユニットテストの必要性
? 問題点1:過去の実績は現在においては不要
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
開発中のプロジェクトにおいて
過去にパスしたテストが
現在もパスするとは限らない
テストするためには
開発が終わっていないと
着手できない
開発が終わりテストをパスした
コードは不具合等がない限り
修正してはならない
ユニットテストの必要性
? 問題点1:過去の実績は現在においては不要
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
開発中のプロジェクトにおいて
過去にパスしたテストが
現在もパスするとは限らない
テストするためには
開発が終わっていないと
着手できない
開発が終わりテストをパスした
コードは不具合等がない限り
修正してはならない
理想的とは思えない汚コードの
修正は許されず、担当者が変
わるとメンテが不可能になる
余談???
ユニットテストの必要性
? 問題点1:過去の実績は現在においては不要の解決策
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
開発中のプロジェクトにおいて
過去にパスしたテストが
現在もパスするとは限らない
パターンを網羅した
テスト用のコードを書く
if ( 処理(パターンAのデータ) != パターンAの正常結果 ) {
エラー
}
if ( 処理(パターンBのデータ) != パターンBの正常結果 ) {
エラー
}
if ( 処理(パターンAのデータ) != パターンAの正常結果 ) {
エラー
}
if ( 処理(パターンBのデータ) != パターンBの正常結果 ) {
エラー
}
ユニットテストの必要性
? 問題点1:過去の実績は現在においては不要の解決策
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
テストするためには
開発が終わっていないと
着手できない
テスト用のコードがあれば
いつでもテストが出来る
ユニットテストの必要性
? 問題点1:過去の実績は現在においては不要の解決策
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
開発が終わりテストをパスした
コードは不具合等がない限り
修正してはならない
テスト用のコードがあれば
コードを直しても結果が
変わらないことを担保できる
if ( 処理(パターンAのデータ) != パターンAの正常結果 ) {
エラー
}
if ( 処理(パターンBのデータ) != パターンBの正常結果 ) {
エラー
}
if ( 処理(パターンAのデータ) != パターンAの正常結果 ) {
エラー
}
if ( 処理(パターンBのデータ) != パターンBの正常結果 ) {
エラー
ユニットテストの必要性
? 問題点1:過去の実績は現在においては不要の解決策
B:「そんなはずはないですよ。テストしましたから」(1ヶ月前に)
C:「あ、共通処理直しました」 (先週)
理想的とは思えない汚コードの
修正は許されず、担当者が変
わるとメンテが不可能になる
余談???
結果が変わらないことが担保で
きているので、常に理想的で
きれいなコードを保てる
ユニットテストの必要性
? 問題点2:修正の影響範囲の判定は職人技
A:「そのときこの処理のテストしたの?」
C:「してないですね。1行しか直してないですし」
修正量に応じて
テストの要/不要が
変わることはない
ユニットテストの必要性
? 問題点2:修正の影響範囲の判定は職人技
A:「そのときこの処理のテストしたの?」
C:「してないですね。1行しか直してないですし」
修正量に応じて
テストの要/不要が
変わることはない
修正範囲の影響は呼出階層
等を参照して判断するが、
基本的に技術者のスキルによる
ユニットテストの必要性
? 問題点2:修正の影響範囲の判定は職人技
A:「そのときこの処理のテストしたの?」
C:「してないですね。1行しか直してないですし」
修正量に応じて
テストの要/不要が
変わることはない
修正範囲の影響は呼出階層
等を参照して判断するが、
基本的に技術者のスキルによる
全てのテストをやり直せば
職人技の判断が不要になるが
それには時間と人手がかかる
ユニットテストの必要性
? 問題点2:修正の影響範囲の判定は職人技
A:「そのときこの処理のテストしたの?」
C:「してないですね。1行しか直してないですし」
修正量に応じて
テストの要/不要が
変わることはない
修正範囲の影響は呼出階層
等を参照して判断するが、
基本的に技術者のスキルによる
全てのテストをやり直せば
職人技の判断が不要になるが
それには時間と人手がかかる
担当者によってテストの精度に
バラツキが出て、リスク回避の為
修正を避けるようになる
ユニットテストの必要性
? 問題点2:修正の影響範囲の判定は職人技の解決策
A:「そのときこの処理のテストしたの?」
C:「してないですね。1行しか直してないですし」
修正量に応じて
テストの要/不要が
変わることはない
修正範囲の影響は呼出階層
等を参照して判断するが、
基本的に技術者のスキルによる
全てのテストをやり直せば
職人技の判断が不要になるが
それには時間と人手がかかる
担当者によってテストの精度に
バラツキが出て、リスク回避の為
修正を避けるようになる
テストを書けば
回避できる
ユニットテストの適用分野
? 適用に向く分野
?共通処理プログラム
?パターンが多い処理プログラム
?ビジネスロジック
?データ更新/取得処理プログラム
?リリースを頻繁に繰り返すプロダクト
などなど???
? 適用に向かない分野
?画面の見た目(パーツの配置)
?画面の情報を更新する処理(スプレッドの更新など)
?一度リリースしたら終了するプロダクト
あくまで例として???
ユニットテストの適用分野
? 継続的インテグレーション(CI:continuous integration)
開発者 ソース管理
CIツールステージングサーバー
1.コミット
2.pull
6.結果の通知
3.ビルド
4.ユニットテストの実施
5.デプロイ
ユニットテストをするためのプロダクト
? 以下の様な代表的なプロダクトが存在する
プロダクト 説明
JUnit Java用のユニットテストのパイオニア
NUnit JUnitをベースにした .NET用のプロダクト
Visual Studio
Unit Testing Framework
Visual Studio付属のプロダクト
DbUnit データベース操作に特化したJava用のプロダクト
以降ではJUnitを使用して、
Springで書いたJavaソースの
ユニットテストを実施する
Springでのユニットテスト
? どこでなにをテストするかがポイント
どこ なにを
Controller(画面制御) 全ての入力項目に対して、全てのパターンの
入力値を与えた妥当性(バリデート)チェック
画面遷移
Service(ビジネスロジック) パターンを網羅したロジックのチェック
Repository(データの入出力) データ取得の確認
データ保存の確認
見た目の確認や負荷テスト等は
JUnitではテストしない
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を起動するクラスを記述
テストの事前処理
テストの事後処理
Resopitoryのユニットテスト
? テスト対象のRepositoryを宣言
/** ProductRepositoryのUT */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = App.class)
public class ProductRepositoryTest {
@Autowired
private ProductRepository productRepository;
テスト対象のRepositoryを
コンテナから取得
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);
}
テスト用データを
事前処理で用意する
(これ自体が保存のテストと
言えなくもない)
Resopitoryのユニットテスト
? テストの実行
/** findOneメソッドのテスト */
@Test
public void findOneメソッドのテスト( ) {
Product product = productRepository.findOne(1);
assertEquals ( product.productName, "ガム“ );
}
テスト用メソッドだと宣言する
事前処理で用意した
データを取得する
評価メソッドで結果を評価する
product.productNameが
「ガム」ではない場合、
例外が発生しテスト失敗となる
※あくまでサンプルとして既定の処理を呼び出している。
本来は自身で作成した処理を呼んでテストする。
Resopitoryのユニットテスト
? 代表的な評価メソッド
メソッド 説明
assertEquals ( A , B ) A と B が同じ値か評価する
assertTrue( A )
assertFalse( A )
A が true か false か評価する
assertNull( A )
assertNotNull( A )
A が null か nullではない か評価する
fail( ) 強制的にエラーとする
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を起動するクラスを記述
テストの事前処理
テストの事後処理
Serviceのユニットテスト
? テスト対象のServiceを宣言
/** ProductServiceのUT */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = App.class)
public class ProductServiceTest {
@Autowired
private ProductService productService ;
テスト対象のServiceを
コンテナから取得
Serviceのユニットテスト
? 事前準備
/** 事前処理 */
@Before
public void 事前処理() throws Exception {
}
何かあれば記述する
Serviceのユニットテスト
? テストの実行
/** プロダクト一覧を取得できるかのテスト */
@Test
public void プロダクト一覧を取得できるかのテスト() {
List<Product> list = productService.getProductList();
}
テスト用メソッドだと宣言する
テスト対象の
ビジネスロジックを呼ぶ
評価メソッドで結果を評価する
(このサンプルでは未実装)
リストの件数や内容を評価する
コードを記述する
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を起動するクラスを記述
テストの事前処理
テストの事後処理
Controllerのユニットテスト(Validate)
? テスト用のバリデータを宣言
/** ProductFormのUT */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = App.class)
public class ProductFormTest {
/** バリデータ */
private Validator validator;
妥当性検証を実行する
バリデータを宣言
Controllerのユニットテスト(Validate)
? 事前準備
/** 事前処理 */
@Before
public void 事前処理() throws Exception {
validator = Validation.buildDefaultValidatorFactory( )
.getValidator( );
}
バリデータを初期化
Controllerのユニットテスト(Validate)
? テスト対象のフォーム
/** 画面の値を保持するForm */
public class ProductForm {
@NotNull
private Integer productCode;
@NotNull
@Length(max=10)
private String productName;
@NotNull
private Integer price;
入力必須
入力必須
最大文字数は10文字まで
入力必須
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文字でエラーになるはず
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 かチェック
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を起動するクラスを記述
テストの事前処理
テストの事後処理
Controllerのユニットテスト(画面遷移)
? テスト用のコンテキストとMVCのモックを宣言
/** ProductControllerのUT */
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = App.class)
public class ProductControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
アプリケーションの設定等々を
管理するコンテキスト
リクエストとレスポンスと、
それに付随する情報の
モックオブジェクト
Controllerのユニットテスト(画面遷移)
? 事前準備
/** 事前処理 */
@Before
public void 事前処理() throws Exception {
mockMvc = webAppContextSetup(wac).build();
}
モックを初期化
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(フォームの内容)に
エラー情報がないか評価する
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メソッドでアクセスする
入力画面で入力した値として
各種パラメータを付加する
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(フォームの内容)に
エラー情報がないか評価する
まとめ
? ユニットテストで単体テストを補完する
→ 画面を開いて境界値テスト等するより向いている
? ユニットテストを過信しない
→ ユニットテストをすれば単体テストをしなくて良いわけではない
? ユニットテストでテストのコストは下がらない
→ コードを書くので画面を開いてテストするよりコストはかかる
→ 時にはテストコードのテストも必要
? テストをするのが目的で、テストを書くのが目的ではない
→ コーディングが楽しいからと言って、目的を失ってはいけない
→ テスト中毒は程々に
? 本体のコードを修正したらテストコードを直すのはマスト
→ テストが通らないテストコードはメンテナンスする意識を一気に失う
→ そのためテストコードのメンテナンスは怠らない(ビルドエラーは論外)
まとめ
? 参考
■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%E
3%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_2
013.html

More Related Content

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