LoginSignup
0
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-07-22

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

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

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ファイル解凍メソッドを作成する

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ファイル解析テストコード

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ファイル解凍テストコード

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

追加:

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

  

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0