Инъекция зависимостей для процедурного программирования

Предположим, я решил написать большое приложение на языке C или на любом другом языке процедурного программирования. Он имеет функции с зависимостями вызовов, которые выглядят так:

A
|
+-------------+
|             |
B1            B2
|             |
+------+      +------+
|      |      |      |
C11    C12    C21    C22

Очевидно, что модульное тестирование функций листьев C11, C12, C21 и C22 очень просто: установить входы, вызвать функции, утвердить выходы.

Но какова правильная стратегия для обеспечения хорошего модульного тестирования для B1, B2 и A?

Будет ли Injection Dependency предлагать, чтобы B1B2 также) были объявлены как следующие?

// Declare B1 with dependency injection for invoking C11 and C12.
int B1(int input, int (*c11)(int), int(*c12)(int));

Но эта стратегия не кажется масштабируемой, если у меня много уровней вызовов. Представьте себе, как выглядит выражение для A:

int A(int input, int (*b1)(int, int (*)(int), int(*)(int)), 
                 int(*b2)(int, int (*)(int), int(*)(int)),
                 int (*c11)(int),
                 int (*c12)(int),
                 int (*c21)(int),
                 int (*c22)(int));

Тьфу! Должен быть лучший способ.

Иногда я чувствую, что DI и другие подобные шаблоны, которые предназначены для повышения модульности и простоты обслуживания, фактически препятствуют ясности кода и усложняют то, что должно быть простым кодированием в абсурдные абсурды и запутанные направления.

Как крупные проекты программного обеспечения на C, такие как Perl и Ruby, имеют дело с модульным тестированием?

Ответ 1

Если вам требуется только DI для модульного тестирования, вы можете использовать компоновщик для этого.

Я имею в виду, что функции B1 и B2 объявляются в заголовке и используются функцией A, поэтому реализация функций B обеспечивается компоновщиком. Вам просто нужно предоставить другой C файл для модульных тестов. Это не должно быть большой проблемой, так как у вас, вероятно, есть собственный make файл для unit test.

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

Ответ 2

A нужно только позвонить B1 и B2. Это не нужно знать ни о чем на уровне C.

В целях тестирования A вы можете вводить разные фиктивные версии функций B1 и B2 в A.

Это изолирует A от необходимости всей структуры и означает, что вы можете тестировать каждую функцию изолированно.

Ответ 3

вы можете поместить зависимости в c-struct, которая станет одним из параметров для вызова функции. в c это будет похоже на файл api, где первым параметром всегда является файл-дескриптор

Ответ 4

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

Поскольку вы процедурные... Я думаю, вы могли бы использовать перегрузку функций, чтобы справиться с этим для вас. Также, когда вы тестируете, вам нужно будет только выкрикивать B1 и B2 при вызове A... чтобы вы могли упростить DI для этой цели. Другими словами, если вы действительно используете только DI для модульного тестирования, вам не нужно вводить все дерево зависимостей только в зависимости от первого уровня...

Итак, из A вы могли бы...

int A(int input){
// create function point to b1 & b2 and call "return A(input, {pointer to b1},{pointer to b2})"
}

Простите мой код psuedo, это было давно, так как я сделал C.

Ответ 5

Вы можете выполнить правильное модульное тестирование B1, B2 и A без DI. Подобно функциям листа, B1 и B2 имеют действительные входы и выходы, и вы тестируете те же, что и A. Чтобы B1 мог использовать C11 и C12 внутренне, чтобы помочь вам выполнить свои модульные тесты, не означает, что их нужно вводить в случаях где вам не нужна такая гибкость.