皆さんこんにちは。株式会社コスモルートでアンドロイドアプリの請け負い開発をしているT.Mです。

前回までの記事はこちら
「とりあえず動く」を卒業するためのアーキテクチャ入門編(1)
「とりあえず動く」を卒業するためのアーキテクチャ入門編(2)
「とりあえず動く」を卒業するためのアーキテクチャ入門編(3)

前回は、テストの概要について触れました。
今回はテストコードなどについて説明していきます。

2.4 実際のテストコード

テストスコープが機能単位の小規模テストを実際に書いてみましょう。
機能単位なので大部分はAndroidに依存しないテストが書けるというメリットもあります。バズツイートを表示するアプリを例に、機能テストを考えてみます。
アプリの要件が、

・バスツイート=フォロアーの100倍のいいね!がついたツイート
・当日のバズツイートをリストで表示

でした。2つ目の当日のバズツイートをリストで表示はUIテストに任せるとして(当ブログでは扱いません)最初のバズツイート=フォロアーの100倍のいいね!をテストしましょう。

まずはTwitterModel.ktを実装していきます。

data class Tweet constructor(private val tweetId:String,val content:String,val like:Int,val date:LocalDate)

data class Account constructor(private val id:String,private val followers:List<String>,val tweets:List<Tweet>){
    fun busTweet(date:LocalDate):List<Tweet>{
        return tweets.filter { tweet -> tweet.date == date && tweet.like >= followers.count()*100}
    }
}

まずアカウントクラスですが、id:StringでIDを文字列で保持しています。そして、busTweet関数がバズツイートを返しています。なのでこの関数をテストします。

class BusTweetTest {

    val today = LocalDate.now()
    //25999ツイートと26000ツイートのツイート2つ
    val tweets = listOf(Tweet("tweet_20211101","",25999,today),Tweet("tweet_20211101",26000,today))

 @Test
    fun testBusTweet(){
        //フォロアーは"a"~"z"までの26フォロアー
        val followers = listOf<String>("a","b","c","d",..."z")
        val account = Account("cosmoroot",followers,tweets)
        val busTweets = account.busTweet(today)
        //バズツイートは1つ
        Assert.assertEquals(1,busTweets.count())
    }
}

バスツイートのテストを書きました。バズツイートの要件はバスツイート=フォロアーの100倍のいいね!がついたツイートだったので、26フォロアーでいいねが26000個以上がバズツイートです。例として境界付近でテストをしました。また、コードは全てAndroidフレームワークに依存してないことにも注目です。なのでテストの実行速度もUIテスト等に比べて速いです。

仮にbusTweet関数がMainActivity.ktのリスト表示で書かれていたらどうでしょうか?その場合機能テストとUIテストが分離されていないので、テストの実行が遅くなるというデメリットがあります。またバズツイートの定義が変更された場合、コードのどの部分を修正すれば良いか探すのに苦労します。テストコードを書くのが難しいと言われる理由は、経験上8割以上コードに問題があるので、まずはレファクタリングすると良いでしょう。

またテストカバレッジ100%を目指す必要はありません。感覚的にはカバレッジが高いほどバグが少ないように思えますが、重要な機能とバグが起こりやすい条件を重点的にテストすべきです。カバレッジが高いほどコードを修正するたびにテストコードを通すために必要以上に時間を費やすことになり、現実的ではありません。

次の章ではクリーンコードについて言及していきます。