Главная / Блог / QA automation INTERVIEW PART 2

Собеседование на должность
QA Automation Engineer.
Основы программирования.

[ Часть 2 ]
Собеседование на должность
QA Automation Engineer.
Основы программирования.

Smartiqa Article
Дата: 1 сентября 2022
Автор: Евгений Поваров

1. Проектирование

  1. Почему глобальные переменные это плохо? 1. Нарушают инкапсуляцию (к ним открыт доступ из любой части программы), добавляют лишние зависимости между компонентами. 2. Ухудшают масштабируемость 3. Способствуют возникновению трудноуловимых ошибок.
  2. Что такое инверсия управления? Явление, при котором роль главной программы в координации и последовательности действий приложения выполняет фреймворк (а не код пользователя). В этом основное отличие фреймворка и библиотеки. Библиотека - это набор функций, которые вызываются кодом пользователя, а после окончания выполнения возвращают управление пользователю. В случае с фреймворком он сам координирует и вызывает код пользователя.
  3. Закон Деметры. Каждый модуль должен обладать минимальной информированностью о других модулях. "Не разговаривай с незнакомцами!"
  4. Принцип подстановки Барбары Лисков. Наследующий класс должен дополнять, а не замещать поведение базового класса. Если класс Б унаследован от А, то мы можем заменить в программе все использования класса А на Б и при этом в работе программы ничего не изменится.
  5. Dependency hell (Ад зависимостей). Разрастание графа зависимостей библиотек. Чем опасно? Например, несколько или даже один программный продукт может косвенно потребовать разные версии одной и той же библиотеки.
  6. Хорошо спроектированное ПО должно обладать сильным сцеплением и слабой связностью. Что это значит? Сцепление - сила зависимостей внутри модуля. Связность - сила зависимостей между разными модулями. Итог: внутри модуля должны быть сильные зависимости, а снаружи - нет.
  7. Нужны ли комментарии в коде? Нужно стараться писать код так, чтобы комментарии были не нужны.
  8. Шаблоны. Singleton (Одиночка). Гарантирует существование только одного объекта класса.
Python. Singleton pattern example.
class Singleton:
    __instance = None

    # Single call check
    def __init__(self):
        print('Constructor called!')

    @staticmethod
    def instance():
        if Singleton.__instance is None:
            Singleton.__instance = Singleton()
        return Singleton.__instance
6. Шаблоны. Observer (Наблюдатель). Наблюдаем за списком объектов. При возникновении события оповещаем каждый их них.
Python. Observer pattern example.
class CameraSystemManager:

    def __init__(self):
        self.__observers = list()

    def attach(self, observer):
        self.__observers.append(observer)

    def detach(self, observer):
        self.__observers.remove(observer)

    def notify(self):
        for observer in self.__observers:
            observer.take_photo()


class AbstractObserver(ABC):
    @abstractmethod
    def take_photo(self):
        pass


class Camera(AbstractObserver):

    def __init__(self, name):
        self.name = name

    def take_photo(self):
        print(f'{self.name}: Photo is done')


camera1 = Camera('Camera 1')
camera2 = Camera('Camera 2')

manager = CameraSystemManager()
manager.attach(camera1)
manager.attach(camera2)

manager.notify()
7. Шаблоны. Abstract Factory (Абстрактная фабрика). В приведенном примере метод create_form_with_buttons(factory) создает объекты классов Form и Button на основе переданной фабрики (LinuxFactory или WindowsFactory). Класс AbstractFactory определяет фабричные методы create_form() и create_button() и передает их по наследству классам LinuxFactory и WindowsFactory.
Python. Abstract Factory pattern example.
class AbstractFactory:

    @classmethod
    def create_form(cls, name):
        return cls.Form(name)

    @classmethod
    def create_button(cls, name):
        return cls.Button(name)


class LinuxFactory(AbstractFactory):
    class Form:
        def __init__(self, name):
            self.name = f'LinuxFactory: {name}'
            self.button = []

        def add_button(self, btn):
            self.button.append(btn)

    class Button:
        def __init__(self, name):
            self.name = f'LinuxFactory: {name}'


class WindowsFactory(AbstractFactory):
    class Form:
        def __init__(self, name):
            self.name = f'WindowsFactory: {name}'
            self.button = []

        def add_button(self, btn):
            self.button.append(btn)

    class Button:
        def __init__(self, name):
            self.name = f'WindowsFactory: {name}'


def create_form_with_buttons(factory):
    form = factory.create_form('Form 1')
    button = factory.create_button('Button 1')
    form.add_button(button)
    return form


linux_form = create_form_with_buttons(LinuxFactory)
windows_form = create_form_with_buttons(WindowsFactory)

