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测试即可。

