ひよっこ。

I want to…

Posts Tagged ‘test’

DBUnitでテストを書いてたらPotential problem found(再修正)

Posted by hikaruworld : 2011 9月 28

DBUnitでテストを書いてたらPotential problem foundの修正が上手くいったと
勘違いしてしまったので再度修正。
ここによると、毎回インスタンスが生成されるのでOverrideしろとのこと。

というわけで、再度修正。

    import org.dbunit.database.IDatabaseConnection;
    import org.dbunit.DatabaseUnitException;
    import java.sql.Connection;
    import org.dbunit.database.DatabaseConnection;
    import org.dbunit.database.DatabaseConfig;
    import org.dbunit.ext.postgresql.PostgresqlDataTypeFactory;

    //....

    /**
     * Connection情報を取得する
     * @return 取得されたDatabaseConnection情報
     * @throws ClassNotFoundException {@link ClassNotFoundException}
     * @throws SQLException {@link SQLException}
     * @throws DatabaseUnitException {@link DatabaseUnitException}
     */
    private IDatabaseConnection getConnection() throws DatabaseUnitException {
    	 // SpringFrameworkでsqlSessionFactoryを注入済み
        Connection conn = this.sqlSessionFactory.openSession().getConnection();
        return new DatabaseConnection(conn, this.schemaName) {
            @Override
            public DatabaseConfig getConfig() {
                DatabaseConfig config =  super.getConfig();
                config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new PostgresqlDataTypeFactory());

                return config;
            }
        };
    }
広告

Posted in program | タグ: , , , | Leave a Comment »

Selenium2のInternetExplorerDriverでエラーになった

Posted by hikaruworld : 2011 7月 26

Selenium2が便利と聞いて試してみたら、InternetExplorerDriverが起動しなくてハマったのでメモしておきます。

取りあえず依存関係をpom.xmlに追加して、以下がテストメソッド(基本チュートリアルのコピペ)になります。

@Test
public void testname() throws Exception {
    WebDriver driver = new InternetExplorerDriver(/*ieCapabilities */);

    driver.get("http://www.google.com");
    WebElement element = driver.findElement(By.name("q"));
    element.sendKeys("Cheese!");

    element.submit();

    driver.quit();
}

これを実行すると、以下のエラーが出た

org.openqa.selenium.WebDriverException: Unexpected error launching Internet Explorer. Protected Mode must be set to the same value (enabled or disabled) for all zones. (WARNING: The server did not provide any stacktrace information)
Build info: version: ‘2.0.0’, revision: ‘12817’, time: ‘2011-07-14 23:50:51’
System info: os.name: ‘Windows 7’, os.arch: ‘x86’, os.version: ‘6.1’, java.version: ‘1.6.0_20’
Driver info: driver.version: InternetExplorerDriver
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:131)
at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:105)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:402)
at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:101)
at org.openqa.selenium.ie.InternetExplorerDriver.setup(InternetExplorerDriver.java:103)
at org.openqa.selenium.ie.InternetExplorerDriver.(InternetExplorerDriver.java:61)
// 以下省略

えー、と思って取りあえずissueを検索してみたら、Issues1795を発見。
あと、SeleniumのWikiにもInternetExplorerDirverにRequired Configurationという記述があります。

