Тестові для Python-інженерів: приклади, критерії оцінки та причини відмов кандидатам

💡 Усі статті, обговорення, новини про Python — в одному місці. Приєднуйтесь до Python спільноти!

Як дізнатися, що кандидат на вакансію дійсно вміє виконувати свою роботу? Не завжди теоретичні знання свідчать про здатність впоратись із реальними завданнями. Ми звернулися до IT-компаній, які наймають Python-інженерів, і попросили розповісти про їхні тестові завдання, незалежно від того, чи йдеться про онлайн-кодинг, чи про домашню роботу.

«Невідповідність вимогам завдання і порушення дедлайнів є основними причинами відмов»

Максим Линник, Principal Python Engineer в Intellias

Я не люблю давати тестові. Звик проводити співбесіди за матрицею компетенцій. Тестові завдання даю, тільки якщо на цьому наполягає клієнт. Є два формати: онлайн-кодинг і домашнє завдання. Якщо людина дійшла до етапу, коли їй дали тестове додому, виконала це завдання і воно відповідає вимогам, я здебільшого схвалюю найм цієї людини. На жаль, в індустрії трапляються випадки, коли людині дають тестове завдання, вона його робить, три тижні від компанії немає жодної відповіді, а потім надходить відмова: найняли іншого кандидата, який виконав завдання краще. Ми в Intellias так не робимо.

Перевіряючи тестове, я насамперед звертаю увагу на те, чи воно виконане відповідно до завдання. Також важить читабельність коду та правильне форматування, наприклад, без триповерхових конструкцій. Невідповідність вимогам завдання та порушення дедлайнів є основними причинами відмов. Наведений приклад тестового Senior-розробник може виконати за кілька годин. Можливо, з використанням ChatGPT, адже це не заборонено. А це завдання даємо на вихідні або ж на цілих три дні. Я завжди пишу фідбек за підсумками співбесід на випадок, якщо кандидату буде цікаво дізнатися про свої слабкі сторони. Туди ж вписую фідбек на тестове.

Умови тестового завдання

Написати вебсервіс (бекенд) на Django | FastAPI | Flask, який буде мати декілька ендпойнтів. Тематика може бути будь-яка, але основні вимоги:

  1. Не використовувати ORM (всі операції з БД виконати через обгортки, додаткові бали будуть за використання класів-обгорток для результатів з бази, замість словників).
  2. В’юшки (або контролери) ендпойнтів бажано оформлювати у класах замість функціонального підходу.
  3. Якщо кандидат вирішить використовувати middleware для обробки реквестів і респонзів, то вітаються самописні.
  4. Є тести на код + код відформатований.

Додадуть балів:

  • виконання завдання в асинхронному підході;
  • доданий докер-файл (або докер-компоуз) для розгортання сервісу однією командою;
  • використання сучасних інструментів для тестування і форматування (pytest, Ruff, etc.).

Часто клієнти можуть давати свої тестові завдання, включаючи складний лайвкодинг. Пригадую одне з них, коли треба було розпарсити два величезних CSV-файли та зібрати в один, прибравши всі дублікати. Файли не вміщалися в оперативну пам’ять, тому потрібно було написати функцію, яка читатиме їх порядково та перевірятиме на наявність дублікатів. На виконання давалось дві години. Важливо було, щоб фахівець розумів, як змінюється складність задачі залежно від того, посортовані файли чи ні. Очікувалося, що кандидати мають застосувати merge sort, а завдяки Generators аналізуватимуть файл ітеративно.

«Більшості наше ТЗ подобається, тому що воно нестандартне»

Макс Нікітенко, Principal Technical Lead, Solution Architect в Namecheap/ZONE3000

Я даю рекрутерам п’ять запитань, на які кандидати мають відповісти, щоб пройти скринінг перед технічною співбесідою. Одне з питань: який патерн проєктування в Python є найбільш доречним, а від якого — найменше користі. Найчастіше у кандидатів спливає в пам’яті синглтон, причому його можуть назвати і там, і там. Синглтон або ненавидять, або одержимі ним.

Про те, що в мову Python вбудований декоратор, знають всі. Про те, що те саме стосується синглтону, замислюються одиниці. Усі модулі в Python фактично є синглтонами. Частина чисел теж синглтони, вони кешуються та мають одні й ті самі лінки. У Python-розробника зазвичай немає потреби писати реалізацію синглтона для роботи. Але це має сенс для того, щоб розуміти, як він влаштований і які можуть бути підводні камені, як-от переініціалізація.

Зразок потенційного тестового завдання

