用柯特林测试安卓应用


测试是任何一种软件开发不可或缺的一部分。为了确保代码准备好部署到生产中,仅仅手动验证新特性是否工作是不够的。当谈到代码质量和可靠性时,自动化测试的重要性怎么强调都不为过,不管是回归测试、单元测试还是用户界面测试。

在这篇博文中(http://blog.greenhouseci.com)我们讨论了为你的安卓应用编写自动化测试的另一个选项。也就是说,我们看如何Kotlin,是基于JVM的语言世界中最新稳定且值得注意的补充之一,可用于安卓应用程序的测试自动化。

关于柯特林的几句话

科特林是一个open source language由智能创意背后的公司——智脑开发和维护,也可能是你最喜欢的安卓开发环境——安卓工作室。

经过5年多的开发(第一次提交可以追溯到2010年11月8日),第一个稳定的语言版本was released今年早些时候。

其主要特征包括

  • 支持面向对象和功能风格。
  • 与Java代码的互操作性,这意味着您可以轻松地将Kotlin混合到您现有的Java代码库中反之亦然
  • 优秀的工具和插件可用于基于智能的IDEs、Eclipse和文本编辑器,如Atom、Emacs、Sublime Text和Vim。
  • 非常强调清晰度和字体安全性。

安卓应用测试概述

与安卓生态系统的早期相比,它通过工具、库和框架对测试的支持有了显著提高。从简单的仪器测试和常规的JUnit测试,我们已经一路走到了方便的工具,比如AssertJEspresso,Robolectric,Robotium,UI Automator等等。所有这些都让我们的工作变得更容易,从而让我们的生活更幸福。

然而,您的测试很少仅仅由断言语句组成。为了准备测试用例,通常会有大量的实用程序和粘合代码,尤其是在测试更复杂的用户流时。更糟糕的是,在某些情况下,测试的复杂性很容易超过被测试代码的复杂性。正是在这些情况下,Java的冗长会成为障碍。打开一个文件,用一两行来循环它的内容,而不是像Java中那样需要几十个位置,这样会很方便。

在这种情况下,像柯特林这样的语言真的可以帮助你提升你的游戏。表现力和简洁的语法,加上漂亮的语言习惯用法,使它比Java的笨拙有了巨大的改进。不过,闲聊到此为止,让我们通过一个实际例子来看看这一切在实践中是如何运作的。

将科特林插入你的安卓项目

为了便于参考,您已经可以从获取示例应用程序的完整源代码this GitHub repository。接下来的所有代码片段都将从那里提取出来。

要开始在现有的安卓项目中使用科特林,你只需要在项目的渐变构建文件中添加一些配置说明。这些声明了您想要使用的Kotlin版本,并确定了需要额外编译的依赖项。就我的经验而言,the official documentation,这看起来很棒,会给你指明正确的方向。无论如何,我的build.gradle构建脚本是这样结束的:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

buildscript {
    ext.kotlin_version = '1.0.+'
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

android {
    ...
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
    ...
}


让我们去掉多余的线条。在第二行,在应用了安卓插件之后,我们注入了针对安卓构建模型的科特林·格拉德勒插件apply plugin: 'kotlin-android'。为了使这个插件可用,我们需要将它声明为buildscript级别相关性:

dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}


其中前面的ext.kotlin_version = '1.0.+'只是从1.0系列中选择最新版本的Kotlin。

最后,在dependencies部分,我们需要声明科特林stdlib必须被编译。因为我们将使用Kotlin专门进行测试,所以它也需要为测试进行编译。

关于测试及其在源树中的位置的一点注记

因为单元和用户界面测试通常存储在src/test/javasrc/androidTest/java分别地,我们可能希望在我们的代码库中保持健全,并在专用目录中保持Kotlin测试,例如src/test/kotlinsrc/androidTest/kotlin。因为这些在默认情况下不会被检测为安卓测试位置(还没有吗?),我们需要让格拉德和安卓工作室意识到这一点。这可以在sourceSets带有以下内容的部分:

android {
    ...
    sourceSets {
        test.java.srcDirs += 'src/test/kotlin'
        androidTest.java.srcDirs += 'src/androidTest/kotlin'
    }
}


我们的示例应用程序

application为这篇博文的目的而创建的当然是另一个惊人的购物清单应用。正如所料,您可以根据自己的意愿在列表中添加或删除一些项目。为了给混合添加一些趣味,并使事情更容易测试,插入到列表中的每个项目都通过一个格式化程序,从输入字符串中移除所有多余的空格。我们用来清理字符串的格式化程序形式的关键业务逻辑存在于Formatter.java看起来是这样的:

public class Formatter {

    public String stripMiddle(String s) {
        return s.replaceAll("\\s+", " ");
    }

    public String stripLeft(String s) {
        return s.replaceAll("^\\s+", "");
    }

    public String stripRight(String s) {
        return s.replaceAll("\\s+$", "");
    }

    public String strip(String s) {
        return stripLeft(stripRight(stripMiddle(s)));
    }
}


连接用户与格式化程序交互的桥梁就位于MainActivityFragment

总的来说,我们的应用展现了它的辉煌,如下图所示:


很自然,我们希望测试项目描述EditText被填充了一些内容,并且“添加项目”按钮被按下,新添加的项目被附加到列表中。此外,最重要的是确保添加到列表中的所有项目都不包含任何多余的空白字符。为此,我们当然需要测试。

单元测试

让我们从单元测试开始Formatter如上所示的类。如前所述,我们将把单元测试存储在src/test/kotlin。在那个目录中,我们有一个测试类FormatterTestKotlin定义于FormatterTestKotlin.kt

package com.greenhouseci.kotlin_tests.kotlintests

import org.junit.Assert.assertEquals
import org.junit.Test


class FormatterTestKotlin {

    private val formatter: Formatter = Formatter()

    infix fun Any.equals(expected: Any) {
        assertEquals(expected, this)
    }

    ...

    @Test
    @Throws(Exception::class)
    fun testStrip() {
        val reference = "my string"
        val testStrings = arrayOf(
            reference,
            " $reference ",
            " $reference  ",
            " \t $reference \t ",
            " \n  $reference \n ",
            "\tmy \n string\t"
        )
        testStrings.map { formatter.strip(it) equals reference }
    }


看一下上面的代码片段,可以发现Kotlin代码继承了一些常见的Java元素,比如包名和导入语句。进一步分解它会发现行尾缺少分号,变量和函数声明也有一些不同。但是也有一些明显的相似之处,比如用花括号分隔代码块,以及支持注释。

变量和函数声明

在我们的示例代码中,我们可以看到声明一个值可以简单到:

val reference = "my string"


其中被分配的对象的类型由编译器推断。或者我们可以通过写:

val formatter: Formatter = Formatter()


其中类型信息跟在带冒号的变量名后面,在要赋值的值之前。

Kotlin还提供了一种区分只读和可变字段的简单方法。它通过公开两种类型的关键字来做到这一点:val用于声明只读字段和var应该是可变的变量。在Java领域,val对应于final修饰语。

同样值得注意的是,我们可以毫不费力地将一个Java对象赋给一个Kotlin变量。

函数的定义使用fun关键字后跟括号中的函数名和参数。返回类型和参数类型是用冒号符号指定的,就像变量声明一样。然而,由于Kotlin对用户定义的中缀函数有一流的支持,如上所述,我们可以毫不费力地用我们选择的语法糖来丰富标准库。例如,这里我们已经替换了:

assertEquals(expected, actual)


检查:

actual equals expected


语句的可读性,利用我们的自定义中缀程序equals

infix fun Any.equals(expected: Any) {
    assertEquals(expected, this)
}


当然,所有其他科特林宝石对我们来说都是现成的,包括方便的数组声明,避免笨拙for循环使用map和其他功能元件。

最后但同样重要的一点是,我们用@Test让编译器知道它们。

@Test
fun myTestCase() { ... }


浓缩咖啡测试

利用Java和科特林代码的互操作性,我们可以将Espresso融入我们的科特林测试类中,并获得它们协同的好处。有了浓缩咖啡,我们得到了一个久经沙场的框架,使得测试用户界面组件变得轻而易举。与此同时,科特林语的所有精华使我们代码和其他测试逻辑干净且易于编写。

像往常一样,我们将浓缩咖啡和其他用户界面测试保存在src/androidTest,除了我们将科特林源代码存储在kotlin子目录而不是java。为了运行新的测试,我们需要在构建脚本中将Espresso声明为一个依赖项。我们还需要设置AndroidJUnitRunner作为默认的测试仪器运行程序,就像普通的浓缩咖啡测试一样:

android {
    ...
    defaultConfig {
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

dependencies {
    ...
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}


现在来看看测试套件本身。我们的示例浓缩咖啡测试课EspressoKotlinTest看起来像这样:

@RunWith(AndroidJUnit4::class)
class EspressoKotlinTest {

    ...

    @Rule @JvmField
    var activityRule = ActivityTestRule<MainActivity>(MainActivity::class.java)

    ...

    @Test
    fun testAddItemsToList() {
        val formatter = Formatter()
        val lv = onData(anything()).inAdapterView(withId(R.id.lv_items))

        var items = arrayOf(
            Pair("    item 1", "item 1"),
            Pair("item 2  ", "item 2"),
            Pair("  item  \n3  ", "item 3")
        )

        items.map { pair -> addItemToList(pair.first) }
        items.mapIndexed { index, pair ->
            val expected = pair.second
            lv.atPosition(index).onChildView(withId(R.id.tv_list_item)).check(
                matches(withText(equalToIgnoringCase(expected)))
            )
        }
    }
}


您可以看到,浓缩咖啡测试套件与我们在上一节中讨论的单元测试没有太大不同。它也和你用Java写的任何浓缩咖啡测试没有太大的不同。事实上,大多数执行繁重任务的代码是普通Java,而其他零件实际上是替换品。

和测试一样,我们从创建一个测试的JUnit 4测试类开始,添加:

@RunWith(AndroidJUnit4::class)


我们的测试类定义开头的注释。自然,我们可以利用浓缩咖啡的内置ActivityTestRule处理我们的活动生命周期:

@Rule @JvmField
var activityRule = ActivityTestRule<MainActivity>(MainActivity::class.java)


一个小警告是,您需要用@JvmField将它从Kotlin属性转换为JUnit可以识别的JVM字段。

除了这些微小的细节,您可以使用熟悉的浓缩咖啡应用编程接口来完成其余的测试功能。

运行我们的科特林测试

因为我们已经更新了sourceSetsbuild.gradle我们不需要经历任何额外的配置魔法。您可以从安卓工作室直接执行单元测试和仪器测试,只需右键单击您的测试包或科特林测试文件,然后选择“在…中运行测试”,如下所示:


如果您想从命令行执行测试,同样的最小配置逻辑也适用。Kotlin单元和仪器测试可以用常规的Gradle命令启动:

gradlew test


以及:

gradle connectedAndroidTest


但是如果你不运行它们,测试有什么用?

现在你有了美丽的考试,这是一种乐趣。下一步?编写一些代码,提交,运行测试套件。一次又一次地重复这个过程。

您知道应该在每次提交后运行测试,但是手动运行是不可行的。为了确保您的应用程序的健康,它必须在每次提交后进行测试,不允许有任何例外!实现这一点的最好方法是利用 CI。和Greenhouse CI你得到了对用Kotlin编写的测试的完全支持。只要遵循我们的常规流程building an Android app我们将自动检测并运行您的所有测试,包括用Kotlin编写的测试。

知道了这一点,你就没有借口不跑出去为自己尝试一下科特林的神奇了!

相关参考卡: