PHP LogoUnit Testing (Birim Testleri)

Unit Testing is a software testing method where individual units or components of a software application are tested in isolation to determine if they are fit for use. A 'unit' is the smallest testable part of an application, typically a function, method, or class. The primary goal of unit testing is to validate that each unit of the software performs as designed.

Why is Unit Testing Important?
1. Early Bug Detection: Catch bugs at an early stage of development, where they are typically easier and cheaper to fix than finding them later in integration or system testing.
2. Facilitates Change: Provides a safety net for refactoring or adding new features. If tests pass after a change, you have higher confidence that the existing functionality hasn't been broken.
3. Simplifies Debugging: When a unit test fails, it immediately points to the specific unit that has a problem, making debugging much faster.
4. Improves Code Quality and Design: Writing unit tests often forces developers to think about the design of their code, leading to more modular, loosely coupled, and testable components.
5. Documentation: Unit tests serve as living documentation for how a particular unit of code is supposed to work and how it should be used.
6. Reduces Integration Issues: By ensuring each unit works correctly in isolation, the chances of encountering integration problems are reduced.

How Unit Testing Works

Developers write test cases for each function or method. Each test case typically involves:
1. Arrange: Setting up the necessary prerequisites and initial state for the test.
2. Act: Executing the unit of code being tested.
3. Assert: Verifying the outcome against an expected result using assertions (e.g., `assertEquals`, `assertTrue`, `assertFalse`, `assertNull`).

Key Characteristics of Good Unit Tests (FIRST Principles)
* Fast: Tests should run quickly to encourage frequent execution.
* Isolated/Independent: Tests should not depend on the outcome of other tests or external factors.
* Repeatable: Tests should produce the same results every time they are run, regardless of the environment or order.
* Self-Validating: Tests should automatically determine if they passed or failed, without manual inspection.
* Timely: Tests should be written before or alongside the code they are testing (Test-Driven Development - TDD).

Unit Testing in PHP

PHPUnit is the most popular testing framework for PHP. It provides a robust set of tools to write and run unit tests. Key concepts in PHPUnit include:
* Test Cases: A class that contains one or more test methods.
* Test Methods: Public methods within a test case class, prefixed with `test` (e.g., `testAddNumbers`), that contain the actual test logic.
* Assertions: Methods provided by the `PHPUnit\Framework\TestCase` class (which test cases extend) to verify expected outcomes. These include `assertEquals()`, `assertTrue()`, `assertFalse()`, `assertContains()`, `assertCount()`, `assertNull()`, `expectException()`, etc.

To use PHPUnit, you typically install it via Composer:
`composer require --dev phpunit/phpunit`

Example Code

```php
<?php

// File: src/Calculator.php
// This is the class we want to test
class Calculator {
    public function add(float $a, float $b): float {
        return $a + $b;
    }

    public function subtract(float $a, float $b): float {
        return $a - $b;
    }

    public function multiply(float $a, float $b): float {
        return $a * $b;
    }

    public function divide(float $numerator, float $denominator): float {
        if ($denominator === 0.0) {
            throw new InvalidArgumentException("Cannot divide by zero.");
        }
        return $numerator / $denominator;
    }

    public function isPositive(float $number): bool {
        return $number > 0;
    }
}

?>
```

```php
<?php

// File: tests/CalculatorTest.php
// This is the Unit Test for the Calculator class

use PHPUnit\Framework\TestCase;

// Autoloading or manual inclusion of the class under test
// In a real project with Composer, you'd use autoloading.
// For this example, we'll manually include it if not using Composer's autoload.
// require_once __DIR__ . '/../src/Calculator.php'; // Adjust path as needed

class CalculatorTest extends TestCase {

    // Test method for the add() function
    public function testAddNumbers(): void {
        $calculator = new Calculator();
        $this->assertEquals(5, $calculator->add(2, 3), "Adding 2 and 3 should be 5");
        $this->assertEquals(0, $calculator->add(-2, 2), "Adding -2 and 2 should be 0");
        $this->assertEquals(10.5, $calculator->add(5.2, 5.3), "Adding decimals should work");
    }

    // Test method for the subtract() function
    public function testSubtractNumbers(): void {
        $calculator = new Calculator();
        $this->assertEquals(1, $calculator->subtract(3, 2), "Subtracting 2 from 3 should be 1");
        $this->assertEquals(-4, $calculator->subtract(2, 6), "Subtracting 6 from 2 should be -4");
        $this->assertEquals(0.5, $calculator->subtract(1.0, 0.5), "Subtracting decimals should work");
    }

    // Test method for the multiply() function
    public function testMultiplyNumbers(): void {
        $calculator = new Calculator();
        $this->assertEquals(6, $calculator->multiply(2, 3), "Multiplying 2 by 3 should be 6");
        $this->assertEquals(-10, $calculator->multiply(2, -5), "Multiplying by negative should work");
        $this->assertEquals(0, $calculator->multiply(10, 0), "Multiplying by zero should be 0");
    }

    // Test method for the divide() function
    public function testDivideNumbers(): void {
        $calculator = new Calculator();
        $this->assertEquals(2, $calculator->divide(6, 3), "Dividing 6 by 3 should be 2");
        $this->assertEquals(-2.5, $calculator->divide(5, -2), "Dividing by negative should work");
        $this->assertEquals(0.5, $calculator->divide(1, 2), "Dividing to get a decimal should work");
    }

    // Test method to ensure division by zero throws an exception
    public function testDivideByZeroThrowsException(): void {
        $calculator = new Calculator();
        // PHPUnit expects a specific exception class to be thrown
        $this->expectException(InvalidArgumentException::class);
        // Optionally, check for a specific exception message
        $this->expectExceptionMessage("Cannot divide by zero.");
        $calculator->divide(10, 0);
    }

    // Test method for the isPositive() function
    public function testIsPositive(): void {
        $calculator = new Calculator();
        $this->assertTrue($calculator->isPositive(5), "5 should be positive");
        $this->assertFalse($calculator->isPositive(-3), "-3 should not be positive");
        $this->assertFalse($calculator->isPositive(0), "0 should not be positive");
        $this->assertTrue($calculator->isPositive(0.1), "0.1 should be positive");
    }
}

?>
```

To run this example:

1.  Install Composer: If you don't have it, download from `getcomposer.org`.
2.  Create a project directory: `mkdir my_project && cd my_project`
3.  Install PHPUnit: `composer require --dev phpunit/phpunit`
4.  Create `src` directory: `mkdir src`
5.  Save `Calculator.php`: Place the first code snippet into `src/Calculator.php`.
6.  Create `tests` directory: `mkdir tests`
7.  Save `CalculatorTest.php`: Place the second code snippet into `tests/CalculatorTest.php`.
8.  Update `tests/CalculatorTest.php`: In `tests/CalculatorTest.php`, add `require_once __DIR__ . '/../src/Calculator.php';` at the top, just after the `use` statement, to manually include the class for simplicity in this example. For larger projects, Composer's autoloader would handle this.
9.  Run tests: `vendor/bin/phpunit tests/CalculatorTest.php` (or simply `vendor/bin/phpunit` if you have a `phpunit.xml` configuration file).