つまり、IEのインターネットオプションのセキュリティ設定で全てのゾーンの保護モードを会わせろとのことのようです。
って、英語でログにそのまま書いてあるじゃんorz…
(Issues1795#comment19の添付画像を参照)

というわけで全部有効に設定して再度確認。

OKでした(-

おまけ、Issues1795#comment1にあるように設定したら、指摘されているようにクラッシュしました。残念。

DesiredCapabilities ieCapabilities = DesiredCapabilities.internetExplorer();
ieCapabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
WebDriver driver = new InternetExplorerDriver(ieCapabilities);

以上です。

Posted in program | タグ: , , , , | 1 Comment »

DBUnitでテストを書いてたらPotential problem found

Posted by hikaruworld : 2011 6月 2

やっぱりうまくいかなかったので、こちらに追記しています。

発生したのはこんなエラー。

WARN : org.dbunit.dataset.AbstractTableMetaData – Potential problem found: The configured data type factory ‘class org.dbunit.dataset.datatype.DefaultDataTypeFactory’ might cause problems with the current database ‘PostgreSQL’ (e.g. some datatypes may not be supported properly). In rare cases you might see this message because the list of supported database products is incomplete (list=[derby]). If so please request a java-class update via the forums.If you are using your own IDataTypeFactory extending DefaultDataTypeFactory, ensure that you override getValidDbProducts() to specify the supported database products. L166

なんじゃらほ、と思って調べていると、DBUnitのFAQでこんなことが書いてあることを発見。

というわけで、DatabaseConnectionから取得出来るDatabaseConfigに以下の設定を追加して解決。

DatabaseConfig config = databaseConnection.getConfig();
config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new PostgresqlDataTypeFactory());

以上です。

Posted in program | タグ: , , , | Leave a Comment »

fxug@北陸in富山をやったよ。

Posted by hikaruworld : 2011 4月 24

ずいぶん間が空いてしまいましたが、
fxug@北陸in富山をやりました。

スピーカーしてくれたwacky,@itsuki_kosen,@shoito
参加してくれた皆さん、ありがとうございます。
次は6月に金沢ですので興味ある方は是非〜。

個人的には富山で初見の人に会えたのがうれしかった!

久々に話しましたが、相変わらずぐだぐだに。
もっと練習しないとなー。

という訳で、セッション内容を張っておきます。
今回はMockitoというFlexのモッキングフレームワークについて話しています。

Posted in program | タグ: , , , , | Leave a Comment »

QuickJUnitを設定して、まず第一にやることを3つ

Posted by hikaruworld : 2011 4月 7

QuickJUnitになれてくると激しく便利です。
ところで、かゆい所に手の届くプラグインですが、もうちょっと細かい設定もしています。

BetaサイトにあるExtension周りを導入

UpdateSiteに以下を指定して、http://quick-junit.sourceforge.jp/updates/beta/
以下の辺りのライブラリを導入します。ProcessCallとか、Mockito連携とか便利です。
。。。TestContextはまだ使えてないけどorz…

  • Quick JUnit process call Extension
  • Quick JUnit Template Extension
  • Quick JUnit Test Feature
  • Quick JUnit TestContext Feature(Experimental)
  • Quick JUnit Mockito Integration Plugin
  • Quick JUnit Plugin

設定の変更

なぜか、自環境だと、Ctrl+Shift+0でデバッグしてくれないという致命的な問題があるので、
キーバインドの設定を変えておきます。
1. 一般 -> キー
2. フィルターに0を設定
3. JUnitデバッグをCtrl+Shift+-に設定

テンプレートを拡張

Mockitoやhamcrestを利用しているので、staticインポートへ変更しておきます。
デフォルトの設定だと、hamcrest側のMatcherをJUnitに内蔵されているMatcherに食われてしまいますorz…

1. Java -> エディター -> テンプレートを選択
2. テンプレートからQを選択
3. 編集をクリックしてテンプレートを以下のように変更

@${testType:newType(org.junit.Test)}
public void ${testname}() throws Exception {
	${junit:importStatic('org.junit.Assert.*')} 
    ${hamcrest:importStatic('org.hamcrest.Matchers.*')}
	${mockIto:importStatic('org.mockito.Mockito.*')}${cursor} 
}

4. テンプレートからATを選択
5. 編集をクリックしてテンプレートを以下のように変更

${junit:importStatic('org.junit.Assert.*')}assertThat(${:localVar(java.lang.Object)}${cursor},${hamcrest:importStatic('org.hamcrest.Matchers.*')}is(${expected}));

おまけというか要望

Ctrl+9でテスティングペアがない場合にテストケースを作成してくれるのはとてもうれしいのですが、
Maven構成とかでやってるとsrc/mainじゃなくてsrc/testのソースフォルダを自動で指定してくれるとめちゃうれしいです(><)

以上です。

Posted in program | タグ: , , , | Leave a Comment »

単体テストの品質分析に関する妄想(途中)

Posted by hikaruworld : 2011 4月 3

単体テストの品質分析に関して、ちょっとまじめに考えている(現在進行形)。
(普段何気なくやってる事を整理しただけなので、別にたいしたことを書いている訳ではない)
単体テストで品質分析を行う場合、
  • プロダクトコードの検証
  • テストコードの検証
の二つの視点で考えるようにしている。

プロダクトコードの検証

コードフォーマットに起因する問題分析

実施方法::CheckStyle,CPDなどを利用する(Javaの場合)
目的::読みやすいクリーンなコードを維持する事で、読み違いやミスなどを防止する。

フォーマット規約に反していないか、冗長なコードがないか、重複したコードがないかを検証する。
問題があった場合に、対象のコードに対応が必要かどうかを検討する。

プログラムコード構造に関する問題分析

実施方法::FindBugs,PMDなどを利用する。
目的::実装中の考慮漏れをコードの構成側から分析する。

構造上に潜在的に危険なコードがないか検証する。
問題があった場合は、問題の大きさを考慮して対応が必要かどうかを検討する。
ただし、これらのツールは問題の大小に応じて修正するかどうかを検討する場合が多い。

#  FindBusgとPMDを別途あげているのは、これらのコード解析方法が異なるため。
# どうせ機械的に実行する以上、いろんな観点から分析できるようにしておく。

テストコードの検証

単体テスト自体の実行のタイミングはSCMへのコミット単位となっている。
そのためテストコードの検証は開発作業の流れの中に取り込む形で考えるため、
以下のような手順で行う。

  1. TDDを用いて、プログラムのクラス、メソッド単位で、仕様が網羅されているか、
    異常系の例外ケースを考えているか考慮しながら設計(テストケース)と実装を行う。
  2. 実装後にカバレッジを確認し漏れている部分を確認する。
  3. 漏れている部分の内、構造上網羅していなければまずい部分に関してはテストケースを追加する。

# イメージ的には、ブラックボックス(1) -> ホワイトボックス(2)というイメージ。ざっと絵を書いてみた。

リグレッションが発生した場合は、以下の点を考慮しながら確認を行う。
なお、リグレッションが発生するタイミングはテストコードの実行されるタイミング=SCMコミット時である。

  1. 仕様が追加ないしは、変更されたことに起因するリグレッションかどうかを確認する。
  2. 実装した内容に問題があったことに起因するリグレッションかどうかを確認する。
  3. 1の場合は、仕様網羅性を考慮してテストケースの確認を行う。
  4. 2.の場合は、構造網羅性を考慮してテストケースの確認を行う。

仕様網羅率を目的とした検証

プログラムのメソッドレベルのI/Oレベルで、仕様を網羅する形でテストコードが実装されていることの検証を行う。
観点としては、これらの項目がテストケースとして加味されていることに観点を当てる。

  • 同値検証
  • 境界値検証
  • 異常系の例外ケース

構造網羅率を目的とした検証

カバレッジレポートを確認しながら、構造上の漏れがないことを検証する。
実行されていないプロダクトコードに関しては、必要性の有無を考慮した上で、
対応したテストコードの実装を行うか検討を行う。

  • 命令網羅率が漏れなく網羅されていること
  • 分岐網羅率が漏れなく網羅されていること

なお、構造網羅率は実装と効果に関するトレードオフを考える必要がある。
たとえばPOJOなクラスのgetter/setter、発生し得ない例外など明らかに不要なテストもあるので、
そこにパワーを使うくらいなら、プライオリティの高い複雑な共通部などに注力した方がよい。

網羅されていない部分に関して

ユニットテストでの実装可能性を考慮する。
実装内容によっては非常に困難なため、手動テスト(デバッグなどブレークポイントを利用した検証)の検討を行う。
実装可能であれば、処理が漏れている部分に関しては追加のテストを実装する。
なお、この際プロダクトコードを変更する事でテストが容易なようであれば、修正を検討する。

修正範囲に関する検証

実装が追加されたコード、削除されたプロダクトコードに関して、
テストコードの修正も適切に行われているかどうかを検証する。
基本的にはプロダクトコードよりもテストコードを先に書いている以上、基本発生しない。
但し、追加されたプロダクトコードによって仕様網羅率が変更される可能性があるため、検証を行う必要がある。

リグレッションに関する検証

テストコードの実行結果からリグレッションの有無、テストの失敗が頻発してる部分をを検証する。
テストコードの実行履歴に関しては、コミット単位で実行されるユニットテストケース履歴から検証する。
特に以下のようなリグレッションが発生した場合は焦点を当ててみる事。
  • ユニットテストの継続的な失敗が発生している場合。
  • リグレッションが発生している場合(修正したテストケース以外がリグレッションしている場合)
  • インターフェイス層を超えた形でエラーが失敗しているケース

層別の公開インターフェイスの変更に伴う検証

インターフェイスが変更されている場合は影響が多きくなるため、整合性に問題がないか参照部分の検証を行う。

おまけ

単体テストと結合テストを絡める形で網羅できればいいというのが実は本音。
品質分析をプログラミングテスト工程!の最後にやらなきゃ行けないというのはちょっと理解できない。CIのタイミングがベターだと思う。

etc…

以上です。

Posted in program | タグ: , , | Leave a Comment »

SpringSecurtiyのSecurityContextを利用したクラスのテスト方法

Posted by hikaruworld : 2011 1月 26

SpringSecurityを利用するとControllerからもこんな感じで、簡単に主体情報を取得できます。

SecurityContextHolder.getContext().getAuthentication().getPrincipal();

ただし、この実装を利用した場合、
ユニットテスト時は当然主体情報を取得できません。
認証情報がありませんので。。。

そのためテストをする場合は、
その認証情報を以下のように、
TestingAuthenticationTokenとしてSecurityContextHolderに格納してあげると良いとのこと。

// User情報は通常格納する主体情報を利用します。
// SpringSecurityを利用する場合は大体UserDetailsを継承しているかと。
User user = new User() {};

// Test用の認証Tokenに格納します。
// 第1引数に実際のユーザ情報、第2引数に資格情報を設定します。
// 後者は@Securedのテストをするような場合に必要になりますが、詳しくは次回。
Authentication authentication = new TestingAuthenticationToken(principal , null);

// SecurityContextの実装クラスをインスタンス化します。
SecurityContext context = new SecurityContextImpl();
context.setAuthentication(authentication);

// staticメソッドでSecurityContextに設定します。
SecurityContextHolder.setContext(context );

実際にテストで利用する場合は@Before辺りで読むと楽です。

参考:Tapestry 5 with Spring Security – problem with unit tests.

以上です。

Posted in program | タグ: , , , | Leave a Comment »

Spring3(というかJSR-303)でBeanValidationのテスト

Posted by hikaruworld : 2011 1月 15

Spring3(というかJSR-303)でBeanValidationのテスト

Spring3にはJSR-303のBeanValidationの機能が実装されています。
これのテストの仕方について調べていました。

対象のコード

例えば以下のようなBeanがあるとします。

public class User {
  private int userId;
  private String userName;
  
  // getter/setter
}

これに、以下のような制約を加えたいとします。

userId

  • 1以上の数字
  • 9桁以内

userName

  • 20文字以内
  • 必須項目

BeanValidation実装後の修正コード

必要なアノテーションを設定すると以下の通りになります。

public class User {
  @Min(value=1)
  @Max(value=999999999)
  private int userId;
  
  @Length(max=20)
  @NotNull
  private String userName;
  
  // getter/setter
}

テストケース

テストケースは以下のようになります。
一部のテストケースだけ抜粋しています。

// Springではなくjavax側のValidatorをimport
import javax.validation.Validator;

public class UserTest {
  /** 検証対象のクラス */
  public User user;
  
  /** Validatorの実行クラス */
  private static Validator validator;
  /** 実行結果格納用 */
  private Set<ConstraintViolation<User>> violations;
  
  /**
   * validatorのインスタンスをFactoryから取得
   */
  @BeforeClass
  public static void beforeClass() {
    validator = Validation.buildDefaultValidatorFactory().getValidator();
  }

  /** 検証対象のクラスを初期化 */
  @Before
  public void before() {
    this.user = new User();
  }

  //---------------------------------------------------------------------------------------------
  //
  // Validationのテスト
  //
  //---------------------------------------------------------------------------------------------
  
  @Test
  public void userIdの最小値の検証_0を許さない() throws Exception {
    this.user.setUserId(0);
    
    this.violations = validator.validate(this.user);
    
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("value", 1L);
    assertThat(this.violations, hasItems(
    		IsViolation.<User>hasViolation("userId", "{javax.validation.constraints.Min.message}", map ))
    );
  }
}

キモは、以下の部分で、validateを実行した結果、
エラーがあった場合にviolationsに格納されます。

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(this.user);

IsViolation.hasViolationは、
テスト結果がviolationsに格納されてテストしづらいので、
Matcher用のクラスを実装した任意のクラスです。

ちなみにIsViolationクラスは以下の通りです。

package com.wordpress.prepro.controller.utils;

import java.util.HashMap;
import java.util.Map;

import javax.validation.ConstraintViolation;
import javax.validation.Path;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeMatcher;

/**
 * アノテーションフィールドを用いたバリデーションで利用されるMatcherクラス.
 * 未テスト
 * 
 * <h1>利用方法</h1>
 * 以下のようなクラスがあった場合
 * <pre>
 * pubic class User {
 *      @Length(max = "3")
 *      private String title;
 * }
 * </pre>
 * 以下のようにユニットテスト側で実行時に利用される。
 * <pre>
 * User user = new User();
 * user.setTitle("hoge");
 * 
 * Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
 * Set&lt;ConstraintViolation&lt;User&gt;&gt; violations = validator.validate(user);
 * 
 * assertThat(violations, hasItem(IsAnnotationValidate.&lt;User&gt;hasViolation(
 *  "title", "{org.hibernate.validator.constraints.Length.message}")
 * )));
 * </pre>
 *
 * @author morikawa
 * @version 0.0.1 
 *
 * @param <T> アノテーションが付与されているBean
 */
public class IsViolation<T> extends TypeSafeMatcher<ConstraintViolation<T>> {
  
  /** 検証対象フィールド */
  private String propertyField;
  /**
  * エラーメッセージ
  * ローカライズされていないkeyを検証する
  */
  private String messageTemplate;
  /** アノテーションのフィールド検証用 */
  private Map<String, Object> annotationValue;
  
  /**
  * コンストラクタ
  * @param propertyField 検証対象フィールド
  * @param messageTemplate エラーメッセージ
  * @param annotationValue アノテーションに設定された値。ValueのObjectはkeyに依存する
  */
  public IsViolation(String propertyField, String messageTemplate, Map<String, Object> annotationValue) {
    this.propertyField = propertyField;
    this.messageTemplate = messageTemplate;
    this.annotationValue = annotationValue;
  }
  
  /**
  * {@inheritDoc}
  * @see org.hamcrest.SelfDescribing#describeTo(org.hamcrest.Description)
  */
  public void describeTo(Description description) {
    description.appendText("a annotation validate ")
        .appendText("PropertyPath:")
        .appendValue(this.propertyField)
        .appendText(" ")
        .appendText("MessageTempate:")
        .appendValue(this.messageTemplate)
        .appendText(" ")
        .appendText("annotationValue:")
        .appendValue(this.annotationValue);
  }
  
  /**
   * {@inheritDoc}
   * @see org.hamcrest.TypeSafeMatcher#matchesSafely(java.lang.Object)
   */
  @Override
  protected boolean matchesSafely(ConstraintViolation<T> item) {
    if (item == null) {
      return false;
    }
    // PropertyPath
    if (item.getPropertyPath() == null) {
      return false;
    }
    boolean msgField = false;
    for (Path.Node node : item.getPropertyPath()) {
      if (node.getName().equals(this.propertyField)) {
        msgField = true;
        break;
      }
    }
    
    // messageTemplate
    boolean msgTemp = this.messageTemplate.equals(item.getMessageTemplate());
    
    // annotation
    Map<String, Object> atrMap = item.getConstraintDescriptor().getAttributes();
  
    boolean atr = true;;
    for (Map.Entry<String, Object> entry : this.annotationValue.entrySet()) {
      if (atrMap.containsKey(entry.getKey())) {
        if (Matchers.is(entry.getValue()).matches(atrMap.get(entry.getKey()))) {
          continue;
        }
        atr = false;
        break;
      }
      atr = false;
      break;
    }
    return msgField && msgTemp && atr;
  }

  /**
   * 
   * @param <T> 検証対象のPOJO
   * @param propertyField 検証対象フィールド
   * @param messageTemplate エラーメッセージ
   * @param annotationValue アノテーションに設定された値。ValueのObjectはkeyに依存する
   * @return {@link IsViolation}のインスタンス
   * @see IsViolation
   */
  public static <T> Matcher<ConstraintViolation<T>> hasViolation(String propertyField, String messageTemplate, Map<String, Object> annotationValue) {
    return new IsViolation<T>(propertyField, messageTemplate, annotationValue);
  }

  /**
   * Annotationプロパティ検証の省略版
   * @see IsViolation#hasValidate(String, String, String, Map)
   */
  public static <T> Matcher<ConstraintViolation<T>> hasViolation(String propertyField, String messageTemplate) {
    return new IsViolation<T>(propertyField, messageTemplate, new HashMap<String, Object>());
  }	
}

以上です。

Posted in program | タグ: , , , , | Leave a Comment »

SpringFramework3 Controllerのhandlingのテスト

Posted by hikaruworld : 2010 12月 7

Spring3でMVCの@Controllerのテストを行いたい場合のやり方に関してメモしておきます。

Spring3においてControllerのテストを行う場合、
メソッド単位であれば単純にControllerをnewしてテストを行うことが出来ます。
つまり、以下のようなControllerがあった場合、

@Controller
@RequestMapping(value="/employee")
public class HomeController {

	/** とりあえずのサービス。必要に応じてMockなりStubなりを作る */
	private CommonService commonService;

	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	/** コンストラクタインジェクションでServiceを設定 */
	@Autowired(required=true)
	public HomeController(CommonService commonService) {
		this.commonService = commonService;
	}

	/**
	 * /employeeへのGETアクセスの場合に実行されるメソッド
	 */
	@RequestMapping(method=RequestMethod.GET)
	public String home() {
		this.commonService.login();
		
		return "home";
	}
	/**
	 * /employeeへのPOSTアクセスの場合に実行されるメソッド
	 */
	@RequestMapping(method=RequestMethod.POST)
	public String school() {
		return "school";
	}
}

テストは以下のように行います

@Test
public void testInner() {
	HomeController controller = new HomeController();
	String path = controller.home();
	assertThat(path, is("home"));
}

メソッドレベルの検証は上記のように非常に簡単になっているのですが、
HTTPレベルからのテストを考えると、DispatcherServletを経由したテストを行いたい場合があります。
ようするに、DispatcherServletを実行した場合に対象のControllerが起動し、適切なメソッドなりreturnなりが実行されるかの検証です。

このテストを行う場合は、ApplicationContextを@Injectした上で、
applicationContextが持っているhandler情報から、Controller情報を引くようにする必要があるようです。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
	// contextConfigLocationの読み込み
	"file:src/main/webapp/WEB-INF/spring/root-context.xml",
	// DispatcherServletの読み込み
	"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"
})
public class HomeControllerTest {

