介绍
本文介绍了如何开始使用Mockk和Truth框架为 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
,启动电机),我们将得到一个异常,因为我们没有定义在模拟中调用该函数时必须发生什么。
为此,通常使用every
and函数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