私が勤めているヌーラボのプロジェクト管理サービス Backlog が本日バージョンアップリリースされました。
プロジェクト管理ツールには様々なものがあります。自分たちで構築して管理すればよいのですが、構築が難しい、管理が面倒、バックアップ、様々な問題があり大変です。
Backlog では面倒な部分をサポートしてくれます。タスク管理、Wiki 、ファイル管理、バージョン管理、ガントチャート、様々な機能があります。また絵文字を使ったコミュニケーションツールもあります。メールでなくタスクのコメントのやりとりを、絵文字を使うことで堅い文面が柔らかくなります。
勤めて 1 ヶ月なのですが、私含め社員全員が Backlog を使ってタスク管理をしています。飲み会もタスクで管理して、文中には絵文字を使って上下関係なくコミュニケーションが取れています(はずです!)
また社員自身が使っていることで、使いづらいところや不具合っぽいものは Backlog で報告され、適時修正が行われています。私もおかしいかなと思ったので使い始めたばかりだったのですが、とりあえずタスクに登録してみたところ対応してもらいました:)
Backlog 開発者(および弊社社員)は使いづらいところや不具合っぽいようなものが残っていることを嫌う人たちばかりです。そして利用者からのフィードバックを大事にしています。ブログや Twitter 、または直接フィードバックすると開発者は喜んでくれます。
最後に開発、メンテナンスはこれからも行われ続けていきますので、安心して 30 日間フリートライアルを試用してもらい、検討して頂ければと思います。
よろしくお願いします。
input 要素の入力フォントサイズが小さいと、 input 要素にフォーカスした後 iPhone Safari が気を利かしてズームインしてくれます。なんですが、 input 要素からフォーカスが外れてもズームアウトしてくれないので
input.text {
-webkit-text-size-adjust: 200%;
}
iPhone に対応するサイトをつくる場合 input 要素のサイズを調整しておくことで、 ユーザーがズームアウトのワンアクションをする必要が無くなりいい感じっぽいです。
GitHub でリポジトリ URL をコピーするものに clippy というライブラリが使われていました。 静的な文字列をそのまま渡すシンプルなもので、 MIT ライセンスの元 GitHub で公開されていました。個人的に任意のタイミングでコピーできるものが欲しかったので javascript と連携できるものをつくってみました。

