#
Какие дополнительные приёмы тестирования существуют в Zig?
Кроме классических тестов на правильность функционирования, в Zig доступно несколько полезных приёмов, позволяющих повысить уверенность в работоспособности вашего кода:
#
1. Параметризированные тесты
Параметризированные тесты позволяют проводить серию проверок одной и той же функции с различными наборами значений. Они полезны, когда необходимо убедиться, что функция стабильно работает с множеством комбинаций аргументов.
Например, предположим, что у вас есть функция, преобразующая температуру из Цельсия в Фаренгейты:
// src/converter.zig
const std = @import("std");
pub fn celsiusToFahrenheit(celsius: f32) f32 {
return celsius * 9 / 5 + 32;
}
Тогда в тестах можно применить параметризацию:
// tests/test_converter.zig
const std = @import("std");
const converter = @import("converter");
test "Преобразование температуры" {
inline for ([_][2]f32{
.{0, 32}, // Входящее значение и ожидаемое выходящее
.{100, 212},
.{-40, -40},
}) |pair| {
const celsius = pair[0];
const expected = pair[1];
const result = converter.celsiusToFahrenheit(celsius);
try std.testing.expectApproxEqAbs(expected, result, 0.01);
}
}
Использование цикла inline for позволяет перебирать пары значений и сравнивать результат с ожиданием.
#
2. Тестирование ошибок
Многие функции в Zig возвращают ошибки (error), и важно уметь их проверять. Для этого служит оператор try, позволяющий перехватывать ошибки.
Допустим, у вас есть функция чтения файла, которая возвращает ошибку, если файл отсутствует:
// src/file_io.zig
const std = @import("std");
pub fn readFile(file_path: []const u8) ![]const u8 {
const file = try std.fs.openFileAbsolute(file_path, .{});
defer file.close();
return file.readToEndAlloc(std.testing.allocator, 1024);
}
В тестах можно проверять возникновение ожидаемой ошибки:
// tests/test_file_io.zig
const std = @import("std");
const file_io = @import("file_io");
test "Ошибка при чтении отсутствующего файла" {
const err = file_io.readFile("/path/to/nonexistent.file") catch |e| e;
try std.testing.expect(err == error.FileNotFound);
}
#
3. Ассертивные ожидания
Иногда приходится проверять сложное состояние, например, свойства массива или сложной структуры. Для этого предусмотрены специальные функции сравнения:
std.testing.expectEqual— проверка точного совпадения значений.std.testing.expectEqualStrings— сравнение строк.std.testing.expectCloseAbs— проверка близости численных значений с заданной точностью.
Пример проверки численного результата с заданной погрешностью:
test "Корень квадратный из 2 примерно равен 1.414" {
const sqrt_two: f32 = std.math.sqrt(2.0);
try std.testing.expectApproxEqAbs(sqrt_two, 1.414, 0.001);
}
#
4. Тестирование на нескольких архитектурах
Одним из преимуществ Zig является возможность кросс-платформенного тестирования. Вы можете протестировать код на разных архитектурах и ОС, используя возможности целевых платформ.
Например, следующий скрипт запускает тесты на трёх разных платформах:
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const targets = [_]std.zig.CrossTarget{
.{},
.{ .cpu_arch = .x86_64, .os_tag = .linux },
.{ .cpu_arch = .arm, .os_tag = .ios },
};
const optimize = b.standardOptimizeOption(.{});
const test_step = b.step("test-all-targets", "Запустить тесты на всех платформах");
for (targets) |target| {
const lib = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("tests/test_main.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_tests = b.addRunArtifact(lib);
test_step.dependOn(&run_tests.step);
}
}
Теперь команда zig build test-all-targets запускает тесты на каждой указанной платформе.
#
Заключение
Правильно составленные тесты являются основой качественной разработки на любом языке программирования, включая Zig. Использование рассмотренных приёмов поможет увеличить покрытие тестов, сократить число возможных дефектов и ускорить выявление проблем. Главное правило — каждый компонент должен быть покрыт соответствующими тестами, а сами тесты должны оставаться лёгкими для понимания и модификации.