В мире разработки программного обеспечения модульное тестирование играет критически важную роль в обеспечении качества и надежности кода. Однако, иногда возникают ситуации, когда необходимо протестировать код, зависящий от внешних функций, таких как UuidCreate, объявление которой может быть скрыто в системных модулях, таких как System.SysUtils в Delphi. В этой статье мы рассмотрим различные подходы к решению этой проблемы, опираясь на обсуждение, возникшее на форуме Delphi.
Проблема:
Необходимо перехватить функцию UuidCreate для целей модульного тестирования, используя библиотеку DDetours. Проблема заключается в том, что объявление этой функции недоступно напрямую, так как оно находится в секции implementation модуля System.SysUtils.
Решение 1: Прямое объявление функции
Remy Lebeau предлагает простое и эффективное решение: объявить функцию UuidCreate в собственном коде. Поскольку UuidCreate является функцией Win32 API, находящейся в rpcrt4.dll, достаточно создать собственное объявление, чтобы получить к ней доступ для перехвата.
function UuidCreate(out guid: TGUID): Longint; stdcall; external 'rpcrt4.dll' name 'UuidCreate';
Это объявление позволяет использовать функцию InterceptCreate из DDetours для перехвата вызовов UuidCreate. Важно помнить, что физически существует только одна функция в rpcrt4.dll, поэтому не имеет значения, сколько объявлений существует для доступа к ней.
Пример использования DDetours (на основе примера dwrbudr):
function Detour_UuidCreate(out guid: TGUID): Longint; stdcall;
begin
guid := Default(TGUID); // Или любое другое предопределенное значение
Result := 0; // Или код ошибки, если необходимо
end;
procedure TForm68.Button1Click(Sender: TObject);
var
myguid: TGUID;
begin
InterceptCreate('rpcrt4.dll', 'UuidCreate', @Detour_UuidCreate);
CreateGUID(myguid); // Вызов функции, которая использует UuidCreate
// ...
end;
Альтернативное решение 2: Абстракции и Mock-объекты
Vincent Parrett предлагает более элегантный и рекомендуемый подход: использование абстракций и Mock-объектов. Вместо прямого перехвата функции UuidCreate, создается абстракция (интерфейс) для генерации UUID, и конкретная реализация, которая вызывает UuidCreate. Для целей тестирования можно использовать Mock-объекты, которые возвращают предопределенные значения, что позволяет изолировать тестируемый код от внешних зависимостей.
interface
IUuidGenerator = interface
['{...}'] // GUID интерфейса
function Generate: TGUID;
end;
TRealUuidGenerator = class(TInterfacedObject, IUuidGenerator)
public
function Generate: TGUID;
end;
implementation
uses
System.SysUtils; // Или прямое объявление UuidCreate, как описано выше
function TRealUuidGenerator.Generate: TGUID;
var
ResultCode: Longint;
begin
ResultCode := UuidCreate(Result);
if ResultCode <> 0 then
begin
// Обработка ошибки
raise Exception.Create('Ошибка при создании UUID: ' + IntToStr(ResultCode));
end;
end;
// В модуле тестирования:
type
TMockUuidGenerator = class(TInterfacedObject, IUuidGenerator)
private
FReturnValue: TGUID;
public
constructor Create(const AReturnValue: TGUID);
function Generate: TGUID; override;
end;
constructor TMockUuidGenerator.Create(const AReturnValue: TGUID);
begin
inherited Create;
FReturnValue := AReturnValue;
end;
function TMockUuidGenerator.Generate: TGUID;
begin
Result := FReturnValue;
end;
// Пример использования в тестируемом коде:
type
TMyClass = class
private
FUuidGenerator: IUuidGenerator;
public
constructor Create(AUuidGenerator: IUuidGenerator);
function DoSomething: string;
end;
constructor TMyClass.Create(AUuidGenerator: IUuidGenerator);
begin
inherited Create;
FUuidGenerator := AUuidGenerator;
end;
function TMyClass.DoSomething: string;
var
NewGuid: TGUID;
begin
NewGuid := FUuidGenerator.Generate;
Result := GUIDToString(NewGuid); // Или другая логика с использованием GUID
end;
// Пример использования в тесте:
procedure TestMyClass;
var
MockGenerator: TMockUuidGenerator;
MyClass: TMyClass;
ExpectedGuid: TGUID;
ActualResult: string;
begin
// Arrange
ExpectedGuid := StringToGUID('{12345678-1234-1234-1234-1234567890AB}');
MockGenerator := TMockUuidGenerator.Create(ExpectedGuid);
MyClass := TMyClass.Create(MockGenerator);
// Act
ActualResult := MyClass.DoSomething;
// Assert
Assert.AreEqual(GUIDToString(ExpectedGuid), ActualResult);
end;
Этот подход позволяет легко подменять реализацию UuidGenerator в тестах, используя Mock-объекты, такие как TMockUuidGenerator. Можно использовать фреймворки для Mock-объектов, такие как Delphi Mocks или Spring4D (хотя EugeneK отмечает, что пока не использует Spring4D из-за отсутствия поддержки пространств имен).
Преимущества использования абстракций и Mock-объектов:
Изоляция: Тестируемый код изолирован от внешних зависимостей, что делает тесты более надежными и предсказуемыми.
Гибкость: Легко подменять реализации для различных тестовых сценариев.
Читаемость: Код становится более читаемым и понятным.
Поддержка TDD: Подходит для разработки через тестирование (TDD).
Альтернативное решение 3: Использование предопределенной GUID для тестирования
Artem Razin предлагает простое решение для целей тестирования: использовать кастомную реализацию, которая всегда возвращает предопределенный GUID. Это может быть полезно, если важна только проверка корректности обработки GUID, а не уникальность.
Вывод:
В зависимости от конкретных требований и предпочтений, можно выбрать один из предложенных подходов для перехвата функции UuidCreate в Delphi для целей модульного тестирования. Прямое объявление функции с использованием DDetours является простым и быстрым решением. Однако, использование абстракций и Mock-объектов является более элегантным и рекомендуемым подходом, обеспечивающим лучшую изоляцию, гибкость и читаемость кода. Использование предопределенной GUID - самое простое решение, если не требуется уникальность GUID в тестах.
В статье рассматриваются различные подходы к перехвату функции `UuidCreate` в Delphi для целей модульного тестирования, включая прямое объявление функции, использование абстракций и Mock-объектов, а также использование предопределенной GUID.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.