diff --git a/build.zig b/build.zig index 9113240..a13b016 100644 --- a/build.zig +++ b/build.zig @@ -1219,9 +1219,8 @@ const exercises = [_]Exercise{ }, .{ .main_file = "102_testing.zig", - .output = "All 1 tests passed.", + .output = "", .run_test = true, - .skip = true, }, .{ .main_file = "999_the_end.zig", diff --git a/exercises/102_testing.zig b/exercises/102_testing.zig index dc1da59..89a0ee8 100644 --- a/exercises/102_testing.zig +++ b/exercises/102_testing.zig @@ -1,10 +1,112 @@ +// +// 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; -fn add(a: u16, b: u16) u16 { +// 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; } -test "simple test" { +// 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(add(41, 1), 42); + + // 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)); } diff --git a/patches/patches/102_testing.patch b/patches/patches/102_testing.patch new file mode 100644 index 0000000..49daf0c --- /dev/null +++ b/patches/patches/102_testing.patch @@ -0,0 +1,8 @@ +86c86 +< try testing.expect(sub(10, 5) == 6); +--- +> try testing.expect(sub(10, 5) == 5); +111c111 +< try testing.expectError(error.???, divide(15, 0)); +--- +> try testing.expectError(error.DivisionByZero, divide(15, 0)); diff --git a/test/tests.zig b/test/tests.zig index f5c3960..b25b29c 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -93,7 +93,7 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step { const case_step = createCase(b, "case-3"); for (exercises[0 .. exercises.len - 1]) |ex| { - if (ex.skip) continue; + if (ex.skip or ex.run_test) continue; if (ex.hint) |hint| { const n = ex.number(); @@ -249,6 +249,21 @@ fn check_output(step: *Step, exercise: Exercise, reader: Reader) !void { return; } + if (exercise.run_test) { + { + const actual = try readLine(reader, &buf) orelse "EOF"; + const expect = b.fmt("Testing {s}...", .{exercise.main_file}); + try check(step, exercise, expect, actual); + } + + { + const actual = try readLine(reader, &buf) orelse "EOF"; + try check(step, exercise, "", actual); + } + + return; + } + { const actual = try readLine(reader, &buf) orelse "EOF"; const expect = b.fmt("Compiling {s}...", .{exercise.main_file});