	@Inject
	private ApplicationContext applicationContext;
	
	/** 検証したいController */
	private HomeController controller;
	
	/** 各種Mock */
	private MockHttpServletRequest request;
	private MockHttpServletResponse response;
	private HandlerAdapter handlerAdapter;
	
	private CommonService commonService;
	
	@Before
	public void setup() {
		this.request = new MockHttpServletRequest();
		this.response = new MockHttpServletResponse();

//		Map<String, HandlerAdapter> map =
//				applicationContext.getBeansOfType(HandlerAdapter.class);
		this.handlerAdapter =
				applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
		this.commonService = applicationContext.getBean(CommonService.class);

		this.controller = applicationContext.getBean(HomeController.class);
		
//		this.controller = new HomeController(this.commonService);
	}
	/** Handlingの実処理 */
	private ModelAndView handle() throws Exception {
		// Map<String, HandlerMapping> map =
		//		applicationContext.getBeansOfType(HandlerMapping.class);
		// AnnotationによってHandlerMappingされた情報を取得
		HandlerMapping handlerMapping =
				this.applicationContext.getBean(DefaultAnnotationHandlerMapping.class);
		// RequestにマッチするControllerをMapping情報から取得
		// この内部にHomeControllerが含まれる。
		HandlerExecutionChain chain = handlerMapping.getHandler(this.request);

		// AOPの取得? TODO ???
		for (HandlerInterceptor interceptor : chain.getInterceptors()) {
			boolean bool = interceptor.preHandle(this.request, this.response, chain);
			if (!bool) {
				return null;
			}
		}
		// Controllerの実行を行い、結果を取得する
		return handlerAdapter.handle(this.request, this.response, chain.getHandler());
	}
	
