使用 Mockk 和 Truth 在 Android 上进行单元测试(I):基础

介绍

本文介绍了如何开始使用MockkTruth框架为 Android 应用程序编写测试。

第一步

第一件事是build.gradle像往常一样将依赖项添加到应用程序模块文件中:

testImplementation "io.mockk:mockk:1.12.3"
testImplementation 'junit:junit:4.13.2'
testImplementation "com.google.truth:truth:1.1.3"
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1'

假设我们有一个 classCar和一个 class Engine,那么它们之间的关系就是每辆车都有一个引擎。因此,Car我们可以说类依赖于类Engine

要开始测试,我们只需在Android Studio中的 tests 文件夹中创建一个新类(注意,不是 androidTest,因为它用于仪器测试)。

注意:如果这是您第一次在 Android 上编写测试,您可以查看此文档

现在是的,让我们看看代码:

class CarTest {
    lateinit var engine: Engine
    lateinit var car: Car

    @Before
    fun setUp(){
        engine = mockk<Engine>() 
        car = Car (engine)
    }

    @After
    fun tearDown(){
        unmockkAll()
    }

    @Test
    fun `Car with broken engine throws exception when turning on`() {
        every { engine.turnOn() } throws BrokenEngineException()
        assertThrows(BrokenEngineException::class.java) { car.turnOn() } 
    }
}

下一节将详细解释代码。

测试结构

setUp 和 tearDown 函数

我们找到的前两个函数是这些,它们也有注释@Before@After. 这已经告诉我们一些关于它们的性质,即它们分别在每次测试之前和之后执行。

通常,在函数setUp中,我们要做的是初始化我们的变量和我们想要测试的内容(从现在开始,我们将把它称为SUT:被测对象)。如您所见,我们将把依赖项初始化为模拟,即“哑”对象,它们将获得我们告诉它们的行为(我们将在下面解释如何)。我们将使用真值对象初始化 SUT,因为我们要测试的是真值对象。

至于函数tearDown,它没有太多历史,在这种情况下,我们删除所有要清理的模拟,仅此而已。在此功能中执行清洁任务。

测试的定义

每个单元测试都只是一个函数,它有一个标签@Test和一个名称来描述它的作用。

至于名字的写法,有很多,但在 Kotlin 中,一般都是写在两个反引号之间,并带有一个英文描述性短语。该短语通常描述 SUT、前提条件和预期结果。

在这个例子中,我们“确保”每辆发动机损坏的汽车 (SUT)(前提条件)在启动时都会抛出异常(预期结果)。

单元测试通常分为三个部分(一般来说,它不是 Kotlin 或 Android 独有的东西),称为三 A:Arrange、Act 和 Assert,翻译成西班牙语类似于组织先决条件、行动和确认结果。

安排:定义我们的模拟的行为

进入方法体,我们要写的第一部分是整理先决条件。这主要是为了建立我们的 mocks 的行为。由于它们是哑对象,如果我们尝试调用某个函数(例如,在 的情况下Engine,启动电机),我们将得到一个异常,因为我们没有定义在模拟中调用该函数时必须发生什么。

为此,通常使用everyand函数coEvery,这取决于我们要模拟的方法是否使用协程。语法完全相同,所以让我们看看使用的示例every

every { engine.turnOff() } just runs
every { engine.turnOn() } throws BrokenEngineException()
every { engine.getHorsePower() } returns 100

主要是我们要使用的三种方法。

  • 第一个用于返回的Unit方法,即不返回任何内容的方法,因此当它们被调用时,它们什么都不做,但不会抛出异常。
  • 使用第二个,以便在调用该方法时,它会抛出我们告诉它的异常。
  • 后者用于返回一些值的方法。

动作:调用 SUT 函数

第二步是调用 SUT 方法。简单地说,当我们已经建立了所有先决条件时,我们将对我们想要测试的方法进行正常调用,仅此而已。由于Truth框架的语法,这与最后一步一起完成了很多次,所以我不会突出任何其他内容。

断言:检查结果

最后,我们必须检查结果。这可以是多种多样的,因为我们想要检查的执行结果可以是任何性质的。通常,我们需要检查以下其中一项:

  • 调用某个函数。
  • 没有调用某个函数。
  • 函数的返回值与另一个预期结果匹配。
  • 一个函数抛出异常。
  • 函数返回的对象属于特定类型。

让我们看看如何使用我们可用的框架检查这些事情:

verify { engine.hasEnoughFuel() }
verify(exactly = 0) { engine.hasEnoughFuel() }

在第一个示例中,我们在第一种情况下检查的是函数被调用,但没有指定多少次。如果至少调用一次,则测试通过,否则,测试失败。在第二个例子中,我们做的是相反的,即检查函数没有被调用。这样,如果函数没有被调用,测试将通过,如果至少调用一次,测试将失败。

注意:就像它存在coEvery于模拟一样,它存在coVerify于测试使用协程的函数。

assertThat(car.getHorsePower()).isEqualTo(100)
assertThat(car.hasEnoughFuel()).isTrue()

在第二个例子中,我们检查的是调用某个函数的返回值是否等于我们期望的另一个值。

assertThrows(BrokenEngineException::class.java) { car.turnOn() }

这第三个示例将检查该方法在调用类方法BrokenEngineException时是否引发类型异常。turnOn``Car

assertThat(car.getEngineType()).isInstanceOf(DieselEngine::class.java)

最后,这将验证汽车的发动机是柴油的。这样,如果它是汽油、混合动力或电动发动机,测试就不会通过。

结论

这应该是与当今 Android 测试世界的第一次接触。这些框架极大地扩展了JUnit另一个最常用框架的功能,并极大地简化了测试语法。

本系列的其他文章将讲解如何编写更复杂的测试,测试类型LiveData的对象,类型的对象Flow,协程等。

作者:Programming journey
链接:https://cmhernandezdel.hashnode.dev/tests-unitarios-en-android-con-mockk-y-truth-i-fundamentos

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容