В интернетах не так много гайдов, как можно освоить нативную мобильную автоматизацию. Многие отказываются от этой затеи, потому что сложно. Поэтому я решил написать цикл статей, которые на мой взгляд, могут помочь в освоении этого ремесла.
В первую очередь, хочется уточнить, что в дальнейшем мы будем рассматривать автоматизацию на нативном фреймворке (Espresso) и надстройками над ним (Kaspresso, Ultron), а не вот эти ваши appium'ы. На мой взгляд, работа с нативным инструментом дает больше скиллов и преимуществ на длинной дистанции (если фреймворк можно применить к вашей задаче), но то мое имхо.
Внимание: Данная статья нацелена на людей, которые не знают с чего начать свой путь в андроид автоматизацию, у кого нет знаний языка программирования Kotlin. |
---|
Собственно, давайте перейдем сразу к делу и обсудим, что вам нужно сделать на этом пути в первую очередь.
Самая база-баз, которую вам нужно освоить - это язык программирования kotlin. Почему не java? Потому что есть более удобный и перспективный kotlin и большинство компаний на рынке имеют стек на этом языке.
Kotlin построен на основе Java, поэтому тестировщикам с опытом работы с Java будет легче освоить Kotlin. Многие моменты в изучении kotlin будут более понятны и пойдут проще. Но если таких знаний или опыта нет, то это не приговор. Все можно изучить при должном подходе и мотивации.
"Ну, вот, kotlin... Там же много чего, что учить - не понятно". Такие заявления я слышал ни раз и ни два. Поэтому давайте рассмотрим тот kotlin core, который по моему мнению, будет достаточен.
Переменные, константы и их инициализация, отложенная инициализация
Условные операторы
Циклы
Ссылочные типы
Nullable типы
Массив
Функции
Классы, конструкторы, объекты
Наследование
Ключевое слово this
Ключевое слово override
Ключевое слово super
Getter/Setter
Модификаторы доступа
Data class
Nested, Inner классы
Расширения
Интерфейсы
Абстрактные классы
Анонимный класс
Companion object
is, as приведение типов
Сравнение объектов и значений
Enum класс
Коллекции в Kotlin (Mutable, read-only)
Set, List, Map, Sequence + их интерфейсы
Работа с коллекциями: сортировка разных типов, перебор
Generics
Лямбда выражения, Анонимные функции, Функции высшего порядка
Scope-функции
Да, списочек немаленький, но здесь все, что действительно пригодится при применении правильных подходов в написании автоматизированных тестов и инфраструктуры под них. Если вы действительно хотите понять, что написано в коде вашими коллегами и программистами, а не копипастить код в свои тесты, то это ваш выбор.
Давайте немного подробностей про каждый пункт:
Переменные, константы и их инициализация, отложенная инициализация
Надо знать/уметь:
различие между val (неизменяемая переменная) и var (изменяемая переменная).
инициализация переменных при их объявлении или позже с использованием конструктора или метода init.
как объявлять и использовать константы с помощью ключевого слова const.
как использовать ключевое слово lateinit для отложенной инициализации переменных.
разобраться с инициализаций через делегат.
val name: String = "John" // Неизменяемая переменная
var age: Int = 25 // Изменяемая переменная
companion object {
const val PI = 3.14159 // Константа
}
lateinit var lazyVariable: String // Отложенная инициализация
val userName: String by lazy { getName() } // Инициализация через делегат
Условные операторы
Надо знать/уметь:
использование ключевых слов if, else if и else для выполнения различных блоков кода в зависимости от условий.
использование оператора when для сопоставления значения переменной с различными вариантами. Обратите внимание на использование диапазонов и оператора in в операторе when.
Оператор when в Kotlin позволяет сопоставлять значение переменной с несколькими вариантами.
val num = 10
if (num > 0) {
println("Число положительное")
} else if (num < 0) {
println("Число отрицательное")
} else {
println("Число равно нулю")
}
val result = when (num) {
in 1..10 -> "Число в диапазоне от 1 до 10"
0 -> "Число равно нулю"
else -> "Число не входит в указанные диапазоны"
}
println(result)
Циклы
Надо знать/уметь:
цикл for для итерации по диапазону или коллекции.
использование циклов while и do-while для выполнения блока кода до выполнения определенного условия. Обратите внимание на операторы break и continue для управления выполнением цикла.
for (i in 1..5) {
println(i)
}
var i = 1
while (i <= 5) {
println(i)
i++
}
i = 1
do {
println(i)
i++
} while (i <= 5)
Ссылочные типы
Надо знать/уметь:
разницу между ссылочными и примитивными типами данных (В отличии от Java (где есть примитивные типы и ссылочные) – в Kotlin нет примитивных типов данных, все типы – объектные (ссылочные)).
String
Long/Int/Short/Byte
Double/Float
Char
Boolean
нужно посмотреть методы каждого класса и понимать, что можно делать с таким типом.
Nullable типы
Надо знать/уметь:
что такое nullable типы и как они отличаются от ненулевых типов.
рассмотрите безопасный вызов (?.) для обращения к свойствам и вызова методов nullable типов.
обратите внимание на использование оператора утверждения о ненулевом значении (!!) в случаях, когда вы уверены, что значение не является null.
var nullableValue: String? = "Nullable"
nullableValue = null // Присваиваем `null` nullable переменной
val length = nullableValue?.length // Безопасный вызов свойства `length`, вернет `null`, если `nullableValue` равно `null`
val uppercaseValue = nullableValue!!.toUpperCase() // Утверждение о ненулевом значении, вызов `toUpperCase()` если `nullableValue` не равно `null`
Массив
Надо знать/уметь:
как создавать и инициализировать массивы различных типов данных.
как получить доступ к элементам массива по индексу и изменение значений элементов.
val numbers = arrayOf(1, 2, 3, 4, 5) // Создание массива целых чисел
val names = arrayOf("John", "Mike", "Sarah") // Создание массива строк
val values = intArrayOf(1, 2, 3, 4, 5) // Создание массива целых чисел
println(numbers[0]) // Выводит первый элемент массива (1)
numbers[1] = 10 // Изменение второго элемента массива на 10
Функции
Надо знать/уметь:
как объявлять функции с помощью ключевого слова fun.
передачу аргументов в функции и возвращение значений. Обратите внимание на параметры функции по умолчанию и именованные аргументы.
fun greet(name: String) {
println("Привет, $name!")
}
greet("John") // Выводит "Привет, John!"
fun addNumbers(a: Int, b: Int): Int {
return a + b
}
val sum = addNumbers(5, 3) // sum = 8
fun multiplyNumbers(a: Int, b: Int = 2): Int {
return a * b
}
val result = multiplyNumbers(4) // result = 8
Классы, конструкторы, объекты
Надо знать/уметь:
как объявлять классы и использовать конструкторы.
различные типы конструкторов, такие как основные, вторичные и конструкторы с параметрами по умолчанию. Обратите внимание на использование ключевого слова object для создания объектов класса.
class Person(val name: String, val age: Int) {
fun introduce() {
println("Меня зовут $name и мне $age лет")
}
}
val person = Person("John", 25)
person.introduce() // Выводит "Меня зовут John и мне 25 лет"
class Car(val brand: String, val model: String) {
constructor(brand: String) : this(brand, "Unknown")
}
val car = Car("Toyota")
println(car.model) // Выводит "Unknown"
object MathUtils {
fun square(number: Int): Int {
return number * number
}
}
val squaredValue = MathUtils.square(5) // squaredValue = 25
Наследование
Надо знать/уметь:
как использовать наследование для создания подклассов из существующих классов.
ключевое слово super для обращения к родительскому классу из подкласса.
open class Animal(val name: String) {
fun sleep() {
println("$name спит")
}
}
class Cat(name: String) : Animal(name) {
fun meow() {
println("$name мяукает")
}
}
val cat = Cat("Tom")
cat.sleep() // Выводит "Tom спит"
cat.meow() // Выводит "Tom мяукает"
Ключевое слово this
Надо знать/уметь:
использование ключевого слова this для обращения к текущему экземпляру класса.
различные сценарии использования this, например, для разрешения конфликта имен между параметрами и свойствами класса.
class Person(val name: String) {
fun introduce() {
println("Меня зовут $name")
}
fun changeName(newName: String) {
this.name = newName
}
}
val person = Person("John")
person.introduce() // Выводит "Меня зовут John"
person.changeName("Mike")
person.introduce() // Выводит "Меня зовут Mike"
Ключевое слово override
Надо знать/уметь:
использование ключевого слова override для переопределения методов и свойств из родительского класса.
правила и ограничения переопределения.
open class Shape {
open fun draw() {
println("Рисуем фигуру")
}
}
class Circle : Shape() {
override fun draw() {
println("Рисуем круг")
}
}
val shape: Shape = Circle()
shape.draw() // Выводит "Рисуем круг"
Ключевое слово super
Надо знать/уметь:
использование ключевого слова super для обращения к методам и свойствам родительского класса.
как super используется в конструкторах подклассов.
open class Animal(val name: String) {
open fun makeSound() {
println("Животное издает звук")
}
}
class Dog(name: String) : Animal(name) {
override fun makeSound() {
super.makeSound()
println("Собака лает")
}
}
val dog = Dog("Барсик")
dog.makeSound()
// Выводит:
// Животное издает звук
// Собака лает
**Getter/Setter **
Надо знать/уметь:
как использовать геттеры и сеттеры для доступа к свойствам класса.
особенности работы с геттерами и сеттерами, такие как вычисляемые свойства и модификаторы доступа.
class Person {
var age: Int = 0
get() = field
set(value) {
field = if (value < 0) 0 else value
}
}
val person = Person()
person.age = -5
println(person.age) // Выводит "0"
class Circle {
var radius: Double = 0.0
set(value) {
field = if (value < 0) 0.0 else value
}
get() = field
}
val circle = Circle()
circle.radius = -5.0
println(circle.radius) // Выводит "0.0"
Модификаторы доступа
Надо знать/уметь:
различные модификаторы доступа в Kotlin, такие как private, protected, internal и public.
как модификаторы доступа влияют на видимость классов, свойств и функций.
open class Person {
private var age: Int = 0 // Доступ к свойству "age" возможен изнутри класса
protected var name: String = ""
internal var address: String = ""
var phoneNumber: String = ""
}
val person = Person()
person.name = "John" // Доступ к свойству "name" возможен изнутри класса и его подклассов
person.address = "123 Main St" // Доступ к свойству "address" внутри модуля
person.phoneNumber = "123-456-7890" // Доступ к свойству "phoneNumber" везде
class Employee : Person() {
fun printEmployeeDetails() {
println(name) // Доступ к свойству "name" унаследованного класса
}
}
Data class
Надо знать/уметь:
использование data class для создания классов, предназначенных для хранения данных.
возможности, предоставляемые data class, такие как автоматическая генерация методов equals(), hashCode() и toString().
data class Person(val name: String, val age: Int)
val person1 = Person("John", 25)
val person2 = Person("John", 25)
println(person1 == person2) // Выводит "true" (сравнение по содержимому объектов)
println(person1.hashCode()) // Выводит хэш-код объекта
println(person1.toString()) // Выводит строковое представление объекта
Nested, Inner классы
Надо знать/уметь:
разницу между nested и inner классами.
доступ к членам внешнего класса из nested и inner классов.
class Outer {
private val outerProperty: Int = 10
class Nested {
fun accessOuter() {
// Недоступно: outerProperty
}
}
inner class Inner {
fun accessOuter() {
val value = outerProperty
}
}
}
val nested = Outer.Nested()
val inner = Outer().Inner()
Расширения
Надо знать/уметь:
как использовать расширения для добавления новых функций и свойств к существующим классам.
возможности расширений и их ограничения.
fun String.isPalindrome(): Boolean {
return this == this.reversed()
}
val text = "level"
println(text.isPalindrome()) // Выводит "true"
fun List<Int>.sum(): Int {
var total = 0
for (number in this) {
total += number
}
return total
}
val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.sum()) // Выводит "15"
Интерфейсы
Надо знать/уметь:
использование интерфейсов для определения контрактов, которым должны соответствовать классы.
изучить реализацию интерфейсов классами и множественное наследование интерфейсов.
interface Drawable {
fun draw()
}
class Circle : Drawable {
override fun draw() {
println("Рисуем круг")
}
}
val circle = Circle()
circle.draw() // Выводит "Рисуем круг"
Абстрактные классы
Надо знать/уметь:
использование абстрактных классов для определения общей структуры классов и невозможности создания экземпляров абстрактного класса.
рассмотреть абстрактные методы и свойства, которые должны быть реализованы в подклассах.
abstract class Shape {
abstract fun calculateArea(): Double
abstract fun calculatePerimeter(): Double
}
class Circle(private val radius: Double) : Shape() {
override fun calculateArea(): Double {
return Math.PI * radius * radius
}
override fun calculatePerimeter(): Double {
return 2 * Math.PI * radius
}
}
val circle = Circle(5.0)
println(circle.calculateArea()) // Выводит площадь круга
println(circle.calculatePerimeter()) // Выводит периметр круга
Анонимный класс
Надо знать/уметь:
создание анонимных классов без явного определения подкласса.
использование анонимных классов для реализации интерфейсов или расширения классов.
interface OnClickListener {
fun onClick()
}
val button = Button()
button.setOnClickListener(object : OnClickListener {
override fun onClick() {
println("Кнопка нажата")
}
})
Companion object
Надо знать/уметь:
использование companion object для создания статических методов и свойств в классе.
как companion object может быть использован для создания фабричных методов или доступа к общим ресурсам.
class MathUtils {
companion object {
fun square(number: Int): Int {
return number * number
}
}
}
val result = MathUtils.square(5)
println(result) // Выводит "25"
is, as приведение типов
Надо знать/уметь:
использование оператора is для проверки типа объекта и оператора as для явного приведения типов.
безопасное приведение типов с использованием оператора as?.
fun printLength(value: Any) {
if (value is String) {
println(value.length)
}
}
val text: Any = "Hello"
printLength(text) // Выводит длину строки
val number: Any = 42
val doubleNumber = number as? Double
println(doubleNumber) // Выводит "null"
Сравнение объектов и значений
Надо знать/уметь:
разницу между сравнением объектов и значений.
использование операторов сравнения == и ===.
val number1 = 5
val number2 = 5
val number3: Int? = 5
println(number1 == number2) // Выводит "true" (сравнение значений)
println(number1 === number2) // Выводит "true" (сравнение ссылок)
println(number1 === number3) // Выводит "true" (сравнение ссылок)
Enum класс
Надо знать/уметь:
использование enum классов для определения ограниченного набора значений.
использование свойств и методов в enum классах.
enum class Color {
RED, GREEN, BLUE
}
val color = Color.GREEN
println(color) // Выводит "GREEN"
enum class Direction(val degrees: Int) {
NORTH(0),
EAST(90),
SOUTH(180),
WEST(270)
}
val direction = Direction.NORTH
println(direction.degrees) // Выводит "0"
Коллекции в Kotlin (Mutable, read-only)
Надо знать/уметь:
понимать разницу между изменяемыми (mutable) и неизменяемыми (read-only) коллекциями.
изучить основные типы коллекций, такие как List, Set и Map.
// Неизменяемая коллекция
val list: List<String> = listOf("apple", "banana", "orange")
val set: Set<Int> = setOf(1, 2, 3, 4, 5)
val map: Map<String, Int> = mapOf("one" to 1, "two" to 2, "three" to 3)
// Изменяемая коллекция
val mutableList: MutableList<String> = mutableListOf("apple", "banana", "orange")
val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3, 4, 5)
val mutableMap: MutableMap<String, Int> = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
// Изменение элементов в изменяемой коллекции
mutableList.add("grape")
mutableSet.remove(3)
mutableMap["four"] = 4
Set, List, Map, Sequence + их интерфейсы
Надо знать/уметь:
основные операции и функции, доступные для Set, List, Map и Sequence.
различия между разными реализациями этих интерфейсов.
// Set
val set: Set<Int> = setOf(1, 2, 3, 4, 5)
println(set.size) // Выводит размер множества
println(set.contains(3)) // Выводит "true" если множество содержит элемент 3
// List
val list: List<String> = listOf("apple", "banana", "orange")
println(list.size) // Выводит размер списка
println(list.get(1)) // Выводит элемент на позиции 1
println(list.indexOf("banana")) // Выводит индекс элемента "banana"
// Map
val map: Map<String, Int> = mapOf("one" to 1, "two" to 2, "three" to 3)
println(map.size) // Выводит размер словаря
println(map["two"]) // Выводит значение для ключа "two"
println(map.containsKey("three")) // Выводит "true" если словарь содержит ключ "three"
// Sequence
val sequence: Sequence<Int> = sequenceOf(1, 2, 3, 4, 5)
val filteredSequence = sequence.filter { it > 2 }
val transformedSequence = filteredSequence.map { it * 2 }
val result = transformedSequence.toList()
println(result) // Выводит [6, 8, 10]
Работа с коллекциями: сортировка разных типов, перебор
Надо знать/уметь:
методы и функции, доступные для сортировки коллекций разных типов.
итерацию и перебор элементов коллекций.
// Сортировка списка чисел
val numbers = listOf(5, 3, 8, 1, 7)
val sortedNumbers = numbers.sorted()
println(sortedNumbers) // Выводит [1, 3, 5, 7, 8]
// Сортировка списка строк
val names = listOf("Alice", "Bob", "Charlie", "David")
val sortedNames = names.sortedBy { it.length }
println(sortedNames) // Выводит [Bob, David, Alice, Charlie]
// Итерация и перебор элементов списка
val fruits = listOf("apple", "banana", "orange")
for (fruit in fruits) {
println(fruit)
}
Generics
Надо знать/уметь:
использование обобщений для создания универсальных классов и функций.
ограничения типов и варианты проекции.
// Универсальный класс
class Box<T>(val value: T)
val intBox = Box(42)
val stringBox = Box("Hello")
// Универсальная функция
fun <T> printValue(value: T) {
println(value)
}
printValue(10) // Выводит 10
printValue("Hello") // Выводит "Hello"
Лямбда выражения, Анонимные функции, Функции высшего порядка
Надо знать/уметь:
использование лямбда выражений, анонимных функций и функций высшего порядка.
изучите передачу функций в качестве аргументов и их возвращение из других функций.
// Лямбда выражение
val sum = { a: Int, b: Int -> a + b }
println(sum(5, 3)) // Выводит 8
// Анонимная функция
val product = fun(a: Int, b: Int): Int {
return a * b
}
println(product(5, 3)) // Выводит 15
// Функция высшего порядка
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
val result = calculate(5, 3) { a, b -> a - b }
println(result) // Выводит 2
Scope-функции
Надо знать/уметь:
использование scope-функций (let, run, with, apply, also) для работы с объектами в ограниченной области видимости.
различия между разными scope-функциями и их применение в разных ситуациях.
// Функция let
val length = "Hello".let { text ->
text.length
}
println(length) // Выводит 5
// Функция run
val result = run {
val a = 5
val b = 3
a + b
}
println(result) // Выводит 8
// Функция with
val person = Person()
val nameLength = with(person) {
name.length
}
println(nameLength) // Выводит длину имени объекта person
// Функция apply
val person = Person().apply {
name = "Alice"
age = 30
}
// Функция also
val numbers = mutableListOf(1, 2, 3)
val modifiedNumbers = numbers.also { list ->
list.add(4)
list.remove(2)
}
println(modifiedNumbers) // Выводит [1, 3, 4]
Для тех, кому нужны видосики и чтоб разжевали, могу порекомендовать те ресурсы, которыми пользовался я (не реклама):
Javabegin - тут надо найти Kotlin, как часть огромного курса фуллстак разработчика. Материал очень хорошо записан, но платно.
Stepik - Kotlin - быстрый старт - тоже платный курс, но оно того стоит.
developer.alexanderklimov - как доп ресурс к курсам или сурс для самообучения. (бесплатно)
Metanit - как доп материал, очень хорошо описано. (бесплатно)
Руководство по языку Kotlin - переводы оф. документации. (для кого-то это лучший сурс для самообучения) (бесплатно)
Kotlin за час. Теория и практика. - как доп материал для тех, кто писал на java (бесплатно)
Курс обучения Kotlin с нуля - курс по котлин с нуля (бесплатно)
Самое важное, если вы берете курсы, то там будет некая практика по ходу прохождения видео. Если вы учитесь сами, то проще брать легкие задачи на сайтах по типу Leetcode и CodeWars и по чуть-чуть решать.
Этого всего вам хватит, чтобы освоить темы, описанные выше. После прохождения одного из курсов, можно будет приступать к освоению андроида и тестового фреймворка. Об этом мы поговорим в следующей статье.