	@Test
	public void testA() throws Exception {
		// 遷移すべきURLとHTTPメソッドを取得
		this.request.setRequestURI("/employee");
		this.request.setMethod("GET");

		// Controllerの呼び出し
		ModelAndView modelAndView = this.handle();

		// 検証処理
		assertViewName(modelAndView, "home");
	}
}

applicationContextから取得出来る、HandlerAdapterやHandlerMappingを
どれをとればよいかわからず、ちょっとはまりました。

以下、参考にしたblog。感謝。
* More on integration testing of Spring’s MVC annotation mapppings for controllers

Spring1.Xの知識がほとんどなので、便利になったもんですなというのが率直な感想です。
Controllerレベルのテストもメソッド単位でとても実行しやすくなってますね。

Posted in program | タグ: , , | Leave a Comment »

hamcrestで年月日比較がしたくなったのでDateMatcherを作ってみた

Posted by hikaruworld : 2010 10月 21

とりあえず、車輪の再発明な気がします。
どっかにきっとあると思うんだ。

以下、本題です。

hamcrestパッケージの叙述的?記法は結構気に入ってるのですが、
Date系のMatcherがあんまりなくて、今のテストだと、
年だけとか、月だけとか、日だけの検証がしたくなってきました。

hamcrestのMatcherクラスは結構簡単に拡張出来るので書いてみました。
以下のメソッドを利用して比較出来ます