2. Языки программирования

  1. Императивные (процедурные и объектно-ориентированные) ЯП. При таком подходе программа представляет собой совокупность инструкций, которые изменяют состояние данных. Примеры: С++, Java, Ruby, Python.
  2. Функциональные ЯП. Обходимся вычислением результатов функций от исходных данных и результатов других функций, и не предполагаем явное хранение состояния. Примеры: Haksell, Erlang.
  3. Компилятор. Транслирует программу на языке высокого уровня в программу на низкоуровневом языке, близком машинному коду. На выходе - исполняемый файл. Пример: C++
  4. Интерпретатор. Построчно выполняет инструкции кода на высокоуровневом языке. Пример: Python
  5. Высокоуровневые ЯП. Легко читаются людьми. Не нужно знать, на каком оборудовании будет запускаться программа. Пример: Java, Python.
  6. Низкоуровневые ЯП. Учитывают требования архитектуры железа. Более быстрые и эффективные, но сложные для работы.
  7. Статическая / динамическая типизация. Статическая - типы данных выясняются на этапе компиляции (С++, Java). Динамическая - на этапе выполнения программы (Python, Ruby).
  8. Явная / неявная типизация. Явная - тип данных задает программист в коде (C++). Неявная - тип данных определяется компилятором / интерпретатором (Python).
  9. Структуры данных. Массив, Стек, Очередь, Связный список, Дерево, Граф, Хэш-таблица.
  10. Стек. Последний вошел (push), первый вышел (pop).
  11. Очередь. Первый вошел(append), первый вышел(pop).
  12. Связный список. Каждый узел списка - данные + указатель на следующий узел.
  13. Граф. Множество узлов, соединенных ребрами. Ребро может иметь вес.
  14. Дерево. Это граф, в котором нет циклов.
  15. Бинарное дерево поиска. Каждый узел может иметь 0, 1 или 2 потомка. Значение кладется в дерево так: последовательно идем от вершины дерева и сравниваем каждый узел в новым значением: если оно меньше узла, но кладем слева, если больше - то справа.
  16. Хэш-таблица. Можно представить как массив, в котором индекс элемента вычисляет как хэш-функция(свертка).
  17. Замыкание. Это функция, которая «запоминает» окружение, в котором она была создана.
Python. Closure example.
def counter():
    cnt = 0

    def current():
        nonlocal cnt
        cnt += 1
        print(cnt)

    return current


closure_function = counter()
closure_function()  # 1
closure_function()  # 2
18. Лямбда функция. Функция, которая 1) не имеет имени 2) возвращает значение одного выражения и 3) используется в коде единожды.
Python. Lambda function example.
def add(value):
    return lambda param: param + value


add_to_100_function = add(100)

a = add_to_100_function(5)  # 105
b = add_to_100_function(50)
19. Функция высокого порядка. Это функция, которая принимает или возвращает другие функции.
20. Циклы. for, while, do while.

3. ООП

  1. Определение ООП. Методология, в которой программа - совокупность объектов, каждый из которых - экземпляр класса, а классы образуют иерархию наследования.
  2. Класс. Шаблон для создания объектов, обеспечивающий начальные значения состояний (инициализация полей и реализация методов).
  3. Объект. Экземпляр класса, имеющий определенные поля (атрибуты) и операции над ними (методы).
  4. Поля, методы. Поле - свойство объекта, метод - функция.
  5. Абстрактный класс. Класс, для которого не реализован ОДИН или БОЛЬШЕ методов. Особенности: 1. Это класс, для которого нельзя создать объект. 2. Может содержать как обычные, так и абстрактные поля и методы. 3. Не допускает множественное наследование.
  6. Абстрактный метод (виртуальный метод). Метод класса, реализация для которого отсутствует.
  7. Интерфейс. Это абстрактный класс, у которого НИ ОДИН метод не реализован, все они публичные и нет переменных класса. Любой интерфейс - это абстрактный класс, но не наоборот.
  8. Абстракция. 1. Выделяет главные свойства предмета. 2. Отбрасывает второстепенные характеристики.
  9. Инкапсуляция. Прячет внутреннюю реализацию объекта, все взаимодействия - через интерфейс.
  10. Наследование. Создаем класс на основе существующего. Потомок наследует поля и методы родителя + добавляет свои. Выражает отношение "Является" (например, Mercedes является машиной).
Python. Inheritance example.
class Human:
    def __init__(self, name):
        self.name = name

    def speak(self, phrase):
        print(f'{self.name} сказал: \'{phrase}\'')


class Doctor(Human):
    def __init__(self, name, specialization):
        super().__init__(name)
        self.specialization = specialization

    def diagnose(self):
        super().speak(f'Ваш диагноз - ОРВИ')