http://github.com/ussy/clippy/tree/master
http://www.pshared.net/demo/clippy/
http://www.pshared.net/pub/flash/clippy/clippy.zip
動作するものとしては clippy.zip にある swf と HTML ページを作成すればいいです。
まず javascript で返す文字列を関数で用意します。
<script type="text/javascript">
function getKeyword() {
return document.getElementById("keyword").value;
}
</script>
<input id="keyword" type="text" />
それから FlashVars の call に先ほど記述した javascript の関数名を書きます。
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
width="110"
height="14"
id="clippy">
<param name="movie" value="clippy.swf"/>
<param name="allowScriptAccess" value="always" />
<param name="quality" value="high" />
<param name="scale" value="noscale" />
<param name="FlashVars" value="call=getKeyword">
<param name="bgcolor" value="#ffffff">
<embed src="clippy.swf"
width="110"
height="14"
name="clippy"
quality="high"
allowScriptAccess="always"
type="application/x-shockwave-flash"
pluginspage="http://www.macromedia.com/go/getflashplayer"
FlashVars="call=getKeyword"
bgcolor="#ffffff"
/>
</object>
あとはローカルでなく WEB サーバー上に html と clippy.swf をアップロードして、 ボタンを Click すると Flash が JavaScript を呼び出し、 JavaScript が返した文字列がクリップボードに入ります。
Seasar2 でテストを書き始めて少し経ちはじめましたが、 S2JUnit4 は個人的に未開拓だったので使ってみました。
使ってみてメリットと感じたものは
これまでは super class のメソッドで呼び出せていたものが、 static import しないとコード量が増えてしまうのがデメリットぐらいでしょうか。
まだまだ業務でも S2TestCase を使って書いている数は少ないのですが、メソッド名に Tx をつけ忘れて実行して orz となることもありました。(癖的なものなんでしょうが)自動的にロールバックしてくれるだけで考える必要もなくなり、開発者の負担が減ります。それから規約で自動的に期待値のファイルを読み込んでくれる。クラス単位、メソッド単位でテストデータの読み込みを切り替えられるのもいいですね。
以上感想でした。
後は適当に書いたものを貼り付けておきます。 Web アプリケーションの場合は SimpleInternalTestContext でなく InternalTestContextImpl にしてあげる必要があります。(jtaEnabled もいらない)
s2junit4.dicon は下みたいな感じです。 src/test/resources 直下に置きました。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd"> <components namespace="s2junit4"> <include path="app.dicon" /> <component name="context" class="org.seasar.framework.unit.impl.SimpleInternalTestContext"> <property name="jtaEnabled">true</property> <property name="preparationType">@org.seasar.framework.unit.PreparationType@ALL_REPLACE</property> </component> <component class="org.seasar.framework.unit.impl.SimpleDataAccessor" /> <component class="org.seasar.framework.unit.impl.ConfigFileIncluderImpl"> <initMethod name="addConfigFile"> <arg>context.testClassShortName + ".dicon"</arg> </initMethod> </component> <component class="org.seasar.framework.unit.impl.TestDataPreparerImpl"> <initMethod name="addTestDataXlsPath"> <arg> context.testClassShortName + "_" + context.testMethodName + ".xls" </arg> </initMethod> <initMethod name="addTestDataXlsPath"> <arg>context.testClassShortName + ".xls"</arg> </initMethod> </component> <component class="org.seasar.framework.unit.impl.ExpectedDataReaderImpl"> <initMethod name="addExpectedDataXlsPath"> <arg> context.testClassShortName + "_" + context.testMethodName + "_Expected" + ".xls" </arg> </initMethod> </component> </components>
サンプルから変更したものとして app.dicon を include させました。それから EJB3 は使っていないので SimpleInternalTestContext を使っています。ただ jtaEnabled が無効になっていて、テスト時にデータベースの読み込みを開始してくれないので true にします。それから 1 メソッドおきに readXlsAllReplaceDb を実行したいので preparationType にも ALL_REPLACE を指定します。
このあたりはドキュメントに書いてあります。
Seasar - DI Container with AOP -
これで src/test/java/example/service/impl 以下のサービスクラス、メソッドと、 src/test/resources/example/service/impl 以下の Excel ファイルが結びつけられる寸法になるわけです。(この例では UserServiceImplTest.xls, UserServiceImplTest_testInsert_Expected.xls を用意してデータを書いておきます)
実際のテストコードはこんな感じですっきりしています。
package example.service.impl;
import static org.seasar.framework.unit.S2Assert.assertEquals;
import org.junit.runner.RunWith;
import org.seasar.framework.unit.DataAccessor;
import org.seasar.framework.unit.Seasar2;
import org.seasar.framework.unit.TestContext;
import example.entity.Account;
import example.service.AccountService;
@RunWith(Seasar2.class)
public class UserServiceImplTest {
private DataAccessor accessor;
private TestContext ctx;
private UserService userService;
@Test
public void testInsert() {
User user = new User();
user.setName("ussy");
user.setPassword("ussy");
user.setMailAddress("ussy@example.com");
userService.insert(user);
assertEquals(ctx.getExpected().getTable("user"), accessor.readDbByTable("user"));
}
}
今回はテーブルの中身が完全に思ったとおりになっていることを確認するために、 DataTable の比較を行っています。ただこれだと ID が自動採番によってテストを行うたびに変わってしまうので、考慮しないといけないなあという感じです。
[追記]
Web アプリケーションのテストを行う場合は SimpleInternalTestContext でなく InternalTestContextImpl にしてあげる必要があります。 ComponentDeployerFactory.Provider の createRequestComponentDeployer で UnsupportedOperationException が発生します。
EJB3 を使わない場合は pom.xml の scope をいじっておくとよいです。
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-ejb_3.0_spec</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
小一時間悩んだのですが、標準設定からいじった自分への罰ですね。でもこんな失敗から少しずつ内部の仕組みが分かっていくのも楽しいです。(忙しくなければ)
Cubby で開発しているときに発生しました。
org.seasar.cubby.routing.RoutingException: アクション URI の正規表現が重複しています
Action をパッケージをまたいで移動させたときに発生します。移動前の class ファイルがキャッシュとして残っていることが原因みたいです。 WTP を使っていたのでいったん clean して再起動すると解決しました。
interceptor はパッケージで対象にかけることが多い一方、 Action はパッケージを移動しても URL には影響がないので interceptor と連携させたいときに相性がバッチリです。
適当なタグクラスをつくったところで WEB-INF/classes/META-INF にタグファイルの定義を記述した tld ファイルを突っ込むと、 mayaa が起動時にカスタムタグの読み込みをしてくれます。
<?xml version="1.0" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<tlib-version>1.0</tlib-version>
<short-name>tags</short-name>
<uri>http://example.com/tags</uri>
<tag>
<name>item</name>
<tag-class>com.example.servlet.tags.ItemTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
mayaa ファイルの名前空間を定義するところに tld ファイルで定義した URL とタグライブラリ名とタグ名をそれぞれ指定すれば使えます。
<?xml version="1.0" encoding="UTF-8"?>
<m:mayaa xmlns:m="http://mayaa.seasar.org"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:tags="http://example.com/tags">
<tags:item m:id="item" name="${item.name}" />
</m:mayaa>
WEB アプリケーションってお決まりパターンがあって、 10 個とかつくると飽きるのか分かりませんが、あんまりつくっていなかったので今は HTTP プロトコルでブラウザと対決するのが面白いです。
それとは別にスケールアウトとかキャッシュとか、より運用(インフラ)を考えたアプリケーションをつくれるようにこれ買ったんですけど、他にも読む雑誌がたくさんあってまいっています。パソコンの前に座れば WEB ページをみていて最近本の消化がまったく進んでいません。
話がずれたのですが、実際にサービスを運用している中の話を聞けるのは開発者にとってすごく嬉しいです。それに 3000 円切っていて安いです。