// 年・月・日が正しいことを比較
assertThat(Date, isDate(Date));
// 年が正しいことを比較
assertThat(Date, isYear(int));
// 月が正しいことを比較
assertThat(Date, isMonth(int));
// 日が正しいことを比較
assertThat(Date, isDay(int));

以下ソースです。

import java.util.Calendar;
import java.util.Date;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
/**
 * Date比較用のMatcherクラス.
 * 年、月、日、あるいは年月日を対象に値の検証を行う
 * @author morikawa
 */
public class DateMatcher extends BaseMatcher<Date> {

    /** Matcher対象のオブジェクト */
    private Date entity;
    /** 出力メッセージ */
    private StringBuilder message;
    /** 検証対象フィールド */
    private DateField field;
    /** 比較フィールドの列挙型 **/    
    public static enum DateField {
        /** インスタンス自体の比較 */
        ALL {
            @Override
            public boolean is(Date base, Date target) {
                return YEAR.is(base, target) && MONTH.is(base, target) && DAY.is(base, target);
            }

            @Override
            public void addDescription(Description description, Date target) {
                description.appendValue(target);
            }
        },
        /** 年比較 */
        YEAR {
            @Override
            public boolean is(Date base, Date target) {
                Calendar baseCal = Calendar.getInstance();
                Calendar targetCal = Calendar.getInstance();
                baseCal.setTime(base);
                targetCal.setTime(target);

                return baseCal.get(Calendar.YEAR) == targetCal.get(Calendar.YEAR);
            }

            @Override
            public void addDescription(Description description, Date target) {
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(target);
                description.appendValue("YEAR::" + calendar.get(Calendar.YEAR));
            }
        },
        /** 月比較 */
        MONTH {
            @Override
            public boolean is(Date base, Date target) {
                Calendar baseCal = Calendar.getInstance();
                Calendar targetCal = Calendar.getInstance();
                baseCal.setTime(base);
                targetCal.setTime(target);

                return baseCal.get(Calendar.MONTH) == targetCal.get(Calendar.MONTH);
            }

            @Override
            public void addDescription(Description description, Date target) {
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(target);
                description.appendValue("MONTH::" + calendar.get(Calendar.MONTH));
            }
        },
        /** 日比較 */
        DAY {
            @Override
            public boolean is(Date base, Date target) {
                Calendar baseCal = Calendar.getInstance();
                Calendar targetCal = Calendar.getInstance();
                baseCal.setTime(base);
                targetCal.setTime(target);

                return baseCal.get(Calendar.DAY_OF_MONTH) == targetCal.get(Calendar.DAY_OF_MONTH);
            }

            @Override
            public void addDescription(Description description, Date target) {
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(target);
                description.appendValue("DAY_OF_MONTH::" + calendar.get(Calendar.DAY_OF_MONTH));
            }
        };
        /**
         * 値が同じであることの比較を行う。
         * @param base 比較元のDate
         * @param target 比較対象のDate
         * @return 値が同一の場合にtrue
         */
        abstract boolean is(Date base, Date target);
        /**
         * Matcherに検証対象オブジェクトの情報を設定する
         * @param description 概要
         * @param date 対象のDate
         */
        abstract void addDescription(Description description, Date target);
    }
    
