На информационном ресурсе применяются рекомендательные технологии (информационные технологии предоставления информации на основе сбора, систематизации и анализа сведений, относящихся к предпочтениям пользователей сети "Интернет", находящихся на территории Российской Федерации)

GeekBrains

4 подписчика

Тестирование в Android. Часть 2: unit-тесты

В предыдущей статье мы разобрались, что такое TDD и какие тесты бывают. Сегодня у нас будет практика: мы напишем наши первые тесты.

Пишем unit-тесты

Давайте напишем наши первые unit-тесты. Создадим тестовое приложение My Test Application. Если вы ни разу не создавали своё приложение под Android, то в качестве шпаргалки пригодится статья «Как создать приложение для Android самому».

Приложение, которое вы увидите ниже, можно скачать и убедиться, что всё работает как надо.

Строковые ресурсы strings.xml и размеры:

 <string name="app_name">My Test Application</string> <string name="email_label">Your Email address:</string> <string name="save">Save</string> <string name="email_hint">Enter your Email</string> <string name="invalid_email">Invalid email</string> <string name="valid_email">OK</string>   <dimen name="activity_padding">16dp</dimen> <dimen name="main_margin">20dp</dimen>

Главный и единственный экран activity_main.xml:

 <?xml version="1.0" encoding="UTF-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:padding="@dimen/activity_padding">      <TextView        android:id="@+id/titleTextView"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_margin="@dimen/main_margin"        android:text="@string/email_label"        android:textAppearance="?android:attr/textAppearanceMedium"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent" />      <EditText        android:id="@+id/emailInput"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_gravity="center_horizontal"        android:layout_margin="@dimen/main_margin"        android:hint="@string/email_hint"        android:inputType="textEmailAddress"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/titleTextView" />      <Button        android:id="@+id/saveButton"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_margin="@dimen/main_margin"        android:text="@string/save"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/emailInput" /> </androidx.constraintlayout.widget.ConstraintLayout>

Создайте класс, проверяющий введённый email:

 package com.example.mytestapplication   import android.text.Editable import android.text.TextWatcher import java.util.regex.Pattern   class EmailValidator : TextWatcher {      internal var isValid = false      override fun afterTextChanged(editableText: Editable) {        isValid = isValidEmail(editableText)    }      override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit      override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit      companion object {          /**         * Паттерн для сравнения.         */        private val EMAIL_PATTERN = Pattern.compile(            "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +                    "\\@" +                    "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +                    "(" +                    "\\." +                    "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +                    ")+"        )          fun isValidEmail(email: CharSequence?): Boolean {            return email != null && EMAIL_PATTERN.matcher(email).matches()        }    } }

Сам главный экран MainActivity:

 package com.example.mytestapplication   import android.os.Bundle import android.widget.Button import android.widget.EditText import android.widget.Toast import androidx.appcompat.app.AppCompatActivity   class MainActivity : AppCompatActivity() {      private val emailValidator = EmailValidator()      override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)          findViewById<EditText>(R.id.emailInput).addTextChangedListener(emailValidator)        findViewById<Button>(R.id.saveButton).setOnClickListener {            if (emailValidator.isValid) {                Toast.makeText(this@MainActivity, getString(R.string.valid_email), Toast.LENGTH_SHORT).show()            } else {                val errorEmail = getString(R.string.invalid_email)                findViewById<EditText>(R.id.emailInput).error = errorEmail                Toast.makeText(this@MainActivity, errorEmail, Toast.LENGTH_SHORT).show()            }        }    } }

Убедимся, что всё работает уже на этом этапе, хотя главное — это написание тестов. Для этого у нас есть две автоматически сгенерированные папки, помимо основной. 

