Як правильно робити Dependency Injection, щоб легко Unit-тестувалося?

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

Я розмістив запитання на stackoverflow, але там довго треба чекати і відповіді інколи дивні.

Надіюся швидше буде нормальна відповідь тут.

======

I am going to add Unit testing for my Python application.

I need to understand how to do a unit testing for methods where some object is created and something is called from it. I need to replace such objects with some injected objects instead of creation. And inject a Mock in my test.

Example of pseudo code

To test this with unit tests i will need to inject the mock object of RandomOperationSelector.

Best practices i have found on the web suggest me to do like this

Or as alternative

But for me this is not acceptable. I do not want to care about creation of the instance of RandomOperationSelector every time when i want to use MyClass. Because in reality there are usually more classes used in a class.

So, there is my question. Is it acceptable to use injections like this example below. Can it be considered as a good practice?

On production i will not do any injections and the class will create that object itself. But on testing i will call set_operation_selector() to inject an object mock.

And same functions to add for every related class.

And another question. Is there some pattern to use a single place to “build objects”? Example,

Here i have some class ObjectsBuilder and we usually have only one instance of this class. It is used as a property in ANY object in my application. ANY object would refer to it to create other objects (or reuse existent if it was set somewhere else)

In unit tests we will always inject all required mocks before calling some method from some object. So, instead of creation it will use existent.

On production there would not be injected objects and it would create itself.

Disadvantage is that every class must have a standard constructor (with a specific argument). And additional data should be added with setters.

Is this acceptable? Any pros/cons?

==========================

Загалом питання в тому де створювати обʼєкти класів, щоб можна було легко тестувати окремі методи з допомогою моків.

👍ПодобаєтьсяСподобалось1
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

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

Ок, припустимо, що у нас кожен клас містить 3-5 об’єктів інших класів. Чи означає це, що нам прийдеться для кожного з цих класів робити мок об’єкти? Цей й на кожен тест може бути окремий клас? У нас так 90% коду буде мок. А будь який рефакторінг приведе до того, що купа тестів впаде, та одного дня просто не буде часу їх правити. Тут втрачається сама ідея Кента Бека, що Unit тести потрібні для полегшення рефакторінгу.

Is it acceptable to use injections like this example below.

Так. Це т. з. Setter Injection.

Can it be considered as a good practice?

Так.

20 років тому Мартін Фаулер описав різні засоби для Dependency Injection: Inversion of Control Containers and the Dependency Injection pattern. Зверніть увагу на розділ “Deciding which option to use”.

Особисто я для тестування використовую unittest.mock.

Ось я робив лекцію з прикладами моків різних сервісів для тестування: Python Mock, MagicMock: мокаем веб-сервисы, базы данных (російською мовою).

Приклад тестування x+y уже говорить про те, що ви ніколи не покривали тестами свій код. Вартість інформації в цій статті — 0 гривень 0 копійок.

Приклади з random something завжди складні для розуміння. Я спробую дати відповідь спираючись на свій досвід із DI та тестами в java/kotlin. Варіант із інжектом через метод в принципі можливий, щось схоже є у фреймворку dagger для java.

На практиці куди більш розповсюджений варіант інжекта залежності через конструктор. Якщо в проді не будеш підміняти цю залежність і вона має створюватись самотужки, то я бачу такі варіанти: конструктор з дефолтним параметром для залежності random selector -> в тесті ти підмінеш залежність на мок, в проді буде дефолтне значення, або додати 2 конструктора -> 1 для прода, сам створить selector, другий для тестів, же ти мок передаш. Обирай під можливості твоєї мови, в java немає дефолтних аргументів.

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

Загалом, надай конкретний кейс, з рендомом дуже складно зрозуміти проблематику.

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