    public DateMatcher(Date date, DateField field) {
        this.entity =  date;
        if (field == null) {
            this.field = DateField.ALL;
        } else {
            this.field = field;
        }
        this.message = new StringBuilder();
    }
    /**
     * 年月日レベルで比較を行う
     * @param date 比較対象のDate
     * @return 年月日比較で用いられる{@link DateMatcher}
     */
    public static DateMatcher isDate(Date date) {
        return new DateMatcher(date, DateField.ALL);
    }
    /**
     * 年比較を行う
     * @param year YYYY形式の日付
     * @return 年比較で用いられる{@link DateMatcher}
     */
    public static DateMatcher isYear(int year) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        return new DateMatcher(calendar.getTime(), DateField.YEAR);
    }
    /**
     * 月比較を行う
     * @param month 1~12までの月
     * @return 月比較を行う{@link DateMatcher}
     */
    public static DateMatcher isMonth(int month) {
        if (month > 12 || month < 1) {
            throw new IllegalArgumentException("対象外の月が設定されています");
        }
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.MONDAY, month -1);
        return new DateMatcher(calendar.getTime(), DateField.MONTH);
    }
    /**
     * 日比較を行う
     * @param day 1~31までの日
     * @return 日比較を行う{@link DateMatcher}
     */
    public static DateMatcher isDay(int day) {
        if (day > 31 || day < 1) {
            throw new IllegalArgumentException("対象外の日が設定されています");
        }
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.DAY_OF_MONTH, day);
        return new DateMatcher(calendar.getTime(), DateField.DAY);
    }

    /**
     * {@inheritDoc}
     * @see org.hamcrest.Matcher#matches(java.lang.Object)
     */
    public boolean matches(Object item) {
        Date date = (Date) item;
        if (this.entity == null ^ date == null) {
            if (this.entity == null) {
                this.message.append(" -> entity is null.");
            } else {
                this.message.append(" -> date is null.");
            }
            return false;
        }
        if (this.entity == null && date == null) {
            //TODO とりあえずどちらもnullの場合はfalseを返してメッセージを設定する
            this.message.append(" -> entity date both is null.");
            return false;
        }

        return this.field.is(this.entity, date);
    }

    /**
     * {@inheritDoc}
     * @see org.hamcrest.SelfDescribing#describeTo(org.hamcrest.Description)
     */
    public void describeTo(Description description) {
//        description.appendValue(this.entity);
        this.field.addDescription(description, this.entity);
        description.appendText(this.message.toString());
    }
}

