exercises/exercises/102_testing.zig
Adam Wheeler 87358c610b Wrap comment at 80 chars in 102.
Some of the inline comments in 102 are wrapped into very short lines. This
rewraps the shortest ones for readability.
2025-10-24 11:54:25 -04:00

97 lines
3.6 KiB
Zig

//
// A big advantage of Zig is the integration of its own test system.
// This allows the philosophy of Test Driven Development (TDD) to be
// implemented perfectly. Zig even goes one step further than other
// languages, the tests can be included directly in the source file.
//
// This has several advantages. On the one hand it is much clearer to
// have everything in one file, both the source code and the associated
// test code. On the other hand, it is much easier for third parties
// to understand what exactly a function is supposed to do if they can
// simply look at the test inside the source and compare both.
//
// Especially if you want to understand how e.g. the standard library
// of Zig works, this approach is very helpful. Furthermore it is very
// practical, if you want to report a bug to the Zig community, to
// illustrate it with a small example including a test.
//
// Therefore, in this exercise we will deal with the basics of testing
// in Zig. Basically, tests work as follows: you pass certain parameters
// to a function, for which you get a return - the result. This is then
// compared with the EXPECTED value. If both values match, the test is
// passed, otherwise an error message is displayed.
//
// testing.expect(foo(param1, param2) == expected);
//
// Also other comparisons are possible, deviations or also errors can
// be provoked, which must lead to an appropriate behavior of the
// function, so that the test is passed.
//
// Tests can be run via Zig build system or applied directly to
// individual modules using "zig test xyz.zig".
//
// Both can be used script-driven to execute tests automatically, e.g.
// after checking into a Git repository. Something we also make extensive
// use of here at Ziglings.
//
const std = @import("std");
const testing = std.testing;
// This is a simple function that builds a sum from the passed parameters and
// returns.
fn add(a: f16, b: f16) f16 {
return a + b;
}
// The associated test. It always starts with the keyword "test", followed by a
// description of the tasks of the test. This is followed by the test cases in
// curly brackets.
test "add" {
// The first test checks if the sum of '41' and '1' gives '42', which is
// correct.
try testing.expect(add(41, 1) == 42);
// Another way to perform this test is as follows:
try testing.expectEqual(42, add(41, 1));
// This time a test with the addition of a negative number:
try testing.expect(add(5, -4) == 1);
// And a floating point operation:
try testing.expect(add(1.5, 1.5) == 3);
}
// Another simple function that returns the result of subtracting the two
// parameters.
fn sub(a: f16, b: f16) f16 {
return a - b;
}
// The corresponding test is not much different from the previous one. Except
// that it contains an error that you need to correct.
test "sub" {
try testing.expect(sub(10, 5) == 6);
try testing.expect(sub(3, 1.5) == 1.5);
}
// This function divides the numerator by the denominator. Here it is important
// that the denominator must not be zero. This is checked and if it occurs an
// error is returned.
fn divide(a: f16, b: f16) !f16 {
if (b == 0) return error.DivisionByZero;
return a / b;
}
test "divide" {
try testing.expect(divide(2, 2) catch unreachable == 1);
try testing.expect(divide(-1, -1) catch unreachable == 1);
try testing.expect(divide(10, 2) catch unreachable == 5);
try testing.expect(divide(1, 3) catch unreachable == 0.3333333333333333);
// Now we test if the function returns an error if we pass a zero as
// denominator. But which error needs to be tested?
try testing.expectError(error.???, divide(15, 0));
}