Let's sniff kotlin
Let's sniff kotlin

В интернетах не так много гайдов, как можно освоить нативную мобильную автоматизацию. Многие отказываются от этой затеи, потому что сложно. Поэтому я решил написать цикл статей, которые на мой взгляд, могут помочь в освоении этого ремесла.

В первую очередь, хочется уточнить, что в дальнейшем мы будем рассматривать автоматизацию на нативном фреймворке (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 для переопределения методов и свойств из родительского класса.

  • правила и ограничения переопределения.

pen 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.

  • как модификаторы доступа влияют на видимость классов, свойств и функций.

class Person {
    private var age: Int = 0
    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 и по чуть-чуть решать.

Этого всего вам хватит, чтобы освоить темы, описанные выше. После прохождения одного из курсов, можно будет приступать к освоению андроида и тестового фреймворка. Об этом мы поговорим в следующей статье.

Комментарии (11)


  1. Getequ
    10.06.2023 17:37
    +1

    Зашёл почитать про автоматизацию - увидел гайд по Котлину...

    Вы либо теги и заголовок поменяйте, либо содержимое на 99% (вводное слово не предвещало расхождения)


    1. SergeyPotapovIT Автор
      10.06.2023 17:37

      Это цикл статей и первая статья про то, что нужно на старте для освоения автоматизации. Без знаний языка никакая автоматизация невозможна, согласитесь.


      1. Getequ
        10.06.2023 17:37

        в теории соглашусь, но на практике - так можно и биологию начать изучать с химии, ведь всё состоит из атомов


        1. SergeyPotapovIT Автор
          10.06.2023 17:37

          На практике людей, которые хотят из ручного тестирования перейти в нативную автоматизацию андроида довольно много. Этот цикл статей будет как раз для них. То, о чем вы говорите, выйдет в следующей статье.


          1. Getequ
            10.06.2023 17:37
            +1

            Тогда так и назовите статью - Котлин для автоматизатора/тестировщика. С таким стартом можно ждать во второй части основ xml-верстки на андроиде, но опять не сабжа. К сожалению. Не вводите людей в заблуждение


            1. SergeyPotapovIT Автор
              10.06.2023 17:37

              спасибо! поменял название


  1. Aquahawk
    10.06.2023 17:37

    а зачем рассказ про базу котлина называть введением в автоматизацию андроида


    1. SergeyPotapovIT Автор
      10.06.2023 17:37

      Нативная автоматизация строится на котлине или джаве и это плацдарм для вхождения. Или у вас есть другая информация?


      1. Aquahawk
        10.06.2023 17:37
        +1

        Заголовок целиком и полностью не соответствует сожержанию. И, как оказалось, я не первый вам об этом сказал. Вы можете думать всё что хотите и строить любые теории подачи материала, но то, что опубликовано сейчас - тотальный мислид.


        1. SergeyPotapovIT Автор
          10.06.2023 17:37

          учел ваши комментарии


  1. KEugene
    10.06.2023 17:37

    Наверное, было бы неплохо какое-то вводное слово про автоматизацию. С чем едят, как используют... "И если вас это все заинтересовало, давайте изучим Котлин."