Довольно просто (если утомительно) модульное тестирование модулей расширения Python, написанных на C, включая случаи ошибок для многих API-интерфейсов Python / C, таких как PyArg_ParseTuple. Например, идиоматический способ запуска функции C, которая реализует функцию или метод Python, выглядит следующим образом:
if (!PyArg_ParseTuple(args, "someformat:function_name")) {
return NULL;
}
Успешный случай этого может быть проверен модулем путем вызова функции с правильным числом и типом аргументов. Случаи сбоев также можно проверить, вызвав функцию с неправильным числом аргументов, а затем с правильным количеством аргументов, но передавая значения неправильного типа. Это приводит к полному покрытию ветвящимся тестом кода C.
Однако не ясно, как использовать отрицательные пути для других API-интерфейсов Python / C. Идиоматический способ начать инициализацию модуля в расширении C выглядит следующим образом:
if (PyType_Ready(&Some_Extension_Structure) < 0) {
return 0;
}
Как заставить PyType_Ready
потерпеть неудачу? Аналогично, функция C для выделения нового экземпляра типа расширения часто использует API-интерфейс, такой как PyObject_New
:
self = PyObject_New(Some_Structure, &Some_Extension_Structure);
if (self == NULL) {
return NULL;
}
Как может один модульный тестировать этот отрицательный случай (особенно если учесть, что PyObject_New
, вероятно, используется много-много раз в ходе выполнения метода одиночного модульного тестирования)?
Кажется возможным построить общее решение, опираясь на уловки динамического компоновщика, такие как LD_PRELOAD
, чтобы обеспечить подделки этих API-интерфейсов C, которые могут быть направлены на сбой правильным способом в нужное время. Однако стоимость создания такой системы кажется недостижимой. Кто-то уже сделал это и сделал результат доступным?
Существуют ли уловки, специфичные для Python / C, которые могли бы облегчить это тестирование?
Должен ли я думать совсем по-другому?