ちょっと補足すると、DateMatcher#describeToでは、標準出力に出力する文字列を定義出来ます。
たとえばEclipseのJUnitでAssertionErrorが発生すると、以下のような出力が行われます。

java.lang.AssertionError: 
Expected: 
     got: 

description.appendValue(Object)でExpected:の値の部分に文字列が、
description.appendText(String)で上記に続く文字列が設定出来ます。

で、DateMatcherを作ったらテストクラスもあるわけで、以下の通りです。

import java.lang.reflect.Field;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;

@RunWith(Parameterized.class)
public class DateMatcherTest {

    @Parameters
    public static Collection methods() {
        return Arrays.asList(new Object[][] {
                {null},
                {DateField.ALL},
                {DateField.YEAR},
                {DateField.MONTH},
                {DateField.DAY}
        });
    }
    private DateField field;
    
    public DateMatcherTest(DateField field) {
        this.field = field;
    }

    // 初期化の検証
    @Test
    public void コンストラクタの検証() throws Exception {
        DateMatcher matcher = new DateMatcher(new Date(), field);
        DateField  field = this.<DateField>getPrivateProperty(matcher, "field");
        assertThat(field, is(field));
    }
    @Test
    public void コンストラクタの検証_null() throws Exception {
        DateMatcher matcher = new DateMatcher(new Date(), null);
        DateField  field = this.<DateField>getPrivateProperty(matcher, "field");
        assertThat(field, is(DateField.ALL));
    }
    @Test
    public void isDateによるインスタンス生成() throws Exception {
        DateMatcher matcher = DateMatcher.isDate(new Date());
        DateField  field = this.<DateField>getPrivateProperty(matcher, "field");
        assertThat(field, is(DateField.ALL));
    }
    @Test
    public void isYearによるインスタンス生成() throws Exception {
        DateMatcher matcher = DateMatcher.isYear(2010);
        DateField  field = this.<DateField>getPrivateProperty(matcher, "field");
        assertThat(field, is(DateField.YEAR));
    }
    @Test
    public void isMonthによるインスタンス生成() throws Exception {
        DateMatcher matcher = DateMatcher.isMonth(11);
        DateField  field = this.<DateField>getPrivateProperty(matcher, "field");
        assertThat(field, is(DateField.MONTH));
    }
    @Test
    public void isDayによるインスタンス生成() throws Exception {
        DateMatcher matcher = DateMatcher.isDay(10);
        DateField  field = this.<DateField>getPrivateProperty(matcher, "field");
        assertThat(field, is(DateField.DAY));
    }
    @Test(expected=IllegalArgumentException.class)
    public void isMonth利用時の境界値の検証_MIN() {
        DateMatcher matcher = DateMatcher.isMonth(0);
    }
    @Test(expected=IllegalArgumentException.class)
    public void isMonth利用時の境界値の検証_OVER() {
        DateMatcher matcher = DateMatcher.isMonth(13);
    }
    @Test(expected=IllegalArgumentException.class)
    public void isDay利用時の境界値の検証_MIN() {
        DateMatcher matcher = DateMatcher.isMonth(0);
    }
    @Test(expected=IllegalArgumentException.class)
    public void isDay利用時の境界値の検証_OVER() {
        DateMatcher matcher = DateMatcher.isMonth(32);
    }
    
    
    // Matcherの検証
    @Test
    public void isDateの検証() {
        Date d = new Date();
        Date d1 = DateUtil.getDay(d);
        assertThat(d1, DateMatcher.isDate(d));
    }
    @Test
    public void isDateの検証_false() {
        Date d = new Date();
        Date d1 = DateUtil.getDay(DateUtil.moveDay(d, 1));
        assertThat(d1, not(DateMatcher.isDate(d)));
    }
    
