Javaでファイル操作(nio2)のユニットテストを書く

Javaでファイル操作をするときに、nio2とjimfsを使えばいい感じにファイル操作のユニットテストが書けることがわかったのでメモ

nio2
Javaファイル関連メモ2(Hishidama's Java Files Memo)

jimfs
Maven Repository: com.google.jimfs » jimfs
GitHub - google/jimfs: An in-memory file system for Java 7+

nio2はファイル操作を抽象化しており、zipやFTP先のサーバなども仮想的なファイルシステムとして利用できる
そのため、オンメモリな仮想ファイルシステムで差し替えることができれば、ローカルのファイルシステムを操作するコードも、
ローカルファイルを汚さずにテストすることができる

例:/rootdirectory/output/result.out ファイルに"Hello nio2"と出力するコード

プロダクトコード

public void method() {
    Path outputDirectory = Paths.get("/rootdirectory/output");
    if (!Files.exists(outputDirectory)) {
        Files.createDirectories(outputDirectory);
    }

    Files.write(outputDirectory.resolve("result.out"), "Hello nio2".getBytes());
}

テストコード
JMockitでモックしてファイルシステムを差し替えている

@Mocked
Paths paths;

@Test
public void 中間ディレクトリがなければ作ってファイルを作成する() throws IOException {

    try(FileSystem dummyFS = Jimfs.newFileSystem(Configuration.unix())) {

        // JMockitでPaths.getでファイルシステムを差し替える
        new NonStrictExpectations(){{
            Paths.get("/rootdirectory/output");
            result = dummyFS.getPath("/rootdirectory/output");
        }};
            
        testTarget.method();
        
        // 差し替えられたファイルシステムにファイルが作られているかアサーションする
        Path createdFilePath = dummyFS.getPath("/rootdirectory/output/result.out");
        assertThat(Files.exists(createdFilePath), is(true));
        assertThat(new String(Files.readAllBytes(createdFilePath)), is("Hello nio2"));
    }
}

以上

自前でnewしたオブジェクトにSpringのBeanをinjectする方法

タイトルの通りです。
トランザクションスクリプトではなくドメインモデル的に作ろうと思うと、ドメインオブジェクトの中からSpringコンテキスト配下のBeanにアクセスしたいケースが出てきます。
こうするとできるよ、というのがわかったので記録しておきます

AutowireCapableBeanFactory の autowireBeanを通せばいいようです

class Entity {
  @Autowired
  private SomeService someService;
}

@Component
class EntityFactory {
  @Autowired
  private AutowireCapableBeanFactory beanFactory;
  
  Entity create() {
    Entity entity = new Entity();
    beanFactory.autowireBean(entity);
    return entity;
  } 
}

chef事始め 依存関係の解決

前回の続き

cookbook・recipe間の依存関係

chefでサーバに適用するcookbookには、Chef Supermarketからダウンロードするコミュニティcookbookと自作cookbookとがあります。
ここを見ると、Environment cookbookをエントリポイントにして、wrapper cookbook→コミュニティcookbookと呼び出していくのが推奨されるやり方のようです。
ただ、具体的なやり方がよくわからなかったので悩みました。
自分なりに得た結論を以下に書いておきます。

なお、chef-serverを使うことは想定していません。
かといってchef-soloは今後開発が停止するようなのでこれも使用していません。
chef-zeroで実行することとします。
また、依存関係の解決には、Berkshelfを使用します。

例として、開発環境用にPostgreSQLApacheをインストールするとします。
cookbookとしては、以下の3つを用意します。

pj-dev (開発環境用)
pj-postgresql (対象プロジェクトでのPostgreSQL設定用)

自身で作成し、バージョン管理対象とするディレクトリ・ファイルは、
以下のような構成となります。

  • カレントディレクトリ

 - site-cookbooks
  - pj-dev
   - recipes
    - default.rb
   - Berksfile
   - metadata.rb
  - pj-postgresql
   - recipes
    - default.rb
   - Berksfile
   - metadata.rb

なお、chefDKの以下のコマンドをつかうと、必要なファイルをごそっと
作ってくれるので大変便利です。

chef generate cookbook pj-dev

上記で生成したcookbook達を上のディレクトリ構成に収めたら、
実際の処理を書いていきます。

まずは、各Berksfile から見ていきます。
Berksfileはberkshelfの設定ファイルです。
これを開くと、以下のようになっているかと思います。

source "https://supermarket.getchef.com"

metadata

1行目のsourceは、このサイトからコミュニティクックブックを落としてね、という意味。
3行目は、依存関係についてはmetadataを見てね、という意味になります。

というわけで次にmetadata.rbを見ます。
これを開くとcookbook名やらバージョン番号、ライセンスなど、自動生成された値が出てきます。
この辺は適宜書き換えてください。

依存関係は、このファイルの末尾に以下のように記載します。
site-cookbook/pj-postgresql/metadata.rb の場合

…
depends  'postgresql', '~> 3.4.14'

postgresql クックブックのv 3.4.14に依存しまっせという意味です

同じように、pj-dev クックブックのmetadata.rbにも次のように書きます。

…
depends  'pj-postgresql'

pj-postgresql クックブックに依存しまっせという意味です
ところがこれだけではberkshelfがpj-postgresqlをopscodeのサイトから探そうと
してしまうので、pj-postgresqlの場所を別途指定してあげる必要があります。

そのため、pj-devクックブックのBerksfileに以下を追記します。

cookbook 'pj-postgresql', path: '../pj-postgresql'

これで、metadata内にあるpj-postgresqlの場所が解決できます。
ほかに場所の指定方法として、gitリポジトリgithubも指定できます。

cookbook "mysql", git: "https://github.com/opscode-cookbooks/mysql.git", branch: "foodcritic"
cookbook "artifact", github: "RiotGames/artifact-cookbook", tag: "0.9.8"

設定が完了したところでberksfileを実行します。

berks vendor cookbooks -b site-cookbooks/pj-dev/Berksfile

berks vendor [PATH]はPATH以下にクックブックを展開する、というコマンドです。
-bオプションは実行するBerksfileのパスを指定します。

上記の例でこれを実行すると、cookbooksディレクトリに、
pj-dev、pj-postgresqlpostgresql、ならびにpostgresqlが依存するcookbook群が
ダウンロード&展開されます。

最終的にサーバで実行するのは、この展開されたcookbooksフォルダ以下のクックブックになります。
cookbooks以下はberks vendorされるたびにまるっと上書きされてしまうので、
クックブックの編集はsite-cookbooks以下で行い、その後berks vendorして実行という流れを忘れないようにしてください。

では、また次回

Chef事始め インストール

最近chefを始めました。
詰まったところがいくつかあったので書いておくことにします。

なお、Server-Client構成はやっていません。
chef-soloも将来的になくなるそうなので、chef-zeroでローカルモードで実行しています。
また、chefを実行するnodeはCentOS6.6です。

インストール

いきなりインストールでハマりました。
chefはruby1.9.3以上が必要なんですが、yumでは1.8.7しかインストールできないようでまず困りました。
rubyenvというツールでバージョン指定してインストールできるみたいなんですが、
いろいろ試してもうまくいかず。。。(自分がrubyに詳しくないのが問題なんですが)
で、途方にくれていたところ、chefDKというツール(しかもchef公式)の存在を知り、
試してみたところ、以下のコマンド一つであっさりインストールできました。

sudo rpm -ivh https://opscode-omnibus-packages.s3.amazonaws.com/el/6/x86_64/chefdk-0.3.6-1.x86_64.rpm

ChefDK公式

追記:chefdk0.4.0が出たようです。
今後もバージョンは上がっていくと思いますのでバージョン番号のところは適宜読み替えてください

これでchefと周辺ツール(BerkshelfやTestKitchenなど)を使うことができます。
rubyやgemやbundlerやなんやかんやで悩む必要がないのでぜひ。

長くなりそうなので次回に続く

テーマ決めました

今後勉強していくテーマを決めました。
簡単なウェブアプリを色々な言語、FWで作るです。

宣言することでやる気になろうという作戦です。

今のところ考えている環境は以下の通り。
SpringBoot
JavaEE
上記2つをRESTにしてJS-MVC(AngularJS)の組み合わせ
Groovy Grails
.netC#
Scala
NodeJS
cakePHP
ruby on rails

その中でTDDやアジャイル・CIツール、不変インフラなど興味のある分野に少しづつ触れていこう。

頑張れ俺

SAStrutsでFormのValidationのネスト

今仕事でSeasar2 with SAStrutsを使っているんですが、そこで不便に思ったのが、
1. Formに配列の要素があると各中身のValidationができない!
2. 入力情報の一部をひとまとめにして別Formとして親Formの中の一属性にした場合、その中身のValidationができない。
3. 1, 2の複合技(子Formの配列)

1,2,3ができるようにオレオレカスタマイザを作ってみたので、公開してみます。

yosshio0426/NestValidation · GitHub

もし同じお悩みを抱えている人がいたら、ちょっと見てみてください。
細かくテストしているわけではないので、使用の際はちゃんと中身を見て理解してから使ってみてください。
ちなみに3階層目までは保証しません。

Spring Boot お勉強 その4 ~自前の設定値の定義~

前回、application.propertiesにSpring Bootの設定値を書く方法をご紹介しました。
となると次に気になるのは、自分のアプリケーション内で設定値を定義したい場合はどうすればいいのか、という点ですね。
例えば、連携先外部システムの接続先など。


まずは、設定値を格納するクラスを作成します。

@Component
@ConfigurationProperties(prefix = "myapp", ignoreUnknownFields = true)
public class MyappProperties {
	
	private final OtherSystem otherSystem = new OtherSystem();
	
	public Push getOtherSystem() {
		return otherSystem;
	}

	public static class OtherSystem {

		private String host = "localhost";
		private String token = "qwety";
		
		// getter,setterは省略
	}
}

こんな感じです。
外部システム接続用のホスト名とトークンを保持するクラスです。
ちなみに"localhost"、"qwerty"の部分は設定のデフォルト値となります。
application.propertiesに該当の設定値が規定されないとこれが有効になります。


次にapplication.propertiesに次のように記載します。

myapp.otherSystem.host=foo.co.jp
myapp.otherSystem.host=bar



設定のキーの先頭、myappの部分は、@ConfigurationPropertiesのprefixでマッチングされます。
それ以下の部分は、該当クラスのインスタンス変数を辿るように値がセットされます。
(まあ、見れば何となくお分かりかと思いますが…)


値の利用は次のように行います。

public class OtherSystemCallService{
	
	@Autowired
	private MyappProperties properties;
	
	public void method() {
		
		connect(properties.getOtherSystem().getHost(), properties.getOtherSystem().getToken())
	}
}

設定値格納クラスに@Componentをつけているので、@Autowiredにより他のBeanにインジェクションされます。


ぜひ使ってみてください。
ではまた。

追記

値1個だけ取得する場合には以下のような書き方でbeanにinjectすることもできます。

@Value("${myapp.otherSystem.host}")
private String otherSystemHost;