"""

Синглтон — це породжувальний патерн проєктування, який гарантує, що у класу є тільки один екземпляр. Чомусь багато хто любить запитувати про цей патерн на інтерв'ю, хоча він є вбудованим в Python із коробки, якщо можна так сказати.

Що ж, якщо ви його так любите, то пропоную «пограти у гру»(c) і відповісти на «питання життя, Всесвіту і взагалі» (c).

Для цього треба виправити assertations і порахувати, скільки синглтонів ви знайшли в цій програмі.

"""

class Foo:
    instance: 'Foo'
    def __new__(cls, *args, **kwargs) -> 'Foo':
        """ Створює новий екземпляр класу """
        if not hasattr(cls, 'instance'):
            setattr(cls, 'instance', super(Foo, cls).__new__(cls))
        return cls.instance
    def __init__(self, value: int) -> None:
        """ Ініціалізує клас """
        self.x = self._execute_hard_calculation(value)
    @staticmethod
    def _execute_hard_calculation(value: int) -> int:
        """ Виконує складні обчислення """
        return value * ((((100 * 2) // 4 + 5 * 20 - (50 // 2) + 10 ** 2 - (75 % 25)) // value) + 25)
if __name__ == '__main__':
    a = Foo(1)  # a.x [250] b.x [undefined]
    b = Foo(38 + 4)  # a.x [1260] b.x [1260]
    mg_result_1 = 250
    mg_result_2 = 1260
    assert id(a) == id(b)  # [True], оскільки екземпляр той самий через Singleton
    assert id(a.x) == id(b.x)  # [True], оскільки екземпляр той самий і значення однакове
    assert id(a.x) == id(mg_result_1)  # False, тому що __init__ викликається двічі, і останнє значення `x` перезаписується
    assert id(a.x) == id(mg_result_2)  # False, оскільки Python кешує тільки перші 256 позитивних цілих чисел
    assert b.x == mg_result_1  # False, тому що __init__ викликається двічі, і останнє значення `x` перезаписується
    assert b.x == mg_result_2  # True, тому що __init__ викликається двічі, і останнє значення `x` перезаписується
    print(f"І відповідь на питання життя, Всесвіту і взагалі це - `{b.x / 30:.0f}`")

"""

Клас Foo: Це явний синглтон, реалізований через патерн Singleton у програмі.

Кешовані числа: В Python числа в діапазоні від -5 до 256 кешуються і є синглтонами.

Об'єкти стандартних бібліотек: До цієї категорії належать такі об'єкти, як None, True, False, які також є сінглтонами в Python.

Стандартні функції Python (print, id, isinstance, тощо) та типи (int, str, object, і т.д.), які є синглтонами, оскільки вони мають унікальні екземпляри під час виконання програми.

Кінцеве число може змінюватися в залежності від версії Python та конкретної реалізації стандартних бібліотек або оптимізацій, що можуть змінити поведінку кешування чи управління пам'яттю.

"""

Що перевіряють у кандидата

Реальне тестове завдання, яке я даю кандидатам, це макет справжнього проєкту. Колись я починав з того, що на співбесідах давав людям завдання написати код на листку паперу. Це могла бути задача описати шахову фігуру у вигляді класу. Але з часом програмувати на папірцях стало не модно. Усі звикли до IDE, а розв’язати задачу на папері кандидатам стало складно. Якийсь час ми запитували, як працює декоратор, і пропонували написати імплементацію, яка приймає параметри, адже ще не всі знали про проблеми із замиканням тощо. Нині ж будь-хто може написати будь-який варіант декоратора.

Читайте також 👇У підсумку я створив проєкт, який дає змогу зрозуміти, як людина орієнтується в чужому коді. У проєкті є як хороший, так і поганий код. Є тести, які повністю покривають завдання. І якщо кандидат хоче перевірити себе після співбесіди, він може це зробити за допомогою тестів. Цим тестовим я хочу дати людині розуміння рівня коду, який ми пишемо в команді, і навчити чогось нового. Наприклад, я використав BDD-тести. 90% розробників, яких я зустрічав, не знали, що в Python є інструменти для BDD.

Для нашої команди важливо, як людина робить декомпозицію задачі. І чи писатиме вона в тому ж стилі, в якому виконаний проєкт. Є розробники, які люблять програмувати тільки у функційному стилі.

На нашому проєкті потрібно розуміти, як Python працює під капотом і як побудована клієнт-серверна архітектура. Тестовий проєкт містить низькорівневий код, який дає змогу перевірити ці знання.

Чому можна отримати відмову після тестового

Під час виконання тестового я оцінюю як hard, так і soft skills, а також рівень підготовки. Буває, кандидат заявляє, що не готовий, наприклад: «У мене ноутбук дружини», — хоча його попереджали про тестове. Камон. Досить часто за тулсетом кандидата можна зрозуміти, чи працював він над великими проєктами. До речі, я вважаю, що Junior, Middle та Senior повинні писати код однаково добре, а от спосіб розв’язку задачі відрізнятиметься.

Більшості наше ТЗ подобається, тому що воно нестандартне. Переважно ми даємо завдання під час технічної співбесіди. Буває, що людина нервує, і як виняток з правил можемо дати тестове додому.

Причиною більшості відмов є погані софт-скіли. Відштовхує нахабність, наприклад, кандидат каже: «Я не хочу відповідати на це запитання, це думати треба». Буває, що агресивно реагують на критику: «Що ти мені розповідаєш?». Дуже неприємно з таким стикатися, і в нашій команді так не заведено. Кожен може помилятися, але треба вміти про це сказати, не гаркаючись.

Може бути так, що людина не впоралась із завданням. Такому кандидату ми кажемо: «Повчись і приходь знову». Я пишу фідбек на теоретичну частину, практичні питання, тестове завдання і софт-скіли. Зараз в нашій команді відкрита вакансія Senior Python Developer — і може відгукнутися джун, який заявляє, що він сеньйор. Останні два роки це трапляється часто. Такій людині ми можемо запропонувати посаду, яка відповідає її реальному рівню. Навпаки ще не було: реальний сеньйор за мого досвіду ще не оголошував себе джуном.

«Вага тестового на співбесіді — 20% оцінки»

Віталій Ковальчук, Principal Engineer в Avenga

Я не даю завдання на кодинг чи алгорими, тому що це досить стресово для кандидатів, і не всі можуть добре себе показати в таких умовах. Крім того, це вимагає багато часу, а я прагну вкластися в 90 хвилин на інтерв’ю на рівень Senior та 60-75 хвилин на Middle. Наше завдання передбачає, що кандидат має зробити код-рев’ю. Міркування людини щодо чужої роботи є одним з показників її здатності писати зрозумілий код.

Зразок тестового завдання

class DataProcessor:

    data_storage = []

    def __init__(self, data=[]):

        self.data = data

        self.result = None

    async def add_data(self, new_data):

        for item in new_data:

            self.data.append(item)

        self.data.sort()

    async def process_data(self):

        total = 0

        for i in range(len(self.data)):

            total = total + self.data[i]

        if len(self.data) > 0:

            self.result = total / len(self.data)

        else:

            self.result = 0

        DataProcessor.data_storage.append(self.result)

        return self.result

    async def save_to_file(self, filename):

        file = open(filename, 'w')

        file.write(str(self.data))

        file.close()

    async def load_from_file(self, filename):

        file = open(filename, 'r')

        content = file.read()

        self.data = eval(content)

        file.close()

Що перевіряють у кандидата

Про наведене завдання можна говорити багато, а єдиної правильної відповіді немає. Тут є як дрібні помилки, так і концептуальні проблеми.

Наприклад, mutable-об’єкти краще не використовувати ані в тілі класу, ані як атрибут за замовчуванням, тому що може бути витік пам’яті. Цикли for не найшвидші в Python. А ще тут незрозуміло, для чого були відсортовані дані.

Проблему ділення на нуль можна вирішити за допомогою try-except. У програмуванні є такий принцип: проси пробачення, а не дозволу. Ми робимо спробу виконати код, і тільки якщо вона невдала, застосовуємо дефолт. Це виглядатиме елегантніше, а можливо і працюватиме швидше залежно від типу даних.

Невиправдано тут і те, що результат є атрибутом самого класу. А щодо роботи з файлами, то в Python є контекст-менеджер, який серед іншого обробляє помилки. Ще краще, якщо людина знає про pathlib.

Python прожерливий у пам’яті, і буде плюсом, якщо кандидат згадує про оптимізації.

Збереження даних як Python-структури без застосування JSON чи CSV може обернутися проблемами з безпекою, та й читати таке не сильно приємно. Якщо даних дуже багато, можна розглянути бінарні формати.

В Python немає строгої типізації, але як і в будь-якій мові хорошим тоном було би додавати анотацію типів. Це допомагає читати код і підсвітлює помилки. До речі, про анотацію типів кандидати згадують рідко.

Асинхронні функції в контексті конкретної задачі не потрібні. Прекрасно, коли кандидат може поміркувати, в яких випадках вони би знадобились.

Вага тестового на співбесіді становить 20% оцінки. Тут є багато нюансів, і кандидат може звернути увагу не на все. Це нормально. Ми оцінюємо критичне мислення. Не висуваємо таких вимог, як Amazon і Google, але для нас важливо найняти інженера, а не програміста одного фреймворка. Можливо, він не знає якусь базу даних, але має розуміти загальні принципи.

Автор матеріалу — Дмитро Скороход.

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось16
До обраногоВ обраному13
LinkedIn

11 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

В реалізації сінглтона є помилка, яка показує що код не тестувався. В мові програмування Python немає функції printf.
Повинно бути так:

print(f"І відповідь на питання життя, Всесвіту і взагалі це -{b.x / 30:.0f}")

Дякую за уважність, Романе! Виправимо. Це моя помилка. Спочатку було правильно, але редагуючи текст я перенабрав цей рядок, що призвело до одруківки: f( замість (f. Беру собі на замітку, що треба бути акуратнішим з кодом.

Вага тестового на співбесіді становить 20% оцінки

А можна опублікувати все РСО? Чи є бали за відвідування? Чи є бонуси за проходження курсів? І взагалі скільки треба набрати, щоб отримати залік автоматом і не витрачати час на співбесіду?

А якщо серйозно, то сподіваюсь, що це не пряма цитата людини, що проводить інтерв’ю, а просто незграбне переформулювання. На співбесіді немає балів та відсотків. Співбесіда — це не екзамен.
В тестовому, як і при використанні будь-якого з інструментів, інтерв’юер може помітити червоний прапорець, що множить на 0 всі інші «виміри». Так само там може бути, щось, що переважить купу недоліків, наприклад у теоретичних знаннях.

Як вже казали нижче, останні 2 «тестових» по факту не є такими, це просто завдання, які можна дати на співбесіді і це займе на кілька хвилин більше ніж їх обговорення. Знову ж тут інтерв’юер має вміти правильно інтерпретувати помилки та неточності у виконанні (так само це треба буде і з домашнім завданням)

Так, напевно, трішки грубе формулювання. Я говорив про те, що на співбесіді оцінюються різні теми, і Python, і в тому числі кодинг/рев’ю завдання — це просто один з критеріїв на рівні з іншими темами в оцінюванні, і воно не має критично більшого впливу за інші оцінювані теми. Звісно, якщо вакансія Python-інженера, то кандидат має знати Python, але він також має на належному рівні володіти й іншими знаннями. І так це саме завдання на співбесіду.

І так це саме завдання на співбесіду.

Це багато що змінює, норм завдання. Я не любитель такого, але воно дозволяє знитя ризики, що людина застопориться на написанні простого коду через стрес.
По мідл+ питань немає. А джунам (без досвіду або до 1 року досвіду) ви його даєете? Як вони вирішують? Чи не відчувається, що відповідають завченими патернами без розуміння?

>

А джунам (без досвіду або до 1 року досвіду) ви його даєете? Як вонивирішують?

Є простіші варіанти, але ідея схожа. Крім того, це діалог, завжди можна направити людину.
>

Чи не відчувається, що відповідають завченими патернами без розуміння?

Частіше всього ні, але якщо бувають такі випадки, то уточнюючі питання це розкривають. Наприклад:

def save_to_file

Більшість скаже про контекст-менеджер, але завжди можна запитати, чи теперішній код не буде еквівалентом контекст-менеджеру. Якщо відповідають, що ні, то можна запитати, що буде еквівалентом.

Останнє тестове мені сподобалось, економить трохи часу на співбесіді обом сторонам, не займає багато часу. Таке можна давати і на інтерв’ю, але через стрес в таких завданнях кандидат може пропустити деталі. З приводу об’ємних технічних завдань, вважаю що це може нашкодити самому процесу, особливо з досвідченими кандидатами: досвідченому кандидату дешевше/легше/швидше рухатися з іншими компаніями без тестового завдання в процесі найму, ніж братися за це тестове. Ось взяти наприклад той самий «вебсервіс з ендпоінтами». Зазвичай, максимум що є в ТЗ — описані які мають бути ендпоінти(чи що має робити сервіс) та база даних. А далі — «твір на тему ...». Витрачаєш час на сетап проекту(адже від використання темплейтів/бойлерплейтів можуть схаритись), правильну структуру, лінтери, пре-коміт хуки, докери, компози, і не знаєш що з того взагалі оцінять, і чи взагалі твоє тестове хтось подивиться. Та ще й приколупатись можуть абсолютно до будь чого.

Так, це завдання саме на інтерв’ю, ми не даємо «на дому» взагалі. З власного досвіду я сам кілька разів відхиляв вакансію не через те, що вона погана, а через довгий процес найму, в тому числі через довгі тестові завдання.
По завданню задача не стоїть набрати максимальну кількість очок, тут більше про те, як кандидат в цілому оцінює код, а важливі моменти, які кандидат дійсно може через стрес пропустити, завжди можна підкреслити додатковими питаннями.

В останньому прикладі, де сказано про «Ще краще, якщо людина знає про passlib.» мова йде про pathlib, а не passlib, так? Бо з контексту там йдеться про шлях файлу, а не хешування паролів.

мене теж трохи здивувало, аж пішов дізнаватись, чому я не знаю, що таке passlib :)

Так, ви праві. Це моя помилка. Виправимо.

Підписатись на коментарі