Both homework assignments use the Criterion testing framework for unit testing. This guide covers how to run tests, interpret results, and write your own test cases.
Unit testing is a development practice where small, testable sections of code (units) are tested individually. This helps you:
Verify individual functions work correctly
Catch bugs early in development
Ensure changes don’t break existing functionality
Document expected behavior through test examples
Some developers use Test-Driven Development (TDD), where you write failing tests first, then implement code to make them pass. This is a valuable industry practice.
# List all available tests without running thembin/png_tests --list# Run tests and show detailed statisticsbin/png_tests --verbose=1# Stop on first failurebin/png_tests --fail-fast# Show help for all Criterion optionsbin/png_tests --help
#include <criterion/criterion.h>#include "png_reader.h"// Test suite name: test_reader// Test name: test_png_open_valid_fileTest(test_reader, test_png_open_valid_file) { // Arrange: Set up test data const char *filename = "tests/data/sample.png"; // Act: Call the function being tested FILE *fp = png_open(filename); // Assert: Verify the results cr_assert_not_null(fp, "png_open should return non-NULL for valid file"); // Cleanup if (fp) fclose(fp);}
Key components:
Test(suite_name, test_name) - Defines a test case
cr_assert_* - Assertion macros that check conditions
Arrange-Act-Assert pattern for clear test structure
The provided tests are MINIMAL and do not comprehensively test your code. The actual grading tests are much more extensive. You are strongly encouraged to write additional tests.
#include <criterion/criterion.h>#include "png_chunks.h" // Headers for functions you're testing#include "png_reader.h"
3
Write Test Cases
Test(my_tests, test_basic_functionality) { // Your test code here int result = my_function(5); cr_assert_eq(result, 10, "Expected my_function(5) to return 10");}Test(my_tests, test_error_handling) { // Test error cases int result = my_function(NULL); cr_assert_eq(result, -1, "Should return -1 for NULL input");}
#include <criterion/criterion.h>#include "png_reader.h"#include "png_chunks.h"Test(custom_tests, test_read_ihdr_chunk) { // Open test file FILE *fp = png_open("tests/data/sample.png"); cr_assert_not_null(fp, "Failed to open test file"); // Read first chunk (should be IHDR) png_chunk_t chunk; int result = png_read_chunk(fp, &chunk); // Verify success cr_assert_eq(result, 0, "png_read_chunk should succeed"); // Verify chunk type cr_assert_str_eq(chunk.type, "IHDR", "First chunk should be IHDR"); // Verify chunk length cr_assert_eq(chunk.length, 13, "IHDR chunk should be 13 bytes"); // Verify data was allocated cr_assert_not_null(chunk.data, "Chunk data should be allocated"); // Parse IHDR png_ihdr_t ihdr; result = png_parse_ihdr(&chunk, &ihdr); cr_assert_eq(result, 0, "png_parse_ihdr should succeed"); // Cleanup png_free_chunk(&chunk); fclose(fp);}
// Global setup (runs once before all tests in the suite)TestSuite(my_suite, .init = suite_setup, .fini = suite_teardown);void suite_setup(void) { // Initialize test resources}void suite_teardown(void) { // Clean up test resources}// Per-test setup (runs before each test)Test(my_suite, my_test, .init = test_setup, .fini = test_teardown) { // Test code}void test_setup(void) { // Runs before each test}void test_teardown(void) { // Runs after each test}
# Compile with debug symbolsmake clean debug# Run specific test under GDBgdb bin/png_tests(gdb) run --filter=test_png_read_chunk# When it crashes or fails:(gdb) backtrace(gdb) print chunk.length