Android单元测试– JUnit4
在本教程中,我们将讨论Android单元测试,该测试构成了Android应用程序开发的组成部分。
我们将使用JUnit4具体实施本地单元测试。
Android单元测试
顾名思义,单元测试就是测试代码的每个单元。
要构建可靠的应用程序,必须进行单元测试。
这是构建高质量应用程序时的重要元素。
单元测试由测试用例组成,这些用例用于检查代码的业务逻辑。
很多时候,当您被要求或者计划在一个正在运行的应用程序中添加功能时,只是意识到它破坏了代码的其他部分。
每次重构代码或者其中添加新内容时,都无法手动执行所有测试。
这就是单元测试为我们提供帮助的地方。
它执行快速的自动测试,并在任何测试失败时提醒您。
您可以快速找出问题所在。
一般而言,测试大致分为以下几种类型:
- 单元测试
- 整合测试
- UI测试
单元测试是最小的(单独地)并且执行时间最少。
以下是量化Google文档中每种测试类型的图示:
以下是Android中使用的一些测试框架:
- JUnit
- Mockito
- Powermock
- Robolectric
- Espresso
- Hamcrest
每当您启动一个新的Android Studio项目时,build.gradle(也称为Expresso Dependency)中已经存在JUnit依赖关系。
在您的Android Studio项目中,以下是src文件夹中的三个重要软件包:
app/src/main/java /
-主Java源代码文件夹。app/src/test/java /
-本地单元测试文件夹。app/src/androidTest/java /
-仪器测试文件夹。
test文件夹是编写JUnit4测试用例的位置。
本地单元测试不能具有Android API。
测试文件夹类仅在JVM上编译和运行。
仪器测试在Android设备或者仿真器上运行。
为了创建测试,我们需要使用TestCase扩展类,或者在方法上方添加注解@Test。
TestCase主要用于JUnit3。
展望未来,我们只需要设置注释即可。
让我们创建一个新的Android Studio项目,其中编写第一个单元测试。
在以下部分中,我们创建了一个基本应用程序,其中将检查字符串是否为有效的电子邮件地址。
为此,我们还将在"活动"中创建一个EditText。
通过编写单元测试,我们将了解如何通过涵盖各种最终条件来改善应用程序逻辑。
代码
让我们为" activity_main.xml"编写代码。
布局如下:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <EditText android:id="@+id/inEmail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="textEmailAddress" android:text="Enter your email here" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="CHECK" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/inEmail" </android.support.constraint.ConstraintLayout>
构建测试用例并完成TDD(测试驱动开发)之后,我们将在稍后查看MainActivity.java代码。
Utils.java类中的代码是:
package com.theitroad.androidunittestingjunit4; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Utils { private static final int MILLIS = 1000; public static boolean checkEmailForValidity(String email) { Matcher matcher = VALID_EMAIL_ADDRESS_REGEX.matcher(email); return matcher.find(); } private static final Pattern VALID_EMAIL_ADDRESS_REGEX = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); public static Date calendarDate(long epocSeconds) { Calendar c = Calendar. getInstance(TimeZone.getTimeZone("UTC")); c.setTimeInMillis(epocSeconds * MILLIS); return c.getTime(); } }
现在让我们为这两种方法编写单元测试:test/java文件夹中的checkEmailForValidity和calendarDate。
单元测试用例1
创建一个新的Java文件UtilsTest.java并添加以下代码:
package com.theitroad.androidunittestingjunit4; import org.junit.Assert; import org.junit.Test; import java.util.Date; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; public class UtilsTest { @Test public void testIsEmailValid() { String testEmail = "[email protected]"; Assert.assertThat(String.format("Email Validity Test failed for %s ", testEmail), Utils.checkEmailForValidity(testEmail), is(true)); } @Test public void testCheckDateWasConvertedCorrectly() { long inMillis = System.currentTimeMillis(); Date date = Utils.calendarDate(inMillis); assertEquals("Date time in millis is wrong", inMillis * 100, date.getTime()); } }
在第一个测试中,我们调用在main/java文件夹中定义的方法checkEmailForValidity。
我们通过一个测试字符串来检查assertThat
方法内部的有效性。
在第二个测试用例中,我们故意将timeInMillis乘以100而不是1000,从而错误地将timeInMillis转换为以秒为单位的时间。
这里我们使用assertEquals
函数。
您可以通过gradle运行单元测试方法,也可以单击它们旁边的运行图标。
使用gradle只需在Android Studio的终端上执行命令gradlew test即可。
让我们看一下每种方法运行时的输出。
除了上述两个assert方法外,还有很多其他方法:
看起来" assertThat"和" assertEquals"具有类似的方法定义。
两者都有一个可选的第一个参数,它是测试失败时显示的消息,后跟预期和实际值。
具有讽刺意味的是,assertThat和assertEquals彼此完全不同。
assertThat与assertEquals
assertThat包含了Hamcrest库,该库提高了代码的可读性。
Hamcrest库由称为匹配器的静态方法组成。
让我们比较一下这两种方法的语法:
assertEquals(expected, actual); assertThat(actual, is(equalTo(expected)));
assertThat首先具有实际值。
多亏了is方法,它提高了可读性。
在assertEquals方法中,您可以很容易混淆并互换实际和期望的参数位置。
断言是安全且简短的类型。
示例:假定foo是以下代码中的对象实例
assertTrue(foo.contains("someValue") && foo.contains("anotherValue"));
用assertThat编写时,同样的事情变成:
assertThat(foo, hasItems("someValue", "anotherValue"));
因此,assertThat应该是其他方法的首选方法。
回到我们的应用程序,让我们添加另一个测试用例。
单元测试用例2
@Test public void testEmailValidityPartTwo() { String testEmail = " [email protected] "; Assert.assertThat(String.format("Email Validity Test failed for %s ", testEmail), Utils.checkEmailForValidity(testEmail), is(true)); }
其中我们在测试字符串之外添加了白色间距。
显然,这将失败。
这提醒我们在checkEmailForValidity方法中修剪白色间距。
我们可以在Utils.java类中的字符串上设置trim()
方法。
public static boolean checkEmailForValidity(String email) { email = email.trim(); Matcher matcher = VALID_EMAIL_ADDRESS_REGEX.matcher(email); return matcher.find(); }
您的MainActivity.java代码如下所示:
package com.theitroad.androidunittestingjunit4; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final EditText editText = findViewById(R.id.inEmail); Button button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { boolean isValid = Utils.checkEmailForValidity(editText.getText().toString()); if (isValid) { Toast.makeText(getApplicationContext(), "Email is valid", Toast.LENGTH_LONG).show(); } else { Toast.makeText(getApplicationContext(), "Email not valid", Toast.LENGTH_LONG).show(); } } }); } }
无需运行您的应用程序来测试电子邮件是否有效,我们只需运行我们之前编写的JVM测试即可。