{"id":25300107,"url":"https://github.com/deftio/c-and-cpp-tests-with-ci-cd-example","last_synced_at":"2025-10-28T06:30:46.691Z","repository":{"id":52946517,"uuid":"82499983","full_name":"deftio/C-and-Cpp-Tests-with-CI-CD-Example","owner":"deftio","description":"C/C++ example project for CI testing with Github Actions or Travis CI + build badges","archived":false,"fork":false,"pushed_at":"2024-08-08T04:35:15.000Z","size":54,"stargazers_count":42,"open_issues_count":1,"forks_count":37,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-08-08T22:46:29.789Z","etag":null,"topics":["badge","build-pipelines","c","c-plus-plus","cicd","code-coverage","continuous-integration","cpp","example-ci","github","github-actions","github-actions-ci","testing","testing-framework","travis-ci","travisci","unit-testing"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/deftio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-02-20T00:25:49.000Z","updated_at":"2024-08-08T00:41:41.000Z","dependencies_parsed_at":"2024-08-07T22:40:36.234Z","dependency_job_id":null,"html_url":"https://github.com/deftio/C-and-Cpp-Tests-with-CI-CD-Example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deftio%2FC-and-Cpp-Tests-with-CI-CD-Example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deftio%2FC-and-Cpp-Tests-with-CI-CD-Example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deftio%2FC-and-Cpp-Tests-with-CI-CD-Example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deftio%2FC-and-Cpp-Tests-with-CI-CD-Example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/deftio","download_url":"https://codeload.github.com/deftio/C-and-Cpp-Tests-with-CI-CD-Example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238603659,"owners_count":19499490,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["badge","build-pipelines","c","c-plus-plus","cicd","code-coverage","continuous-integration","cpp","example-ci","github","github-actions","github-actions-ci","testing","testing-framework","travis-ci","travisci","unit-testing"],"created_at":"2025-02-13T05:38:23.443Z","updated_at":"2025-10-28T06:30:46.684Z","avatar_url":"https://github.com/deftio.png","language":"C","readme":"[![Language](https://img.shields.io/badge/Language-C%2FC%2B%2B-blue.svg)](https://github.com/deftio/C-and-Cpp-Tests-with-CI-CD-Example)\r\n[![Build Status](https://img.shields.io/badge/Build-Passing-brightgreen.svg)](https://github.com/deftio/C-and-Cpp-Tests-with-CI-CD-Example)\r\n[![CI](https://github.com/deftio/C-and-Cpp-Tests-with-CI-CD-Example/actions/workflows/ci.yml/badge.svg)](https://github.com/deftio/C-and-Cpp-Tests-with-CI-CD-Example/actions/workflows/ci.yml)\r\n[![Coverage Status](https://coveralls.io/repos/github/deftio/C-and-Cpp-Tests-with-CI-CD-Example/badge.svg?branch=master)](https://coveralls.io/github/deftio/C-and-Cpp-Tests-with-CI-CD-Example?branch=master)\r\n[![License](https://img.shields.io/badge/License-BSD%202--Clause-blue.svg)](https://opensource.org/licenses/BSD-2-Clause)\r\n\r\n# C/C++ Testing with CI/CD Example\r\n\r\nA quick overview of testing C/C++ code, covering basic unit tests to continuous integration. In this repo we'll go through concepts such as unit testing and coverage to CI with a simple working example.\r\n\r\n## What's In This Repository\r\n\r\nThis repository shows testing concepts using a simple C library. We'll explore:\r\n\r\n- Unit testing basics\r\n- Code coverage and what it tells us\r\n- Test-driven development concepts\r\n- Continuous integration with GitHub Actions\r\n- How these pieces fit together in practice\r\n\r\n## Project Structure\r\n\r\n```\r\n.\r\n├── lib.h                  # Library header with function declarations\r\n├── lib.c                  # Library implementation (math \u0026 bitwise operations)\r\n├── test-library.c         # Test suite with assertions\r\n├── makefile              # Build rules with coverage flags\r\n├── CMakeLists.txt        # CMake build configuration (alternative to make)\r\n├── run_coverage_test.sh  # Script to generate coverage reports\r\n├── .github/\r\n│   └── workflows/\r\n│       └── ci.yml        # GitHub Actions CI configuration\r\n├── codecov.yml           # Coverage service configuration\r\n└── .gitignore            # Git ignore patterns\r\n```\r\n\r\n## Getting Started with Testing\r\n\r\n### Why Test?\r\n\r\nWhen we write a function, we usually run it a few times manually to check it works. Testing just formalizes this process. Instead of manual checks, we write code that does the checking for us.\r\n\r\nTesting helps us understand:\r\n\r\n- **Does it work?** - Does the code do what we intended?\r\n- **Does it handle edge cases?** - What about negative numbers? Zero? Maximum values? (or even more complex inputs)\r\n- **Will it keep working?** - When we change other code, did we break this?\r\n- **Is it completely tested?** - Did we miss any scenarios?\r\n\r\n### A Simple Test Example\r\n\r\nLet's look at a basic test. In this repo we have a small math operations library.  Here's one of our library functions:\r\n\r\n```c\r\n// From lib.c\r\nint op_add(int x, int y) {\r\n    int r = x + y;\r\n    return r;\r\n}\r\n```\r\n\r\nTo test this, we write a function that calls our function in dedicated test program:\r\n\r\n```c\r\n// From test-library.c\r\nif (op_add(2, 3) != 5) {\r\n    printf(\"TEST FAILED: op_add(2, 3) should equal 5\\n\");\r\n    return 1;  // Exit with error\r\n}\r\nprintf(\"TEST PASSED: op_add works correctly\\n\");\r\n```\r\n\r\nThis is a test! It's simple but powerful:\r\n- It calls the function with known inputs\r\n- It checks if the output is correct\r\n- It reports success or failure\r\n\r\n### Chapter 3: Testing All Paths\r\n\r\nNow let's look at a slightly more complex example:\r\n\r\n```c\r\nint add5ifGreaterThan2(int a) {\r\n    int r;\r\n    if (a \u003e 2)\r\n        r = a + 5;    // Path 1: When a \u003e 2\r\n    else\r\n        r = a;        // Path 2: When a \u003c= 2\r\n    return r;\r\n}\r\n```\r\n\r\nThis function has **two execution paths**. If we only test with `a = 10`, we only test Path 1. We've missed half the function! \r\n\r\nTo test completely, we need to test **every path**:\r\n\r\n```c\r\n// Test Path 1: When a \u003e 2\r\nassert(add5ifGreaterThan2(3) == 8);   // 3 + 5 = 8 ✓\r\nassert(add5ifGreaterThan2(10) == 15); // 10 + 5 = 15 ✓\r\n\r\n// Test Path 2: When a \u003c= 2  \r\nassert(add5ifGreaterThan2(1) == 1);   // Returns 1 unchanged ✓\r\nassert(add5ifGreaterThan2(2) == 2);   // Boundary: exactly 2 ✓\r\n\r\n// Test edge cases\r\nassert(add5ifGreaterThan2(0) == 0);   // Zero ✓\r\nassert(add5ifGreaterThan2(-5) == -5); // Negative ✓\r\n```\r\n\r\n**Key Insight**: Every `if` statement creates paths. The same is true for switch statements. Every path needs tests.\r\n\r\n### Chapter 4: Enter Code Coverage\r\n\r\nBut how do we know we've tested all paths? That's where **code coverage** comes in.  We can use a tool to find what paths have been taken in our tests and which ones have not been tested.\r\n\r\nCode coverage is like a GPS tracker for your tests - it shows you exactly which lines of code were executed during testing. Let's see it in action:\r\n\r\n```bash\r\n# Run tests with coverage tracking\r\nmake clean\r\nmake\r\n./test-library.out\r\ngcov lib.c\r\ncat lib.c.gcov\r\n```\r\n\r\nThe coverage report shows:\r\n\r\n```\r\n        2:    9:int op_and(int x, int y) {\r\n        2:   10:    return x \u0026 y;\r\n        -:   11:}\r\n        -:   12:\r\n        3:   17:int op_xor(int a, int b){\r\n        3:   18:    int r = a ^ b;\r\n        3:   19:    return r;\r\n        -:   20:}\r\n        -:   21:\r\n    #####:   22:int op_xnor(int a, int b){\r\n    #####:   23:    return ~(a ^ b);\r\n        -:   24:}\r\n```\r\n\r\nWhat do these symbols mean?\r\n- `2:` - This line ran 2 times ✓\r\n- `3:` - This line ran 3 times ✓\r\n- `#####:` - **This line NEVER ran!** ⚠️\r\n- `-:` - Non-executable line (comments, brackets)\r\n\r\n**The smoking gun**: Lines 22-23 (`op_xnor` function) were never tested! Our test suite has a gap.\r\n\r\n#### Coverage Metrics\r\n\r\nFrom this report, we calculate:\r\n- **Lines of code**: 10 executable lines\r\n- **Lines tested**: 8 lines executed\r\n- **Coverage**: 8/10 = 80%\r\n\r\nTo achieve 100% coverage, we need to add:\r\n```c\r\nassert(op_xnor(0x0F, 0xF0) == ~(0x0F ^ 0xF0));  // Test the missing function\r\n```\r\n\r\n### Chapter 5: The Coverage Paradox\r\n\r\n**Warning**: 100% coverage ≠ bug-free code!\r\n\r\nConsider this function with 100% line coverage:\r\n```c\r\nint divide(int a, int b) {\r\n    return a / b;  // 100% covered if we test divide(10, 2)\r\n}\r\n```\r\n\r\nBut what about `divide(10, 0)`? 💥 Division by zero!\r\n\r\nCoverage tells you what you tested, not what you missed. As Dijkstra famously said: *\"Testing shows the presence, not the absence of bugs.\"*\r\n\r\nResearch shows (Namin \u0026 Andrews, 2009):\r\n- **\u003c 50% coverage**: Many bugs remain\r\n- **70-80% coverage**: Good balance of effort vs benefit\r\n- **\u003e 90% coverage**: Diminishing returns\r\n\r\n### Testing Frameworks for C/C++\r\n\r\nWhile this example uses simple assertions to keep things clear, there are many testing frameworks available that provide more features:\r\n\r\n#### Popular C/C++ Testing Frameworks\r\n\r\n- **[Google Test](https://github.com/google/googletest)** - Feature-rich, widely used in industry\r\n- **[Catch2](https://github.com/catchorg/Catch2)** - Header-only, simple to integrate\r\n- **[Unity](http://www.throwtheswitch.org/unity)** - Minimal framework, perfect for embedded systems\r\n- **[CppUTest](http://cpputest.github.io/)** - Designed specifically for embedded development\r\n- **[Check](https://libcheck.github.io/check/)** - Unit testing framework for C\r\n- **[CUnit](http://cunit.sourceforge.net/)** - Lightweight C testing framework\r\n- **[Boost.Test](https://www.boost.org/doc/libs/release/libs/test/)** - Part of the Boost libraries\r\n\r\n#### Why We Use Simple Assertions Here\r\n\r\nThis repository intentionally uses basic assertions rather than a framework to:\r\n- Focus on testing concepts rather than framework syntax\r\n- Keep the example accessible to embedded developers\r\n- Show that testing doesn't require complex tools\r\n- Minimize dependencies\r\n\r\nOnce you understand the concepts, you can easily adopt any framework that suits your needs.\r\n\r\n### Chapter 6: Test-Driven Development (TDD)\r\n\r\nTDD flips the script: write tests BEFORE code. \r\n\r\n1. **Red** 🔴 - Write a failing test\r\n2. **Green** 🟢 - Write minimal code to pass\r\n3. **Refactor** 🔄 - Clean up while tests stay green\r\n\r\nExample:\r\n```c\r\n// Step 1: Write test first (RED - fails because function doesn't exist)\r\nassert(op_multiply(3, 4) == 12);\r\n\r\n// Step 2: Write minimal code (GREEN - just enough to pass)\r\nint op_multiply(int a, int b) {\r\n    return a * b;\r\n}\r\n\r\n// Step 3: Refactor if needed (keep it GREEN)\r\n```\r\n\r\n### Chapter 7: Continuous Integration - Automating Trust\r\n\r\nNow imagine you're working with a team. How do you ensure everyone's code is tested? **Enter CI/CD**.\r\n\r\n#### The Manual Way (Error-Prone)\r\n1. Developer writes code\r\n2. Developer remembers to run tests (maybe)\r\n3. Developer commits code\r\n4. Other developers pull broken code\r\n5. 😱 Everything breaks\r\n\r\nBut we can do better.  In fact we can *force* the tests to be run every time code is checkedin to the repo.   This is what CI (continuosu integration) is about.\r\n\r\n#### The CI Way (Automated)\r\n1. Developer writes code\r\n2. Developer commits code\r\n3. CI automatically runs all tests\r\n4. If tests fail, the commit is rejected\r\n5. ✅ Main branch stays clean\r\n\r\nWith CI you can have some assurance that the test suites are being run and even how much coverage there is with each check-in.\r\n\r\nLook at the badges at the top of this README:\r\n- **Green CI badge**: All tests passing, safe to use\r\n- **Red CI badge**: Tests failing, something's broken\r\n- **Coverage badges**: Show test coverage percentage\r\n\r\nThese update **automatically** with every commit!\r\n\r\n### Chapter 8: When CI Fails - The Safety Net\r\n\r\nWhat happens when CI detects a failure?\r\n\r\n```yaml\r\n# From .github/workflows/ci.yml\r\n- name: Run tests\r\n  run: |\r\n    ./test-library.out\r\n    # If this fails, the build stops here!\r\n```\r\n\r\nIf tests fail:\r\n1. CI stops immediately ❌\r\n2. Badge turns red 🔴\r\n3. GitHub can block the merge \r\n4. Team gets notified\r\n5. **No broken code reaches production**\r\n\r\nThis is why CI exists - it's a safety net that never forgets to test.\r\n\r\n### Chapter 9: From CI to CD\r\n\r\n- **CI (Continuous Integration)**: Automatically test every change\r\n- **CD (Continuous Deployment)**: If tests pass, automatically deploy\r\n\r\nThe full pipeline:\r\n```\r\nCode → Test → Build → Deploy\r\n      ↑\r\n      CI ensures this never fails\r\n```\r\n\r\nIf CI fails, deployment stops. This prevents broken code from reaching users.\r\n\r\n## Running the Examples\r\n\r\n### Quick Start\r\n\r\n```bash\r\n# Clone this repository\r\ngit clone https://github.com/deftio/C-and-Cpp-Tests-with-CI-CD-Example.git\r\ncd C-and-Cpp-Tests-with-CI-CD-Example\r\n\r\n# Build and run tests\r\nmake\r\n./test-library.out\r\n\r\n# Check coverage\r\n./run_coverage_test.sh\r\ncat lib.c.gcov  # Shows which lines were tested\r\n```\r\n\r\n### Things to Try\r\n\r\n- Run coverage and see if any functions are missing tests\r\n- Add a test for any untested functions (hint: check `op_xnor`)\r\n- Modify a function to break its test, then fix it\r\n- Fork the repo and watch GitHub Actions run your tests automatically\r\n\r\n## The Bigger Picture\r\n\r\nThis simple example demonstrates principles that scale to massive projects:\r\n\r\n- **Linux Kernel**: ~30 million lines, extensive test suites\r\n- **Chrome Browser**: Thousands of tests run on every commit\r\n- **Embedded Systems**: Safety-critical code with 100% coverage requirements\r\n\r\nThe task of writing tests, check coverage, automate with CI are the same ones used by professional developers worldwide.\r\n\r\n## Testing Concepts\r\n\r\nBeyond CI and CD are many other types of tests, such as integration tests which show how well code connects together, system and endurance tests which test how robust code is to certain types of errors or whether it can run a long time.  Often small memory leaks are not caught early on because it takes a long time to for enough memory to be lost to make the system unstable.   Knowing your domain well is key to avoiding many classes of errors.\r\n\r\n### Frequently Asked Questions\r\n\r\n**Q: How much testing is enough?**\r\nA: Generally, when you feel confident making changes without breaking things.\r\n\r\n**Q: Should I test simple/obvious code?**\r\nA: It's often worth it - simple code can have surprising bugs.\r\n\r\n**Q: What if code is hard to test?**\r\nA: This often suggests the code could be structured better.\r\n\r\n\r\n## Build Instructions - The code in this repo\r\n\r\nThe code in this repo is written in C (but build tools can also handle C++)\r\n\r\n### Prerequisites\r\n- **Compiler**: GCC or Clang (C99+)\r\n- **Tools**: Make and/or CMake\r\n- **Coverage**: gcov (included with GCC)\r\n\r\n### Using Make\r\n```bash\r\nmake clean    # Clean build artifacts\r\nmake          # Build project\r\nmake test     # Run tests  \r\nmake coverage # Generate coverage report\r\n```\r\n\r\n### Using CMake\r\n```bash\r\nmkdir build \u0026\u0026 cd build\r\ncmake ..\r\nmake\r\nmake test\r\nmake coverage\r\n```\r\n\r\n### Platform Installation\r\n\r\n**Ubuntu/Debian:**\r\n```bash\r\nsudo apt-get install gcc make cmake lcov\r\n```\r\n\r\n**macOS:**\r\n```bash\r\nbrew install gcc cmake lcov\r\n```\r\n\r\n**Windows:**\r\nUse WSL or MinGW\r\n\r\n## Coverage Services Setup\r\n\r\nTo get coverage badges working:\r\n\r\n### Codecov\r\n1. Visit [codecov.io](https://codecov.io)\r\n2. Sign in with GitHub\r\n3. Add your repository\r\n4. (Optional) Add CODECOV_TOKEN to GitHub secrets\r\n\r\n### Coveralls  \r\n1. Visit [coveralls.io](https://coveralls.io)\r\n2. Sign in with GitHub\r\n3. Enable your repository\r\n\r\nBoth services are free for open source projects.\r\n\r\n## References\r\n\r\n### Online Resources\r\n- [Unit Testing](https://en.wikipedia.org/wiki/Unit_testing) - Wikipedia\r\n- [Continuous Integration](https://www.martinfowler.com/articles/continuousIntegration.html) - Martin Fowler\r\n- [Google Test Primer](https://google.github.io/googletest/primer.html) - Google\r\n\r\n## Contributing\r\n\r\nPull requests are welcome! This repository is meant to be educational, so contributions that improve clarity or add examples are especially valued.\r\n\r\n## Version History\r\n\r\n- **1.0.4** (2025) - Added CMake, enhanced documentation, focus on testing narrative\r\n- **1.0.3** (2024) - Added GitHub Actions\r\n- **1.0.2** (2021) - Travis CI updates\r\n- **1.0.0** (2016) - Initial release\r\n\r\n## License\r\n\r\nBSD 2-Clause License - see [LICENSE.txt](LICENSE.txt)\r\n\r\n© 2016-2025 M. A. Chatterjee \u003cdeftio [at] deftio [dot] com\u003e","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeftio%2Fc-and-cpp-tests-with-ci-cd-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdeftio%2Fc-and-cpp-tests-with-ci-cd-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeftio%2Fc-and-cpp-tests-with-ci-cd-example/lists"}