    @Test
    public void isYearの検証_true() throws ParseException {
        Date d = DateFormater.toDate("2010/11/12");
        assertThat(d, DateMatcher.isYear(2010));
    }

    @Test
    public void isYearの検証_false() throws ParseException {
        Date d = DateFormater.toDate("2010/11/12");
        assertThat(d, not(DateMatcher.isYear(2011)));
    }

    @Test
    public void isMonthの検証_true() throws ParseException {
        Date d = DateFormater.toDate("2010/11/12");
        assertThat(d, DateMatcher.isMonth(11));
    }

    @Test
    public void isMonthの検証_false() throws ParseException {
        Date d = DateFormater.toDate("2010/11/12");
        assertThat(d, not(DateMatcher.isMonth(10)));
    }

    @Test
    public void isDayの検証_true() throws ParseException {
        Date d = DateFormater.toDate("2010/11/12");
        assertThat(d, DateMatcher.isDay(12));
    }

    @Test
    public void isDayの検証_false() throws ParseException {
        Date d = DateFormater.toDate("2010/11/12");
        assertThat(d, not(DateMatcher.isDay(11)));
    }
    @Test
    public void null値を含む検証0() {
        DateMatcher matcher = new DateMatcher(new Date(), null);
        assertThat(matcher.matches(null), is(false));
    }
    @Test
    public void null値を含む検証1() {
        DateMatcher matcher = new DateMatcher(null, null);
        assertThat(matcher.matches(new Date()), is(false));
    }
    @Test
    public void null値を含む検証2() {
        DateMatcher matcher = new DateMatcher(null, null);
        assertThat(matcher.matches(null), is(false));
    }
    
    
    // ユーティリティ
	// privateフィールドをリフレクションで取得する。
    public static <T> T getPrivateProperty(DateMatcher matcher, String propertyName) throws Exception {
        Field field = matcher.getClass().getDeclaredField(propertyName);
        field.setAccessible(true);
        return (T) field.get(matcher);
    }
}

良ければ使ってみてください。

Posted in program | タグ: , , , | Leave a Comment »