Вы уже знаете, что папка, помеченная androidTest, предназначена для инструментальных тестов (загляните в неё ради интереса). Нам нужна папка, помеченная просто test. Сейчас там находится единственный класс, созданный для примера: ExampleUnitTest. Давайте добавим свой класс для тестирования функционала нашего приложения, EmailValidatorTest:

 class EmailValidatorTest { package com.example.mytestapplication   import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test   class EmailValidatorTest {      @Test    fun emailValidator_CorrectEmailSimple_ReturnsTrue() {        assertTrue(EmailValidator.isValidEmail("name@email.com"))    }      @Test    fun emailValidator_CorrectEmailSubDomain_ReturnsTrue() {        assertTrue(EmailValidator.isValidEmail("name@email.co.uk"))    }      @Test    fun emailValidator_InvalidEmailNoTld_ReturnsFalse() {        assertFalse(EmailValidator.isValidEmail("name@email"))    }      @Test    fun emailValidator_InvalidEmailDoubleDot_ReturnsFalse() {        assertFalse(EmailValidator.isValidEmail("name@email..com"))    }      @Test    fun emailValidator_InvalidEmailNoUsername_ReturnsFalse() {        assertFalse(EmailValidator.isValidEmail("@email.com"))    }      @Test    fun emailValidator_EmptyString_ReturnsFalse() {        assertFalse(EmailValidator.isValidEmail(""))    }      @Test    fun emailValidator_NullEmail_ReturnsFalse() {        assertFalse(EmailValidator.isValidEmail(null))    } }

Давайте разбираться:

  • Все методы, предполагающие тестирование, должны помечаться аннотацией @Test. Так среда разработки понимает, что это методы для тестирования.
  • Названия методов должны описывать то, что они тестируют, и записываться в camel_Snake_Case.
  • Названия тестов должны быть в одном стиле, чтобы было проще искать их, если перед глазами только лог (например, при CI/CD). Главное, чтобы все в команде разработчиков называли тесты единообразно.
  • Действует правило: одно Утверждение (assert) — один тест.
  • Мы проверяем все случаи, которые придут нам в голову. Допускаем, что в нашем тестовом классе проверяются не все возможные Утверждения, но мы точно проверяем все основные.

    Как мы проверяем наши Утверждения? Мы используем метод assert из пекеджа org.junit (зависимость testImplementation 'junit:junit:4.+' в Gradle). Там есть довольно много методов, проверяющих разные значения, но нам для этого примера достаточно двух: assertTrue и assertFalse. Они принимают на вход значение и проверяют, совпадает ли оно с нашим Утверждением. В качестве значения мы передаем результат проверки класса EmailValidator.


На будущее

Помимо assertTrue и assertFalse, существуют методы: 

  • assertEquals;
  • assertNotEquals;
  • assertArrayEquals;
  • assertNull;
  • assertNotNull;
  • assertSame.

Названия методов говорят сами за себя.


Осталось запустить тесты и посмотреть, как они выполняются. Для этого достаточно кликнуть на EmailValidatorTest правой кнопкой мыши:

Тест сразу запустится, и вы увидите результат. Если какие-то тесты не пройдут проверку Утверждений, вы сразу увидите это и сможете исправить или код, или тест — в зависимости от того, что именно пошло не так. 

Конфигурация для прогона ваших тестов создалась автоматически, её можно найти в окне конфигураций запуска (там всегда есть как минимум одна конфигурация app для запуска приложения):

Не забудьте сменить конфигурацию, если хотите запустить приложение, а не тесты:

Есть опция запуска с отображением покрытия вашего приложения тестами:

После прогона тестов откроется дополнительное окно, которое показывает, насколько ваше приложение покрыто тестами:

Через двойной щелчок можно посмотреть, какие классы и как покрыты тестами:

Мы можем покрыть тестами класс EmailValidator, потому что он написан на чистом Kotlin без привязки к жизненному циклу Activity, но нет никакой возможности проверить работу методов нашей Activity. Для этого JUnit уже не подойдёт, как бы вы этого ни хотели. Для проверки работы Activity/Fragment нужны другие инструменты и виды тестов (мы ещё дойдём до этого на нашем курсе). Это ещё раз доказывает, что благодаря тестированию мы делаем свой код лучше. В этом примере благодаря тестированию мы:

  • вынесли логику проверки в отдельный класс,
  • сделали наш код переиспользуемым для любой Activity или Fragment.

Читайте больше полезных статей для начинающих Android-разработчиков:

А если затянет — приходите на факультет Android-разработки. Во время учебы вы разработаете Android-приложение и выложите его в Google Play, даже если никогда не программировали. А также освоите языки Java и Kotlin, командную разработку, Material Design и принципы тестирования.

 

Ссылка на первоисточник

Картина дня

наверх