この記事では、私がJUnitを学んでいた頃に使っていた「悪い」コーディング手法を再現し、Sensei を使って、「悪い」パターンを合意された「より良い」コーディングパターンに変換する方法を紹介します。
JUnitを学んでいた頃は、一度に多くのことを頭の中に入れておくことができませんでした。テストが機能していないときにテストをスキップする方法を常に忘れていました。
チームで作業している場合は、プルリクエストでコードレビューを行うことで、コーディングスタイルの徹底を図ることができます。また、経験豊富なプログラマーとのペアプログラミングでは、フィードバックサイクルを短縮することができます。
また、ツールを使ってプロセスを強化し、ツールが正しい行動を促すこともできます。Thoughtworks社は、同社のTechnology Radarに掲載されているSensei 、これを「ルールよりもツール」と表現しています。"チェックリストのようなガバナンスのルールや手順を適用するのではなく、正しいことを簡単に行うことができるようにする」。
JUnitテストの無効化
理想的には、皆さんご存知のように、@Disabledアノテーションを使って書きます。
@Disabled
@Test
void canWeAddTwoNumbers(){
Assertions.fail("this test was skipped and should not run");
}
しかし、学習の際には、@Disabledを使う訓練をしなければなりませんでした。
テストメソッドを無効にする方法を忘れてしまったときは、@Testアノテーションを削除し、テストの名前を変更していました。
class SkipThisTest {
void SKIPTHIScanWeAddTwoNumbers(){
Assertions.fail("this test was skipped and should not run");
}
}
上手くはありませんでしたが、仕事はこなせました。Sensei のような記憶を助けるものがなかったので、お粗末なコーディングパターンを使ってしまいました。
今回の投稿で私が受けた課題は
- メソッドの名前を変更することで、「スキップ」または「無効」になったメソッドを見つけるルールを作成します。
- QuickFixを作成してメソッドの名前を変更し、@Testと@Disabledの両方のアノテーションを追加します。
レシピの設定
Sensei で行う最初のステップは、「新しいレシピを追加」して、レシピに反映させたいコーディングパターンを検索することです。
名前を教えてください。JUnitです。SKIPTHIS から @Disabled @Test を作る。
簡単に説明します。メソッドにSKIPTHISという名前をつけるのをやめ、代わりに@Disabled @Testを使用する。
そして、私の検索はとてもシンプルです。基本的な正規表現を使ってメソッド名をマッチさせています。
search:
method:
name:
matches:"SKIPTHIS.*"
QuickFixの設定
QuickFixはコードを書き換えるので少し複雑ですが、最終的なコードを実現するためにいくつかのステップを使います。
したいのです。
- メソッドに@Testアノテーションを追加
- メソッドに@Disabledアノテーションを追加する。
- メソッド名の修正
アノテーションの追加は、addAnnotation修正プログラムを使えば簡単です。アノテーションに完全修飾名を使用すれば、Sensei が自動的にインポートを追加してくれます。
availableFixes:
- name: "Add @Disabled and @Test Annotation" (日本語訳)
actions:
- addAnnotation:
annotation:"@org.junit.jupiter.api.Test"
- addAnnotation:
アノテーション。"@org.junit.jupiter.api.Disabled"
実際のリネームはもう少し複雑なようですが、私は正規表現による置換を行っているだけで、Sensei で行う一般的な方法は、rewrite アクションで sed を使用することです。
書き換えアクションはMustacheのテンプレートであるため、Sensei はテンプレートのメカニズムにおいていくつかの機能拡張を行っています。関数は{{#...}}で表されるので、sedの場合、関数は{{#sed}}となります。関数はコンマで区切られた2つの引数を取ります。
第1引数にはsed文を指定します。
- s/(.*) SKIPTHIS(.*)/$1 $2/
2番目の引数は、sed文を適用するStringで、この場合はメソッドそのものであり、これはMustacheの変数では次のように表されます。
のリライトアクションを与える。
- を書き換えます。
to: "{{#sed}}s/(.*) SKIPTHIS(.*)/$1 $2/,{{{.}}}{{/sed}}"
sedの実装では、引数自体にカンマが含まれている場合、{{#encodeString}}と{{/encodeString}}でラップする必要があります。- 例:{{#encodeString}}{{.}}}{{/encodeString}}。
リバースレシピ
これは例であり、デモではこれを使いたいと思うかもしれないので、Sensei レシピを使って上記の変更を逆に行う方法を探ってみました。
考えてみると、@Disabledでアノテーションされたメソッドを見つけたいのですが、デモを行うSkipThisTestクラスでしか見つかりません。
名前JUnit: Demo in SkipThisTest @Disabled を削除して SKIPTHIS に戻す
説明: @Disabledを削除してSKIPTHISに戻し、プロジェクトでのデモ用に使用する。
レベル:警告
レシピ設定検索は、特定のクラスのアノテーションを照合するという非常にシンプルなものです。
search:
method:
annotation:
type:"Disabled"
in:
class:
name: "SkipThisTest"
コードがエラーであるように見せないために、レシピの一般的な設定を「警告」と定義しました。警告はコードのハイライトで表示され、コードが大きな問題を抱えているようには見えません。
Quick fixでは、方法が一致しているので、rewriteアクションを使い、変数を使ってテンプレートに入力します。
availableFixes:
- name: "Remove Disabled and rename to SKIPTHIS..."
actions:
- rewrite:
to: "{{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
\ }}}{{{ body }}}"
モディファイア以外のすべての変数を追加し(アノテーションを取り除きたいので)、SKIPTHISテキストをテンプレートに追加します。
この修正は、修飾子を削除することで、他の注釈も削除してしまうという弱点があります。
別のアクションの追加
別の名前の修正プログラムを追加して、alt+enterでQuickFixを表示したときに選択できるようにします。
availableFixes:
- name: "Remove Disabled and rename to SKIPTHIS..."
actions:
- rewrite:
to: "{{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
\ }}}{{{ body }}}"
target: "self"
- name: "Remove Disabled, keep other annotations, and rename to SKIPTHIS..."
actions:
- rewrite:
to: "{{#sed}}s/(@Disabled\n.*@Test)//,{{{ modifierList }}}{{/sed}}\n\
{{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
\ }}}{{{ body }}}"
target: "self"
ここでは、新しいQuick Fixで行を追加しました。
{{#sed}}s/(@Disabled\n.*@Test)//,{{{ modifierList }}}{{/sed}}となります。
これはモディファイアのリストを受け取り、文字列としてエンコードし、sedを使って文字列から@Disabledの行を削除しますが、モディファイアの他の行はすべて残します。
注意:sedの中に「,」を入れることを忘れないでください。そうしないと、プレビューにコメントが追加されてしまいます。これは、Sensei が sed コマンドのシンタックスエラーを警告する方法です。
/* e.g: {{#sed}}s/all/world/,helloall{{/sed}} */
ネストしたsedの呼び出し
幸運なことに、1回の検索と置換で@Disabledと@Testの両方にマッチすることができました。
もし、コードがもっと複雑で、一連のsedコマンドが必要な場合は、ネスト化することで可能になります。
{{#sed}}s/@Test//,{{#sed}}s/@Disabled\n//,{{{ modifierList }}}{{/sed}}{{/sed}}
上の例では、{{modifierList }}に@Disabledの置換を適用した結果に@Testの置換を適用しています。
概要
sedはコードの書き換えを実現する非常に柔軟な方法であり、複雑な書き換え条件のためにsedの関数呼び出しをネストさせることも可能です。
このようなレシピは、プログラミングプロセスを改善するために使用しているので、一時的なものであることが多く、筋肉の記憶が蓄積されて、そのようなプログラミングパターンを使用しなくなったら、クックブックから削除したり、無効にしたりすることができます。
---
IntelliJの "Preferences ˶Plugins" (Mac)または "Settings ˶Plugins" (Windows)から"sensei secure code "を検索して、Sensei をインストールできます。
このブログ記事のすべてのコードは、GitHub のブログサンプルリポジトリの `junitexamples` モジュールにあります。 https://github.com/SecureCodeWarrior/sensei-blog-examples