Внедрение зависимостей для процедурного программирования - PullRequest
11 голосов
/ 03 апреля 2011

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

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

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

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

Будет ли Внедрение зависимостей предполагать, что 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, занимаются модульным тестированием?

Ответы [ 5 ]

3 голосов
/ 14 апреля 2011

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

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

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

3 голосов
/ 06 апреля 2011

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

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

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

2 голосов
/ 03 апреля 2011

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

0 голосов
/ 14 апреля 2011

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

0 голосов
/ 03 апреля 2011

Мне нравится этот вопрос.Это немного сложно в процедурных языках ... но я думаю, что вы можете позаимствовать идею из мира 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})"
}

прости мой псевдо-код, прошло много времени с тех пор, как я сделал C.

...