最近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
}
}
}
追加:
元々Csv解析の処理を呼ばれるかの検証に対して、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も解決できなさそうですので、継承とオーバライドでTempCheckCsvFile
のクラスを作成して、テストします。