robolectricでZip解凍とCSV解析のテストコードを試作しましょう!

#### 最近Zipファイル中身のCSVファイルの解析とテストコード書きのタスクを完成して、こちらでちょっど整理しますね。

## 1.Csv読み込みのメソッドを作成する

  ```kotlin:LoginHelper.kt
    // 例: taro,abc (ユーザ名,パスワード)
    open fun readCsvFile(input: InputStream, expactedName: String): String? {
        try {
            val parser = CSVParser.parse(input, Charset.defaultCharset(),
                    CSVFormat.RFC4180.withIgnoreEmptyLines()
                            .withHeader("ユーザ名", "パスワード")
                            .withFirstRecordAsHeader()
                            .withIgnoreSurroundingSpaces())
            parser.filter {
                it.size() > 1
            }.forEach {
                val userName = it.get(0)
                val password = it.get(1)
                if (userName.equals(expactedName)) {
                    return password
                }
            }
            parser.close()
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }
        return null
    }
  ```

## 2.Zipファイル解凍メソッドを作成する

  ```kotlin:LoginHelper.kt
    fun getPasswordFromZipFile(file: File, expactedName: String): String? {
        try {
            val zipFile = ZipFile(file)
            if (zipFile.isEncrypted) {
                zipFile.setPassword("123456")
            }
            val fileHeader = zipFile.getFileHeader("userInfo.csv")
            val input = zipFile.getInputStream(fileHeader)
            return readCsvFile(input, expactedName)
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }
    }
  ```
  ここまで、機能実装コート完成した！

(￣▽￣)(￣▽￣)(￣▽￣) 私は実装コードとテストコードの分割線です (￣▽￣)(￣▽￣)(￣▽￣)

## 3.Csvファイル解析テストコード

  ```kotlin:CsvFileParseTest.kt
@RunWith(ParameterizedRobolectricTestRunner::class)
@PrepareForTest(System::class, LoginHelper::class)
class CsvFileParserTest(
        private var userName: String,
        private var result: String?,
        private val dataFileName: String?
) {
    companion object {
        @ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
        @JvmStatic
        fun data() = listOf(
                arrayOf("taro", "aaaaccc", "user_info_data.csv"), //正常ケース
                arrayOf("sanro", "8888\\r77\\n55", "user_info_data.csv"),//パスワードに改行が含まれる
                arrayOf("sichiro", "", "user_info_data.csv"), //Csvファイル読む時、関連のパスワードを見つけない場合
                arrayOf("", "", "user_info_data.csv"),        //受け取る名前がからになる
                arrayOf("hachiro", null, null),               //Csvファイルnullになる
                arrayOf("kyuro", null, "user_info_empty.csv") //Csvファイルは空です
        )
    }

    internal val context: Context
        get() = RuntimeEnvironment.application

    @Test
    fun readCsvAccountInfoTest() {
        val password: String?
        if (dataFileName != null) {
            //テストのため、 user_info_data.csvテストファイルがassetsフォルダに入れて、読み込み
            val am = context.assets
            val input = am.open(dataFileName)
            password = LoginHelper().readCsvAccountInfo(input, userName)
        } else {
            password = LoginHelper().readCsvAccountInfo(ByteArrayInputStream(byteArrayOf()), userName)
        }
        Assert.assertEquals(result, password)
    }
}
  ```

