Dalam proyek kami, kami mencoba untuk menutupi kode dengan tes yang diperlukan dan mematuhi prinsip arsitektur SOLID dan bersih. Kami ingin berbagi dengan pembaca Habr terjemahan artikel oleh Hannes Dorfmann, penulis serangkaian publikasi tentang pengembangan Android. Artikel ini menjelaskan teknik yang dapat membantu Anda memisahkan penggunaan string untuk menyembunyikan detail interaksi dengan berbagai jenis resource string dan mempermudah penulisan pengujian unit.
Jika Anda sedang mengerjakan aplikasi Android besar dan Anda curiga bahwa kode Anda mungkin bingung saat bekerja dengan sumber daya dari sumber yang berbeda, atau jika Anda ingin menyederhanakan penulisan tes terhadap string, maka artikel ini mungkin berguna bagi Anda. Diterjemahkan dengan izin penulis.
. , Android Android.
?
Android? , , . , , , , , , . , :
R.string.some_text, resources.getString(R.string.some_text)
, , .. context.getString(R.string.some_text, «arg1», 123)
<string name=”some_formatted_text”>Some formatted Text with args %s %i</string>
, Plurals, , resources.getQuantityString(R.plurals.number_of_items, 2):
<plurals name="number_of_items">
<item quantity="one">%d item</item>
<item quantity="other">%d items</item>
</plurals>
, Android XML- strings.xml, String ( R.string.some_text). , , json .
, , ? . :
1. , , .
2. ( ) -, , .
: , http, , fallback- strings.xml. , :
class MyViewModel(
private val backend : Backend,
private val resources : Resources // Android context.getResources()
) : ViewModel() {
val textToDisplay : MutableLiveData<String> // MutableLiveData
fun loadText(){
try {
val text : String = backend.getText()
textToDisplay.value = text
} catch (t : Throwable) {
textToDisplay.value = resources.getString(R.string.fallback_text)
}
}
}
MyViewModel, . , loadText(), Resources, StringRepository ( ""), :
interface StringRepository{
fun getString(@StringRes id : Int) : String
}
class AndroidStringRepository(
private val resources : Resources // Android context.getResources()
) : StringRepository {
override fun getString(@StringRes id : Int) : String = resources.getString(id)
}
class TestDoubleStringRepository{
override fun getString(@StringRes id : Int) : String = "some string"
}
- StringRepository , , ?
class MyViewModel(
private val backend : Backend,
private val stringRepo : StringRepository //
) : ViewModel() {
val textToDisplay : MutableLiveData<String>
fun loadText(){
try {
val text : String = backend.getText()
textToDisplay.value = text
} catch (t : Throwable) {
textToDisplay.value = stringRepo.getString(R.string.fallback_text)
}
}
}
- -:
@Test
fun when_backend_fails_fallback_string_is_displayed(){
val stringRepo = TestDoubleStringRepository()
val backend = TestDoubleBackend()
backend.failWhenLoadingText = true // backend.getText()
val viewModel = MyViewModel(backend, stringRepo)
viewModel.loadText()
Assert.equals("some string", viewModel.textToDisplay.value)
}
interface StringRepository , ? . , :
StringRepository , (. ). , - , , String. .
, TestDoubleStringRepository , , ? TestDoubleStringRepository . -, R.string.foo R.string.fallback_text StringRepository.getString(), . , TestDoubleStringRepository, :
class TestDoubleStringRepository{
override fun getString(@StringRes id : Int) : String = when(id){
R.string.fallback_test -> "some string"
R.string.foo -> "foo"
else -> UnsupportedStringResourceException()
}
}
? ( )?
, .
TextResource
TextResource. , domain. , -. :
sealed class TextResource {
companion object { // ,
fun fromText(text : String) : TextResource = SimpleTextResource(text)
fun fromStringId(@StringRes id : Int) : TextResource = IdTextResource(id)
fun fromPlural(@PluralRes id: Int, pluralValue : Int) : TextResource = PluralTextResource(id, pluralValue)
}
}
private data class SimpleTextResource( // inline
val text : String
) : TextResource()
private data class IdTextResource(
@StringRes id : Int
) : TextResource()
private data class PluralTextResource(
@PluralsRes val pluralId: Int,
val quantity: Int
) : TextResource()
//
...
- TextResource:
class MyViewModel(
private val backend : Backend // , , , - , StringRepository.
) : ViewModel() {
val textToDisplay : MutableLiveData<TextResource> // String
fun loadText(){
try {
val text : String = backend.getText()
textToDisplay.value = TextResource.fromText(text)
} catch (t : Throwable) {
textToDisplay.value = TextResource.fromStringId(R.string.fallback_text)
}
}
}
:
1) textToDisplay c LiveData<String> LiveData<TextResource>, - , String. TextResource. , , , TextResource – , .
2) -. « » StringRepository ( Resources). , , , ? , TextResource. , Android, (R.string.fallback_text – Int). -:
@Test
fun when_backend_fails_fallback_string_is_displayed(){
val backend = TestDoubleBackend()
backend.failWhenLoadingText = true // backend.getText()
val viewModel = MyViewModel(backend)
viewModel.loadText()
val expectedText = TextResource.fromStringId(R.string.fallback_text)
Assert.equals(expectedText, viewModel.textToDisplay.value)
// data class- equals,
}
, : TextResource String, , , TextView? , Android, UI.
// context.getResources()
fun TextResource.asString(resources : Resources) : String = when (this) {
is SimpleTextResource -> this.text // smart cast
is IdTextResource -> resources.getString(this.id) // smart cast
is PluralTextResource -> resources.getQuantityString(this.pluralId, this.quantity) // smart cast
}
TextResource String UI ( ) , TextResource «» (.. ), R.string.* .
: - TextResource.asString(), . , when. resources.getString(). , TextResource , «/». , , : , TextResource, when TextResource.asString().
: , TextResource /. / TextResource, sealed class TextResouce abstract fun asString(r: Resources), . , / asString(r: Resources), ( , , /). ? , Resources API TextResource , (, SimpleTextResource ). , API, , ( ).
: dimens, , . , . , – , , . !