Android: テストメソッドの命名規則について考えてみた
佐藤 隼 (@stsn_jp)
目次
背景: そもそもの問題点/出発点
Androidでテストを書き始めたは良いものの, テストコードがカオスになってきた...
可読性が低い, そもそも何のテストをしているか分からない, 粒度が荒い, テストが落ちた時に修正ではなく, 削除したくなる
背景: そもそもの問題点/出発点
テストコードを書くと決めたら, 負債にならないようにする必要がある!!
テストメソッドの命名規則をしっかりとすれば, ある程度解決できると思い, ルール作りをしました.
一般的なテストの構成要素
で構成される.
これらが組み合わせることで, 1つのテストが構築される
public class HTTPConnectionTest {� HTTPConnection httpConnection;� @Before public void setup() {
// 事前準備(setup)
httpConnection = new HTTPConnection();� }�
@Test public void execute() {� // 事前準備(setup)� Request validRequest = new Request("http://valid-url.com")� // 実行(exercise)� Response response = httpConnection.execute(validRequest);� // 宣言(assertion)� assertThat(response.isOk()).isTrue();�� // 事前準備(setup)� Request inValidRequest = new Request("http://invalid-url.com")� // 実行(exercise)� response = httpConnection.execute(inValidRequest);� // 宣言(assertion)� assertThat(response.isOk()).isFalse();� }�}
テストexecuteの
粒度を考える
HTTPConnectionクラスのexecute関数にはvalid/invalidの2パターンがある
右の例だと, valid/invalidが1つのテストにまとまっている(1つのテストの中で複数のことを宣言してはいけない)
public class HTTPConnectionTest {� HTTPConnection httpConnection;� @Before public void setup() {� httpConnection = new HTTPConnection();� }�
@Test public void execute() {� // 事前準備(setup)� Request validRequest = new Request("http://valid-url.com")� // 実行(exercise)� Response response = httpConnection.execute(validRequest);� // 宣言(assertion)� assertThat(response.isOk()).isTrue();�� // 事前準備(setup)� Request inValidRequest = new Request("http://invalid-url.com")� // 実行(exercise)� response = httpConnection.execute(inValidRequest);� // 宣言(assertion)� assertThat(response.isOk()).isFalse();� }�}
テストを分割
valid/invalidを右のように分割する
これで, 1つのことを宣言するようになった
しかし, execute_ok, execute_ngだと, どのような前提条件(引数)の場合に, ok, ngになるのか分からない
public class HTTPConnectionTest {� HTTPConnection httpConnection;� @Before public void setup() {� httpConnection = new HTTPConnection();� }�
@Test public void execute_ok() {� Request validRequest = new Request("http://valid-url.com")� Response response = httpConnection.execute(validRequest);� assertThat(response.isOk()).isTrue();� }
@Test public void execute_ng() {� Request inValidRequest = new Request("http://invalid-url.com")� Response response = httpConnection.execute(inValidRequest);� assertThat(response.isOk()).isFalse();� }�}
前提条件を含める
execute_ok_200URL, execute_ng_404URLとし, 前提条件もメソッド名に含める
「5xx系のテストやってないじゃないか!」 などのテスト漏れを防ぐことが出来る
public class HTTPConnectionTest {� HTTPConnection httpConnection;� @Before public void setup() {� httpConnection = new HTTPConnection();� }�
@Test public void execute_ok_200URL() {� Request validRequest = new Request("http://valid-url.com")� Response response = httpConnection.execute(validRequest);� assertThat(response.isOk()).isTrue();� }
@Test public void execute_ng_404URL() {� Request inValidRequest = new Request("http://invalid-url.com")� Response response = httpConnection.execute(inValidRequest);� assertThat(response.isOk()).isFalse();� }�}
こうすることのメリット
粒度が保たれる
前提条件がメソッド名に含まれるため, 他のデータを使うようなテストを書けない (e.g. execute_ok_200URLの中に, 3xx系のテストは混入しない)
テストが失敗した時の原因究明/修正がしやすくなる
名前から推測, 1つのテストで1つのことをすることが強制されるため
一般化すると
テスト対象メソッド名_期待される振る舞い_前提条件
execute ok 200URL
まとめ
テストを負債化しないため, テスト全体のルール作りは必須
命名規則, 粒度, ツール, などなど最低限のルールを決める
makeコマンドや, gradleコマンドでbuildするときは, テストも走らせるようにすると忘れないで良い
Android Studioのcoverage reportの機能は非常に便利
Thanks!