if __name__ == "__main__":
    doctor_alexander = Doctor('Александр', 'Терапевт')
    doctor_alexander.diagnose()
11. Композиция. Класс, известный как составной, содержит объект другого класса, известный как компонент. Выражает отношение "Имеет" (Например, машина имеет двигатель).
Python. Composition example.
class Hobby:
    def __init__(self, title):
        self.title = title

class Human:
    def __init__(self, name, hobby_title):
        self.name = name
        self.hobby = Hobby(hobby_title)

if __name__ == '__main__':
    human = Human('Павел', 'Шахматы')
OOP. Inheritance and Composition.
ООП. Композиция и наследование.
12. Чем отличаются Наследование и Композиция? Общее: позволяют повторно использовать существующий код. Отличия:
  1. Наследование требует расширения наследуемого класса.
  2. Во многих языках запрещено множественное Наследование. А значит нельзя переиспользовать функционал нескольких разных классов. В Композиции- можно.
  3. При Композиции легче писать юнит тесты - делаем заглушки. При Наследовании это сделать сложнее - не получится заменить заглушкой родительский класс.
  4. При наследовании класс-потомок зависит от функционала класса-родителя. Ломается родитель - ломается и потомок.
Итог: когда нужно использовать класс как таковой без каких-либо изменений, рекомендуется Композиция, а когда нужно изменить поведение метода в другом классе, рекомендуется Наследование.
OOP. Inheritance and Composition. Code.
ООП. Наследование и Композиция. Код.
13. Полиморфизм. Поддержка нескольких реализаций на основе общего интерфейса. Т.е. позволяет перегружать одноименные методы родительского класса в классах-потомках.

Читайте также

4. Алгоритмы, задачи

1. Сформируйте последовательность Фибоначчи.
Python. Fibonacci sequence.
LENGTH = 7

def create_fib_sequence(length = LENGTH):
    lst = [0, 1]
    for i in range(1, length-1):
        lst.append(lst[i-1] + lst[i])
    return lst


res = create_fib_sequence()
2. Определить, является ли строка палиндромом.
Python. Palindrome function.
def is_palindrome(str):
    reversed_str = str[::-1]
    return str == reversed_str
3. Сортировка. Пузырьком. Проходимся по элементам массива и попарно сравниваем. Если левый больше правого - меняем местами.
Python. Bubble Sort.
for i in range(n-1):
    for j in range(n-i-1):
        if a[j] > a[j+1]:
            a[j], a[j+1] = a[j+1], a[j]
4. Сортировка. Вставка. Делим массив на две части (левую и правую). Левую часть считаем отсортированной. Изначально первый элемент массива оставляем в левой части, все остальное относим к правой (не отсортированной). Начинаем перемещаться по не отсортированной части. Берем первый элемент, и попарно сравнивая с соседними, ищем ему место в отсортированной части. Например, имеем массив [ 4 6 2 1 ]. Выполняем сортировку:
  1. Делим на 2 части: [ 4 | 6 2 1 ].
  2. Берем элемент 6 и ставим его на подходящее место в отсортированной части: [ 4 6 | 2 1 ].
  3. Ставим на свое место элемент 2: [ 2 4 6 | 1 ].
  4. Ставим на свое место элемент 1: [ 1 2 4 6 ].
5. Сортировка. QuickSort. Основывается на выборе опорного элемента и дальнейшей сортировке элементов на группы: меньше / равны / большего опорного. В качестве опорного элемента эффективно выбирать медианное значение. Медианное значение - значение, которое находится в середине отсортированного списка. Алгоритм:
  1. Выбираем опорный элемент.
  2. Перераспределяем элементы относительно опорного - слева меньше, справа больше.
  3. Рекурсивно выполняем п 1 и п 2 на полученных подмассивах.
  4. Рекурсия не применяется, если в подмаслила остался 1 элемент или вообще ни одного.