## 4.Zipファイル解凍テストコード

   ```kotlin:ZipFileParseTest.kt
@RunWith(ParameterizedRobolectricTestRunner::class)
@PrepareForTest(System::class, LoginHelper::class)
class ZipFileParseTest(
        private var userName: String,
        private var result: String?,
        private var isEncrypt: Boolean,
        private var fileNameInZipFile: String,
        private var zipFilePath: String?,
        private var is0byte: Boolean,
        private var zipPassword: String,
        private var isReadCsvFileCalled: Boolean
) {
    companion object {
        @ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
        @JvmStatic
        fun data() = listOf(
                arrayOf("taro", "password1", true, "user_info.csv", Environment.getExternalStorageDirectory()?.path + "/user_info.zip", false, "password", true), //正常ケース
                arrayOf("taro", "password2", false, "user_info.csv", Environment.getExternalStorageDirectory()?.path + "/user_info.zip", false, "password", true), //暗号化されていない
                arrayOf("taro", null, false, "user_info.txt", Environment.getExternalStorageDirectory()?.path + "/user_info.zip", false, "password", false), //ファイルタイプ合ってない
                arrayOf("taro", null, false, "user_info.csv", "888888888.zip", false, "password", false), //ファイル名合ってない
                arrayOf("taro", null, false, "user_info.csv", Environment.getExternalStorageDirectory()?.path + "/user_info.zip", true, "password", false), //ZIPファイルが壊れる
                arrayOf("taro", null, false, "user_info.csv", Environment.getExternalStorageDirectory()?.path + "/user_info.zip", false, "123", true) //Zipパスワードを正しく入力されない
        )
    }

    internal val context: Context
        get() = RuntimeEnvironment.application

    @Test
    fun getPasswordFromZipFileParseTest() {
        val csv = "タイトル1,タイトル2\r\njiro,password1\r\nsanro,password2\r\nsiro,password3"
        val csvInputStream = ByteArrayInputStream(csv.toByteArray());

        val externalStorageDirectory = Environment.getExternalStorageDirectory()
        val zipPath = File(externalStorageDirectory, "user_info.zip")

        val commonParameters = ZipParameters().apply {
            isSourceExternalStream = true
            encryptionMethod = Zip4jConstants.ENC_METHOD_STANDARD
            isEncryptFiles = isEncrypt
            password = zipPassword.toCharArray()
            fileNameInZip = fileNameInZipFile
        }

        if (is0byte) {
            zipPath.createNewFile() //0byteのzipファイルを作成する
        } else {
            val zipFile = ZipFile(zipFilePath)
            zipFile.addStream(csvInputStream, commonParameters)
        }

        val helper = TempCheckCsvFile(result)
        val password = helper.getPasswordFromZipFile(zipPath, userName)
        Assert.assertEquals(result, password)
        Assert.assertEquals(isReadCsvFileCalled, helper.isTempCheckCsvFileCalled) //CSVの処理を略しても、呼ばれるかを検証します。

        if (isReadCsvFileCalled) {
            Assert.assertEquals(csv, IOUtils.toString(helper.readCsvAccountInfoStream, "UTF-8"))
            Assert.assertEquals(userName, helper.name)
        }
    }

    //主にZip解凍テストするため、こちらはCsv解析の処理を継承とオーバライドで略します。
    class TempCheckCsvFile(val result: String?) : LoginHelper() {
        internal var isTempCheckCsvFileCalled = false
        internal lateinit var readCsvAccountInfoStream: InputStream
        internal var name: String? = null

        override fun readCsvAccountInfo(input: InputStream, userName: String): String? {
            isTempCheckCsvFileCalled = true
            readCsvAccountInfoStream = input
            name = userName
            return result
        }
    }
}
  ```
ここまで、終わり！
![OJBk.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601674/79ddbaef-b029-98c3-1318-ee8d7488df9a.jpeg)

## 追加：
元々Csv解析の処理を呼ばれるかの検証に対して、[mockito-kotlin](https://github.com/nhaarman/mockito-kotlin) の`verify(helper, times(1)).readCsvAccountInfo(any(), any())`を使ってみたいですが、`java.lang.IllegalArgumentException: Parameter specified as non-null is null`: のエラーが出ますが、 [この記事Parameter-specified-as-non-null-is-null](https://github.com/nhaarman/mockito-kotlin/wiki/Parameter-specified-as-non-null-is-null)も解決できなさそうですので、継承とオーバライドで`TempCheckCsvFile`のクラスを作成して、テストします。