You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MVVM架构下使用String Resources的最佳实践?Kotlin单元测试遇Context难题

如何优化依赖Context的工具类,简化单元测试?

问题场景

我在编写单元测试时,遇到工具类(比如示例中的testClass)依赖Context来获取字符串资源的问题。原本想通过这类工具类拆分ViewModel逻辑、实现复用,但测试时必须模拟Context,操作不够简便。

现有代码实现

工具类testClass

class testClass {
    fun compareNumbers(numberOne: Int, numberTwo: Int, context: Context): String {
        var message = ""
        when {
            numberOne > numberTwo -> message = context.getString(R.string.numberOne)
            numberOne < numberTwo -> message = context.getString(R.string.numberTwo)
            numberOne == numberTwo -> message = context.getString(R.string.tie)
        }
        return message
    }
}

ViewModel中的调用

fun declareWinner(context: Context) {
    val result = testClass.compareNumbers(numberOne, numberTwo, context)
    updateResult(result) //updates the result to a database
}

优化方案

1. 分离业务逻辑与资源获取

把数值比较的业务逻辑和字符串资源获取拆分开,工具类只负责返回逻辑结果(比如枚举),由调用方(ViewModel)处理资源获取。这样工具类完全不依赖Context,测试零成本。

示例改造:

// 定义枚举表示比较结果
enum class CompareResult {
    NUMBER_ONE_GREATER, NUMBER_TWO_GREATER, TIE
}

class NumberComparator {
    fun compareNumbers(numberOne: Int, numberTwo: Int): CompareResult {
        return when {
            numberOne > numberTwo -> CompareResult.NUMBER_ONE_GREATER
            numberOne < numberTwo -> CompareResult.NUMBER_TWO_GREATER
            else -> CompareResult.TIE
        }
    }
}

ViewModel中处理资源:

fun declareWinner(context: Context) {
    val result = numberComparator.compareNumbers(numberOne, numberTwo)
    val message = when(result) {
        CompareResult.NUMBER_ONE_GREATER -> context.getString(R.string.numberOne)
        CompareResult.NUMBER_TWO_GREATER -> context.getString(R.string.numberTwo)
        CompareResult.TIE -> context.getString(R.string.tie)
    }
    updateResult(message)
}

这种方式的好处是:工具类的单元测试只需验证数值与枚举的对应关系,完全无需处理Context;ViewModel的测试也可专注于枚举到字符串的映射逻辑,或用Mockito轻量模拟Context。

2. 注入字符串资源提供者(依赖倒置)

如果不想把资源逻辑放到ViewModel里,可以定义资源提供接口,让工具类依赖接口而非具体Context,测试时用Mock实现接口即可。

示例:

// 定义资源提供接口
interface StringResourceProvider {
    fun getString(resId: Int): String
}

// Android环境下的资源提供者实现
class AndroidStringResourceProvider(private val context: Context) : StringResourceProvider {
    override fun getString(resId: Int): String {
        return context.getString(resId)
    }
}

// 工具类依赖接口
class NumberComparator(private val resourceProvider: StringResourceProvider) {
    fun compareNumbers(numberOne: Int, numberTwo: Int): String {
        return when {
            numberOne > numberTwo -> resourceProvider.getString(R.string.numberOne)
            numberOne < numberTwo -> resourceProvider.getString(R.string.numberTwo)
            else -> resourceProvider.getString(R.string.tie)
        }
    }
}

ViewModel中注入实现类:

// ViewModel初始化时传入AndroidStringResourceProvider
val numberComparator = NumberComparator(AndroidStringResourceProvider(context))

fun declareWinner() {
    val result = numberComparator.compareNumbers(numberOne, numberTwo)
    updateResult(result)
}

测试时Mock接口:

// 单元测试示例
@Test
fun testCompareNumbers() {
    val mockProvider = mock(StringResourceProvider::class.java)
    `when`(mockProvider.getString(R.string.numberOne)).thenReturn("Number One wins")
    
    val comparator = NumberComparator(mockProvider)
    val result = comparator.compareNumbers(5, 3)
    
    assertEquals("Number One wins", result)
}

这种方式既保留了工具类的功能完整性,又降低了与Android框架的耦合,测试灵活性大幅提升。

3. 用AndroidX Test的InstrumentationRegistry(仅Instrumented测试)

如果测试必须验证真实资源(需跑在真机/模拟器上),可以用AndroidX Test提供的InstrumentationRegistry获取真实Context,无需Mockito:

import androidx.test.platform.app.InstrumentationRegistry

@Test
fun testCompareNumbersWithRealContext() {
    val context = InstrumentationRegistry.getInstrumentation().targetContext
    val comparator = testClass()
    val result = comparator.compareNumbers(5, 3)
    
    assertEquals(context.getString(R.string.numberOne), result)
}

注意:这是Instrumented测试,运行速度远慢于纯单元测试,仅适合必须验证真实资源的场景。

总结

优先选方案1,它彻底分离业务逻辑与Android依赖,单元测试最简单;若需保持工具类的资源获取能力,选方案2,通过依赖倒置降低耦合;方案3仅适合验证真实资源的场景。

内容的提问来源于stack exchange,提问作者Hunter K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 07:37:41