5. Python

  1. Какая типизация используется в Python? Динамическая
  2. Какие типы данных вы знаете? None, bool, int, float, complex, list, tuple, str, bytes, bytearray, memoryview, set, frozenset, dict.
  3. Чем отличаются изменяемые и неизменяемые данные? Изменяемый тип — сложный тип данных в объектно-ориентированном программировании, значения которого (как правило — объекты) после своего создания допускают изменение своих свойств. К неизменяемым относятся целые числа (int), числа с плавающей запятой (float), булевы значения (bool), строки (str), кортежи (tuple). К изменяемым — списки (list), множества (set), байтовые массивы (byte arrays) и словари (dict).
  4. Дан кортеж: tpl = (1, 2, 3, [1,2,3], 5). Как все знают, кортеж - это неизменяемый тип данных. Изменится ли его содержимое после выполнения команды tpl[3].append(4)? Да, изменится, так как кортеж содержит только ссылки на объекты и поэтому не может защитить объект внутри себя от изменения.
  5. Чем отличаются генераторы от итераторов? Итератор — механизм поэлементного обхода данных, а генератор позволяет отложено создавать результат при итерации. Генератор может создавать результат на основе алгоритма или брать элементы из источника данных и изменять их. Любой генератор в Python - итератор, но не наоборот. Примеры генераторов: range() (генерирует арифметическую прогрессию), enumerate() (генерирует двухэлементные кортежи: индекс + элемент итерируемого объекта)
  6. Как пройтись по всем парам ключ-значение в словаре? for key, value in dct.items():
  7. Что такое lambda в Python? Анонимная функция, то есть функция, которая имеет функционал (в одну строку), но не имеет имени. Как правило, используется как параметр функции (например filter(), sorted()) или как возвращаемое значение функции. Примеры:
Python. Lambda.
# Пример 1
def multiply(num):
    return lambda x: x + num

multiply_by_10 = multiply(10)
res = multiply_by_10(5)


# Пример 2
filtered_lst = list(filter(lambda x: x % 2 == 0, lst))
filtered_dict = dict(filter(lambda item: 'O' in item[1], dct.items()))

# Пример 3
generated_dict = {item[0]: item[1] * 2 for item in dct.items()}

# Пример 4
mapped_lst = list(map(lambda x: x * 10, lst))
8. Что такое декоратор? Декоратор - это обёртка над функцией (или другим объектом), которая изменяет ее поведение. Это удобно, т.к. не нужно менять исходный код, который не обязательно будет вашим. Использование: оценка времени работы функции, кеширование, запуск только в определенное время, задание параметров подключения к базе данных и т.д.
Python. Decorator.
# Простой декоратор
def my_decorator(my_func):

    def inner(value):
        value *= 2
        my_func(value)

    return inner


# Декоратор для функции с параметрами
def my_decorator_with_params(my_func):

    def inner(*args, **kwargs):
        print('Decorator starts...')
        my_func(*args, **kwargs)
        print('Decorator finishes...')

    return inner


@my_decorator
def print_hi():
    print('Hi!')


@my_decorator_with_params
def adder(**nums):
    print(sum(nums.values()))

# Вызов декорированных функций
print_hi()
adder(a=1, b=2)
9. Знаете ли какие-нибудь встроенные в Python декораторы? 1. @staticmethod, @classmethod. 2. @lru_cache из модуля functools. 3. @dataclass из модуля dataclass
10. Что такое контекстный менеджер? Для чего он нужен? Приведите пример. Контекстные менеджеры позволяют задать поведение при работе с конструкцией with: при входе и выходе из блока. Это упрощает работу с ресурсами в части их захвата и освобождения.
Python. Context manager.
import io

with open('1.txt', 'w') as f:
    f.write('Hello!')
11. Приведите пример обработки исключения.
Python. Exception.
import io

class MyPersonalException(Exception):
    pass

# Example 1
try:
    f.write('Hello')
except io.UnsupportedOperation as e:
    print('UnsupportedOperation!')
finally:
    print('Releasing resources')
    f.close()

# Example 2
try:
    raise MyPersonalException('This is my personal exception!')
except MyPersonalException as e:
    if 'my personal' not in str(e):
        raise e
    print('My personal exception was caught')
12. Назовите основные функции по работе с json в питоне. Запись: dump(), dumps(). Чтение: load(), loads().
Python. JSON.
import json

st = 'String'
lst = [1, 2, 3]
dct = {1: 'One', 2: 'Two'}

# Дампим словарь в json и записываем в файл
with open('1.txt', 'w') as f:
    json.dump(dct, f, indent=4)

# Формируем json на основе словаря и сохраняем в переменную
my_json = json.dumps(dct, indent=4)

# Парсим строку в словарь или список
my_str = '{"1": "One", "2": "Two"}'
my_json = json.loads(my_str)
my_str = '[1, 2, 3]'
my_json = json.loads(my_str)
13. Какой вывод ожидается после третьего вызова функции? Почему?
Python
def my_func(a, lst=[]):
    lst.append(a)
    return lst

print(my_func(10))
print(my_func(100))
print(my_func(1000))
В Python аргументы со значением по умолчанию вычисляются единожды в момент объявления функции. В нашем примере таким аргументом является список lst. И каждый раз при вызове функции - его содержимое будет меняться.
Вывод
[10]
[10, 100]
[10, 100, 1000]
1 СЕНТЯБРЯ / 2022
Как вам материал?

Читайте также