Ver4.0 ๐งช PHP ํ ์คํ ์ ๋ฌธ๋ถํฐ PHPUnit ๊ธฐ๋ฐ ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD) ์ค์ ๊น์ง

๐งช PHP ํ ์คํ ์ ๋ฌธ๋ถํฐ PHPUnit ๊ธฐ๋ฐ ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD) ์ค์ ๊น์ง
"๋ด ์ฝ๋ ์ ๋์๊ฐ๋๋ฐ์?"๋ผ๊ณ ๋งํ๋ค๊ฐ ์๋ฒฝ 3์์ ์๋ฒ ๋ถ์ก๊ณ ์ธ์ด๋ณธ ๋ชจ๋ PHP ๊ฐ๋ฐ์๋ฅผ ์ํ ์ด์ผ๊ธฐ
โ ๋ค์ด๊ฐ๋ฉฐ : ํ ์คํธ๋ ์ ํญ์ "๋์ค์"๊ฐ ๋๋๊ฐ
์น๊ตฌ์ผ, ์์งํ๊ฒ ์๊ธฐํด๋ณด์.
์ฐ๋ฆฌ PHP ๊ฐ๋ฐ์๋ค์ ๋ธ๋ผ์ฐ์ ์๋ก๊ณ ์นจ์ด ๊ณง ํ
์คํธ์๋ ์์ ์ ๋ค๋ค ํ ๋ฒ์ฉ ๊ฒช์์์.
var_dump() ์ฐ๊ณ , die() ๋ฐ๊ณ , ํ๋ฉด์ ๋ฐฐ์ด ๋ฟ๋ ค๋ณด๊ณ ,
"์ค์ผ์ด ๋์๋ค!" ํ๊ณ ์ปค๋ฐ. ๊ทธ๋ฆฌ๊ณ ๋ฐฐํฌ. ๊ทธ๋ฆฌ๊ณ โฆ ์ฅ์ .
์ฌ์ค ์ด๊ฑด ๊ฐ๋ฐ์๊ฐ ๊ฒ์๋ฌ์๊ฐ ์๋์ผ.
PHP๋ ํ์์ด "์์ฒญ ํ๋ ๋ฐ์์ HTML ๋ฑ๊ณ ์ฃฝ๋" ์ธ์ด๋ผ์,
ํ ๋ฒ ์คํํ๊ณ ๋๋๋ ๊ตฌ์กฐ๊ฐ ๋๋ฌด ์์ฐ์ค๋ฌ์ ๊ฑฐ๋ .
ํ๋ก์ธ์ค๊ฐ ์ด์์์ง ์์ผ๋ "ํ
์คํธ๋ฅผ ๋ฐ๋ก ๋๋ฆฐ๋ค"๋ ๊ฐ๋
์์ฒด๊ฐ ๋ฏ์ค์์ง.
๊ทธ๋ฐ๋ฐ ์ง๊ธ์ ์ํฉ์ด ์์ ํ ๋ฌ๋ผ์ก์ด.
Composer, PSR ํ์ค, LaravelยทSymfony ๊ฐ์ ํ๋ ์์ํฌ,
๊ทธ๋ฆฌ๊ณ PHPUnit์ด๋ผ๋ ์ฌ์ค์์ ํ์ค ํ
์คํธ ํ๋ ์์ํฌ๊น์ง.
PHP ์ํ๊ณ๋ ์ด์ ํ
์คํธ ์ ์ง๋ฉด ์คํ๋ ค ์ด์ํ ๋๋ค๊ฐ ๋์ด.
์ด ๊ธ์์๋ PHPUnit์ ๊ธฐ๋ณธ๊ธฐ๋ถํฐ,
ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD)์ Red-Green-Refactor ์ฌ์ดํด,
๊ทธ๋ฆฌ๊ณ ์ค๋ฌด์์ ์ง์ง ์จ๋จน๋ ๋ชจํนยท๋ฐ์ดํฐ ํ๋ก๋ฐ์ด๋ยท์ปค๋ฒ๋ฆฌ์ง๊น์ง
์น๊ตฌ๋ ์นดํ์์ ์๋ค ๋จ๋ฏ ์ญ ํ์ด๋ณผ๊ฒ.
๐งญ 1. PHPUnit์ด๋ ๋ฌด์์ธ๊ฐ
xUnit ๊ฐ๋ฌธ์ PHP ์ฅ๋จ
PHPUnit์ Sebastian Bergmann์ด 2004๋
๊ฒฝ๋ถํฐ ๋ง๋ค์ด์จ PHP์ฉ ๋จ์ ํ
์คํธ ํ๋ ์์ํฌ์ผ.
๋ฟ๋ฆฌ๋ Kent Beck์ SUnit / JUnit ๊ณ์ด, ์ฆ xUnit ์ํคํ
์ฒ๋ฅผ ๋ฐ๋ฅด๊ณ ์์ด.
๊ทธ๋์ Java์ JUnit์ด๋ Python์ unittest๋ฅผ ์จ๋ณธ ์ฌ๋์ด๋ผ๋ฉด
setUp(), tearDown(), assertEquals() ๊ฐ์ ์ด๋ฆ์ด ์ต์ํ ๊ฑฐ์ผ.
์ฉ์ด๊ฐ ๊ฑฐ์ ๊ณต์ฉ์ด์ฒ๋ผ ํต์ผ๋ผ ์๊ฑฐ๋ .
๋ฒ์ ์ด์ผ๊ธฐ (์ค์!)
PHPUnit์ PHP ๋ฒ์ ์ ๊ฐํ๊ฒ ๋ฌถ์ฌ ์์ด.
๋๋ต์ ์ธ ์ง์ ๊ด๊ณ๋ ์ด๋ฐ ์์ด์ผ.
| PHPUnit ๋ฉ์ด์ | ์๊ตฌ PHP | ํน์ง |
|---|---|---|
| 9.x | PHP 7.3+ | ๋ ๊ฑฐ์ ํ๋ก์ ํธ์์ ์ฌ์ ํ ๋ง์ด ์ฌ์ฉ |
| 10.x | PHP 8.1+ | ์ด๋ฒคํธ ์์คํ ์ฌ์์ฑ, ์ค์ ํ์ผ ์คํค๋ง ๋ณ๊ฒฝ |
| 11.x | PHP 8.2+ | ์ดํธ๋ฆฌ๋ทฐํธ ์ค์ฌ ์ ํ ๊ฐ์ |
| 12.x | PHP 8.3+ | ์ด๋ ธํ ์ด์ (์ฃผ์) ๋ฐฉ์ ์ฌ์ค์ ์ ๋ฆฌ, ์ต์ ๊ธฐ์ค |
โ ๏ธ ๊ฐ์ฅ ๋ง์ด ๊ฒช๋ ํจ์
์์ ๋ธ๋ก๊ทธ ๊ธ ๋ณด๊ณ /** @test */, @dataProvider ๊ฐ์
PHPDoc ์ด๋
ธํ
์ด์
์ ์ผ๋๋ฐ ์ต์ PHPUnit์์ deprecated ๊ฒฝ๊ณ ๊ฐ ๋จ๋ ๊ฒฝ์ฐ.
PHPUnit 10๋ถํฐ๋ PHP 8 ์ดํธ๋ฆฌ๋ทฐํธ(#[Test], #[DataProvider])๊ฐ ๊ถ์ฅ ๋ฐฉ์์ด์ผ.
์ค์น๋ Composer ํ ์ค
composer require --dev phpunit/phpunit
# ์คํ
./vendor/bin/phpunit
# ๋ฒ์ ํ์ธ
./vendor/bin/phpunit --version
--dev ํ๋๊ทธ ๊ผญ ๋ถ์ฌ์ค.
ํ
์คํธ ๋๊ตฌ๋ฅผ ํ๋ก๋์
์์กด์ฑ์ ๋ฃ์ผ๋ฉด ๋ฐฐํฌ ์ฉ๋๋ ๋๊ณ ,
๋ณด์ ๊ด์ ์์๋ ์ข์ ๊ฒ ํ๋ ์์ด.
๐๏ธ 2. ํ๋ก์ ํธ ๋ผ๋ ์ก๊ธฐ
ํ
์คํธ๋ฅผ ์์ํ๋ ค๋ฉด ์คํ ๋ก๋ฉ์ด ๋จผ์ ์ผ.
PSR-4 ๊ท์น์ผ๋ก composer.json์ ์ด๋ ๊ฒ ์ก์๋ณด์.
{
"autoload": {
"psr-4": { "App\\": "src/" }
},
"autoload-dev": {
"psr-4": { "Tests\\": "tests/" }
},
"require-dev": {
"phpunit/phpunit": "^11.0"
}
}
๊ทธ๋ฆฌ๊ณ composer dump-autoload ํ ๋ฒ ์คํ.
phpunit.xml ์ค์ ํ์ผ
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
cacheDirectory=".phpunit.cache"
failOnWarning="true"
failOnRisky="true">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
๐ก ํ
failOnWarning๊ณผ failOnRisky๋ฅผ ์ผ๋๋ฉด
"ํ
์คํธ๋ ํต๊ณผํ์ง๋ง ์ฌ์ค ์๋ฌด๊ฒ๋ ๊ฒ์ฆ ์ ํ" ์ ๋ น ํ
์คํธ๋ฅผ ์ก์๋ผ ์ ์์ด.
assertion์ด ํ๋๋ ์๋ ํ
์คํธ๋ risky๋ก ๋ถ๋ฅ๋๊ฑฐ๋ .
๐ด๐ข๐ต 3. TDD์ ์ฌ์ฅ, Red-Green-Refactor
TDD๋ Kent Beck์ด ์ ๋ฆฌํ ๊ฐ๋ฐ ๋ฐฉ๋ฒ๋ก ์ธ๋ฐ,
ํต์ฌ์ ๋จ ํ๋์ผ. "ํ
์คํธ๋ฅผ ๋จผ์ ์ด๋ค."
๋ง์ ๊ฐ๋จํ๋ฐ ์ฒ์ ๋ค์ผ๋ฉด ์ง์ง ์ด์ํ๊ฒ ๋๊ปด์ง์ง.
"์๋, ์๋ ์ฝ๋๋ฅผ ์ด๋ป๊ฒ ํ
์คํธํด?"
๊ทผ๋ฐ ๊ทธ๊ฒ ๋ฐ๋ก ํฌ์ธํธ์ผ. ํ
์คํธ๋ฅผ ๋จผ์ ์ด๋ค๋ ๊ฑด ๊ณง "์ค๊ณ๋ฅผ ๋จผ์ ํ๋ค"๋ ๋ป์ด๊ฑฐ๋ .
๋จ๊ณ๋ณ๋ก ๋ฏ์ด๋ณด๊ธฐ
1๋จ๊ณ ยท RED ๐ด
์์ง ์กด์ฌํ์ง ์๋ ๊ธฐ๋ฅ์ ๋ํ ํ
์คํธ๋ฅผ ์ด๋ค.
๋น์ฐํ ์คํจํ๋ค. ํด๋์ค๊ฐ ์์ผ๋ Fatal Error๊ฐ ๋ ์๋ ์์ด.
์คํจ๋ฅผ "ํ์ธ"ํ๋ ๊ฒ ์ค์ํด. ์คํจ๋ฅผ ์ ๋ณด๋ฉด ๊ทธ ํ
์คํธ๊ฐ
์ง์ง ๊ฒ์ฆํ๊ณ ์๋์ง ์ ์๊ฐ ์๊ฑฐ๋ .
2๋จ๊ณ ยท GREEN ๐ข
ํ
์คํธ๋ฅผ ํต๊ณผ์ํค๋ ๊ฐ์ฅ ๋จ์ํ ์ฝ๋๋ฅผ ์ด๋ค.
์ฌ์ง์ด ์์๋ฅผ ๊ทธ๋ฅ return ํด๋ฒ๋ ค๋ ๋ผ. ๋ถ๋๋ฌ์ํ ํ์ ์์ด.
์ด๊ฑธ "๊ฐ์ง๋ก ํต๊ณผ์ํค๊ธฐ(Fake It)"๋ผ๊ณ ๋ถ๋ฅด๋๋ฐ, ์ ์ TDD ๊ธฐ๋ฒ์ด์ผ.
3๋จ๊ณ ยท REFACTOR ๐ต
ํ
์คํธ๊ฐ ์ด๋ก๋ถ์ธ ์ํ์์ ์ฝ๋๋ฅผ ์ ๋ฆฌํ๋ค.
์ค๋ณต ์ ๊ฑฐ, ์ด๋ฆ ๊ฐ์ , ๋ฉ์๋ ์ถ์ถ.
ํ
์คํธ๊ฐ ์์ ๋ง์ด๋๊น ๋ง์๊ป ๋ฏ์ด๊ณ ์น ์ ์์ด.
ํต์ฌ ๊ท์น 3๊ฐ์ง (Uncle Bob ๋ฒ์ )
1. ์คํจํ๋ ํ
์คํธ๊ฐ ์์ผ๋ฉด ํ๋ก๋์
์ฝ๋๋ฅผ ์์ฑํ์ง ์๋๋ค.
2. ์คํจ์ํค๊ธฐ์ ์ถฉ๋ถํ ๋งํผ๋ง ํ
์คํธ๋ฅผ ์์ฑํ๋ค.
3. ํ์ฌ ์คํจ ์ค์ธ ํ
์คํธ๋ฅผ ํต๊ณผ์ํฌ ๋งํผ๋ง ํ๋ก๋์
์ฝ๋๋ฅผ ์์ฑํ๋ค.
๐ 4. ์ค์ TDD : ์ฅ๋ฐ๊ตฌ๋ ํ ์ธ ๊ณ์ฐ๊ธฐ ๋ง๋ค๊ธฐ
๋ง๋ก๋ง ํ๋ฉด ๊ฐ์ด ์ ์ค๋๊น, ์ง์ง ํ๋ ๋ง๋ค์ด๋ณด์.
์๊ตฌ์ฌํญ์ ์ด๋.
์๊ตฌ์ฌํญ
ยท ์ฅ๋ฐ๊ตฌ๋์ ์ํ(์ด๋ฆ, ๊ฐ๊ฒฉ, ์๋)์ ๋ด์ ์ ์๋ค.
ยท ์ด์ก์ ๊ณ์ฐํ๋ค.
ยท ์ด์ก์ด 50,000์ ์ด์์ด๋ฉด 10% ํ ์ธ.
ยท 100,000์ ์ด์์ด๋ฉด 15% ํ ์ธ.
ยท ํ ์ธ ๊ธ์ก์ ์ ๋จ์ ์ ์ฌ(๋ด๋ฆผ).
๐ด Step 1 : ์ฒซ ๋ฒ์งธ ์คํจ ํ ์คํธ
<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Cart;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\Test;
final class CartTest extends TestCase
{
#[Test]
public function ๋น_์ฅ๋ฐ๊ตฌ๋์_์ด์ก์_0์์ด๋ค(): void
{
$cart = new Cart();
$this->assertSame(0, $cart->total());
}
}
์คํํ๋ฉด?
Error: Class "App\Cart" not found
์๋ฒฝํด. ์ด๊ฒ ์ ์์ ์ธ RED ์ํ์ผ.
๊ทธ๋ฆฌ๊ณ ํ๊ตญ์ด ๋ฉ์๋๋ช
, PHP์์ ์๋ฌด ๋ฌธ์ ์์ด ์ ๋์๊ฐ.
ํ ์ปจ๋ฒค์
๋ง ๋ง์ผ๋ฉด ํ
์คํธ ๊ฐ๋
์ฑ์ด ํ ์ฌ๋ผ๊ฐ์ ์๊ทผํ ์ธ๊ธฐ ์๋ ๋ฐฉ์์ด์ผ.
๐ข Step 2 : ์ต์ ๊ตฌํ
<?php
declare(strict_types=1);
namespace App;
final class Cart
{
public function total(): int
{
return 0;
}
}
"์ด๊ฒ ๋ฌด์จ ๊ตฌํ์ด์ผ?" ์ถ์ง? ๊ทผ๋ฐ ์ด๊ฒ ๋ง์.
ํ์ฌ ํ
์คํธ๋ฅผ ํต๊ณผ์ํค๋ ์ต์ ์ฝ๋๋๊น.
๋ค์ ํ
์คํธ๊ฐ ์ด ๊ฑฐ์ง๋ง์ ๊นจ๋จ๋ ค ์ค ๊ฑฐ์ผ.
๐ด Step 3 : ๋ค์ ํ ์คํธ๋ก ์๋ฐํ๊ธฐ
#[Test]
public function ์ํ์_๋ด์ผ๋ฉด_๊ฐ๊ฒฉ์_ํฉ์ด_์ด์ก์ด_๋๋ค(): void
{
$cart = new Cart();
$cart->add('์๋ฉ๋ฆฌ์นด๋
ธ', 4500, 2);
$cart->add('ํฌ๋ก์์', 3800, 1);
$this->assertSame(12800, $cart->total());
}
์ด์ return 0;์ ๋ชป ๋ฒํ
จ. ์ง์ง ๊ตฌํ์ ํด์ผ ํด.
<?php
declare(strict_types=1);
namespace App;
use InvalidArgumentException;
final class Cart
{
/** @var array<int, array{name:string, price:int, qty:int}> */
private array $items = [];
public function add(string $name, int $price, int $qty = 1): void
{
if ($price < 0) {
throw new InvalidArgumentException('๊ฐ๊ฒฉ์ ์์์ผ ์ ์์ต๋๋ค.');
}
if ($qty < 1) {
throw new InvalidArgumentException('์๋์ 1 ์ด์์ด์ด์ผ ํฉ๋๋ค.');
}
$this->items[] = ['name' => $name, 'price' => $price, 'qty' => $qty];
}
public function total(): int
{
$sum = 0;
foreach ($this->items as $item) {
$sum += $item['price'] * $item['qty'];
}
return $sum;
}
}
๐ข Step 4 : ํ ์ธ ๊ท์น ์ถ๊ฐ
์ด์ ํ ์ธ์ด์ผ. ์ฌ๋ฌ ์ผ์ด์ค๋ฅผ ํ ๋ฒ์ ๊ฒ์ฆํ๊ณ ์ถ์ผ๋
Data Provider๋ฅผ ์จ๋ณด์. ์ด๊ฒ PHPUnit์ ์ง์ง ๋งค๋ ฅ ํฌ์ธํธ ์ค ํ๋์ผ.
use PHPUnit\Framework\Attributes\DataProvider;
#[Test]
#[DataProvider('ํ ์ธ_์๋๋ฆฌ์ค')]
public function ์ด์ก์_๋ฐ๋ผ_ํ ์ธ์ด_์ ์ฉ๋๋ค(
int $์๊ธ, int $๊ธฐ๋๊ฐ
): void {
$cart = new Cart();
$cart->add('ํ
์คํธ์ํ', $์๊ธ, 1);
$this->assertSame($๊ธฐ๋๊ฐ, $cart->payable());
}
public static function ํ ์ธ_์๋๋ฆฌ์ค(): array
{
return [
'ํ ์ธ ์์ ๊ฒฝ๊ณ ์๋' => [49_999, 49_999],
'10% ํ ์ธ ์์์ ' => [50_000, 45_000],
'10% ๊ตฌ๊ฐ ์๋จ' => [99_999, 89_999], // 89999.1 โ ๋ด๋ฆผ
'15% ํ ์ธ ์์์ ' => [100_000, 85_000],
'๊ณ ์ก ๊ฒฐ์ ' => [250_000, 212_500],
];
}
์ด๋ ๊ฒ ๋ฐฐ์ด ํค์ ์ด๋ฆ์ ๋ถ์ฌ๋๋ฉด
์คํจํ์ ๋ ์ด์ก์_๋ฐ๋ผ_ํ ์ธ์ด_์ ์ฉ๋๋ค with data set "10% ๊ตฌ๊ฐ ์๋จ"
์ด๋ฐ ์์ผ๋ก ๋ฑ ์ง์ด์ ์๋ ค์ค. ๋๋ฒ๊น
์๊ฐ์ด ํ ์ค์ด๋ค์ด.
public function payable(): int
{
$total = $this->total();
$rate = match (true) {
$total >= 100_000 => 0.15,
$total >= 50_000 => 0.10,
default => 0.0,
};
return (int) floor($total * (1 - $rate));
}
๐ธ ๋ถ๋์์์ ์ฃผ์๋ณด
๋ ๊ณ์ฐ์ float๋ฅผ ์ฐ๋ฉด 0.1 + 0.2 !== 0.3 ๊ฐ์ ๊ณ ์ ์ ์ธ ์ง์ฅ์ด ์ด๋ ค.
์ค๋ฌด์์๋ ๊ธ์ก์ ์ ์(์ ๋จ์)๋ก ๋ค๋ฃจ๊ฑฐ๋
BCMath / brick/money ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐ๋ ๊ฒ ์์ ํด.
ํ
์คํธ์์ float๋ฅผ ๋น๊ตํด์ผ ํ๋ค๋ฉด assertEqualsWithDelta()๋ฅผ ์ฐ์.
๐ต Step 5 : ๋ฆฌํฉํฐ๋ง
์ง๊ธ Cart๊ฐ "๋ด๊ธฐ + ๊ณ์ฐ + ํ ์ธ์ ์ฑ
"๊น์ง ๋ค ํ๊ณ ์์ด.
๋จ์ผ ์ฑ
์ ์์น(SRP) ๊ด์ ์์ ํ ์ธ ์ ์ฑ
์ ๋ถ๋ฆฌํด๋ณด์.
interface DiscountPolicy
{
public function rateFor(int $total): float;
}
final class TieredDiscountPolicy implements DiscountPolicy
{
public function rateFor(int $total): float
{
return match (true) {
$total >= 100_000 => 0.15,
$total >= 50_000 => 0.10,
default => 0.0,
};
}
}
final class Cart
{
public function __construct(
private readonly DiscountPolicy $policy = new TieredDiscountPolicy()
) {}
// ...
}
์ฌ๊ธฐ์ ๋ง๋ฒ์ด ์ผ์ด๋.
๋ฆฌํฉํฐ๋ง ํ์๋ ํ
์คํธ๋ฅผ ๋ค์ ๋๋ฆฌ๋ฉด ์ ๋ถ ์ด๋ก๋ถ์ด์ผ.
๋ด๋ถ ๊ตฌ์กฐ๋ฅผ ํต์งธ๋ก ๋ฐ๊ฟจ๋๋ฐ ๋์์ ๊ทธ๋๋ก๋ผ๋ ๊ฑธ 1์ด ๋ง์ ์ฆ๋ช
ํ ๊ฑฐ์ง.
์ด๊ฒ TDD๋ฅผ ํ๋ ์ง์ง ์ด์ ์ผ.
ํ
์คํธ๋ ๋ฒ๊ทธ ์ก๋ ๋๊ตฌ์ด๊ธฐ ์ด์ ์ ๋ณ๊ฒฝ์ ๋๋ ค์ํ์ง ์๊ฒ ํด์ฃผ๋ ์์ ๋ฒจํธ๊ฑฐ๋ .
๐ญ 5. ํ ์คํธ ๋๋ธ : ๋ชจํน์ ์ธ๊ณ
์ค๋ฌด ์ฝ๋๋ ์์ ๊ณ์ฐ๋ง ํ์ง ์์ง.
DB์ ๋ถ๊ณ , API ํธ์ถํ๊ณ , ๋ฉ์ผ ๋ณด๋ด๊ณ , ํ์ผ ์ฐ๊ณ โฆ.
์ด๋ฐ ๊ฑธ ํ
์คํธ์์ ์ง์ง๋ก ์คํํ๋ฉด?
๋๋ฆฌ๊ณ , ๋ถ์์ ํ๊ณ , ์ง์ง ๊ณ ๊ฐํํ
๋ฉ์ผ ๋ ์๊ฐ๋ ์ฐธ์ฌ๊ฐ ์๊ฒจ.
๊ทธ๋์ ํ
์คํธ ๋๋ธ(Test Double), ํํ ๋งํ๋ "๊ฐ์ง ๊ฐ์ฒด"๋ฅผ ์ฐ๋ ๊ฑฐ์ผ.
| ์ข ๋ฅ | ์ญํ | ๋น์ |
|---|---|---|
| Dummy | ์๋ฆฌ๋ง ์ฑ์, ์ค์ ๋ก ์ ์ฐ์ | ๋ง๋คํน |
| Stub | ๋ฏธ๋ฆฌ ์ ํด์ง ๊ฐ์ ๋ฐํ | ๋๋ณธ ์ฝ๋ ๋ฐฐ์ฐ |
| Spy | ํธ์ถ ๊ธฐ๋ก์ ๋จ๊น | CCTV |
| Mock | ํธ์ถ ๊ธฐ๋์น๋ฅผ ๊ฒ์ฆ | ๊น๊นํ ๊ฐ๋ ๊ด |
| Fake | ๋จ์ํ์ง๋ง ๋์ํ๋ ๊ตฌํ | ์ธ๋ฉ๋ชจ๋ฆฌ DB |
PHPUnit์์ Stub ๋ง๋ค๊ธฐ
#[Test]
public function ํ์จ_API๊ฐ_์คํจํ๋ฉด_๊ธฐ๋ณธํ์จ์_์ฌ์ฉํ๋ค(): void
{
$api = $this->createStub(ExchangeRateApi::class);
$api->method('fetch')
->willThrowException(new \RuntimeException('timeout'));
$converter = new CurrencyConverter($api, fallbackRate: 1300);
$this->assertSame(130_000, $converter->toKrw(100));
}
Mock์ผ๋ก "ํธ์ถ๋๋์ง" ๊ฒ์ฆํ๊ธฐ
#[Test]
public function ์ฃผ๋ฌธ_์๋ฃ์_์๋ฆผ์ด_์ ํํ_ํ๋ฒ_๋ฐ์ก๋๋ค(): void
{
$notifier = $this->createMock(Notifier::class);
$notifier->expects($this->once())
->method('send')
->with(
$this->equalTo('user@example.com'),
$this->stringContains('์ฃผ๋ฌธ์ด ์๋ฃ')
);
$service = new OrderService($notifier);
$service->complete(new Order('user@example.com'));
}
๐ง ๋ชจํน ๋จ์ฉ ๊ฒฝ๊ณ
๋ชจ๋ ๊ฑธ ๋ค ๋ชจํนํ๋ฉด ํ
์คํธ๊ฐ ๊ตฌํ ์ธ๋ถ์ฌํญ์ ๋ชป ๋ฐํ๋ฒ๋ ค.
๋ฆฌํฉํฐ๋ง๋ง ํด๋ ํ
์คํธ๊ฐ ์ฐ์์ ๊นจ์ง๋ "๊นจ์ง๊ธฐ ์ฌ์ด ํ
์คํธ(Fragile Test)"๊ฐ ๋์ง.
์์น์ ์ด๊ฑฐ์ผ โ ๋ด๊ฐ ์์ ํ์ง ์์ ๊ฒ(์ธ๋ถ API, ์๊ฐ, ๋๋ค, ํ์ผ์์คํ
)๋ง ๋ชจํนํ๋ค.
๋ด ๋๋ฉ์ธ ๋ก์ง๋ผ๋ฆฌ๋ ์ฌ๋งํ๋ฉด ์ง์ง ๊ฐ์ฒด๋ก ๋ถ์ฌ์ ํ
์คํธํ๋ ๊ฒ ๋ ๊ฒฌ๊ณ ํด.
์๊ฐยท๋๋ค ๋ค๋ฃจ๊ธฐ
time()์ด๋ rand()๋ฅผ ์ฝ๋์ ์ง์ ๋ฐ์ผ๋ฉด ํ
์คํธ๊ฐ ๋ถ๊ฐ๋ฅํด์ ธ.
"์์ ์๋ง ์คํจํ๋ ํ
์คํธ" ๊ฐ์ ๊ดด๋ด์ด ์ฌ๊ธฐ์ ๋์.
interface Clock { public function now(): \DateTimeImmutable; }
final class FrozenClock implements Clock
{
public function __construct(private \DateTimeImmutable $t) {}
public function now(): \DateTimeImmutable { return $this->t; }
}
// ํ
์คํธ์์
$clock = new FrozenClock(new \DateTimeImmutable('2025-01-01 00:00:00'));
$coupon = new Coupon(expiresAt: '2024-12-31', clock: $clock);
$this->assertTrue($coupon->isExpired());
์ฐธ๊ณ ๋ก PSR-20์ ClockInterface๊ฐ ํ์ค์ผ๋ก ์ ์๋ผ ์์ด์,
์ ํ๋ก์ ํธ๋ผ๋ฉด ๊ทธ๊ฑธ ๊ทธ๋๋ก ์ฐ๋ ๊ฒ ์ข์.
๐ 6. Assertion, ์ ๋๋ก ๊ณจ๋ผ ์ฐ๊ธฐ
PHPUnit์ assertion์ด ์์ญ ๊ฐ ์์ด. ๊ทผ๋ฐ ์ค๋ฌด์์ 90%๋ ์ด ์ ๋๋ก ๋๋.
| ๋ฉ์๋ | ์ฉ๋ |
|---|---|
assertSame() | ๊ฐ + ํ์ ๊น์ง ์๊ฒฉ ๋น๊ต (===). ๊ธฐ๋ณธ์ผ๋ก ์ด๊ฑฐ ์ฐ์ |
assertEquals() | ๋์จํ ๋น๊ต. ๊ฐ์ฒด๋ ์์ฑ ๋จ์ ๋น๊ต |
assertTrue/False() | ๋ถ๋ฆฌ์ธ ๊ฒ์ฆ |
assertNull() | null ์ฌ๋ถ |
assertCount() | ๋ฐฐ์ด/Countable ๊ฐ์ |
assertInstanceOf() | ํ์ ๊ฒ์ฆ |
assertArrayHasKey() | ๋ฐฐ์ด ํค ์กด์ฌ |
assertStringContainsString() | ๋ฌธ์์ด ํฌํจ |
expectException() | ์์ธ ๋ฐ์ ๊ฒ์ฆ |
โ ๏ธ assertEquals์ ํจ์
assertEquals(0, 'hello')๋ PHP 7๊น์ง๋ ํต๊ณผํ์ด(๋์จํ ๋น๊ต ๋๋ฌธ์).
PHP 8์์ ๋น๊ต ๊ท์น์ด ๋ฐ๋์ด ์ง๊ธ์ ์๋์ง๋ง,
assertEquals('1', 1)์ ์ฌ์ ํ ํต๊ณผํด.
์๋ํ ๊ฒ ์๋๋ผ๋ฉด ๋ฌด์กฐ๊ฑด assertSame์ ์ฐ๋ ์ต๊ด์ ๋ค์ด์.
์์ธ ํ ์คํธ
#[Test]
public function ์์_๊ฐ๊ฒฉ์_๊ฑฐ๋ถ๋๋ค(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('๊ฐ๊ฒฉ์ ์์์ผ ์ ์์ต๋๋ค.');
(new Cart())->add('์ด์ํ์ํ', -1000);
}
expectException()์ ๋ฐ๋์ ์์ธ๊ฐ ํฐ์ง๋ ์ฝ๋ ๋ฐ๋ก ์์ ์จ์ผ ํด.
๊ทธ๋ฆฌ๊ณ ๊ทธ ๋ค์ ์ฝ๋๋ฅผ ๋ ์ฐ๋ฉด ์คํ๋์ง ์์ผ๋,
ํ ํ
์คํธ์๋ ์์ธ ํ๋๋ง ๊ฒ์ฆํ๋ ๊ฒ ์์น์ด์ผ.
๐ 7. ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง, ์ซ์์ ์์ง ๋ง์
์ปค๋ฒ๋ฆฌ์ง๋ ํ
์คํธ๊ฐ ์คํํ ์ฝ๋์ ๋น์จ์ด์ผ.
์ธก์ ํ๋ ค๋ฉด Xdebug(coverage ๋ชจ๋) ๋๋ PCOV ํ์ฅ์ด ํ์ํด.
์๋๋ง ๋๊ณ ๋ณด๋ฉด PCOV๊ฐ ์๋์ ์ผ๋ก ๋น ๋ฅด๋ CI์์๋ PCOV๋ฅผ ์ถ์ฒํ ๊ฒ.
# HTML ๋ฆฌํฌํธ ์์ฑ
XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html build/coverage
# ํฐ๋ฏธ๋์์ ์์ฝ๋ง
XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-text
๐จ ์ปค๋ฒ๋ฆฌ์ง 100%์ ๊ฑฐ์ง๋ง
public function divide(int $a, int $b): float
{
return $a / $b; // ์ด ์ค์ 100% ์ปค๋ฒ๋จ
}
// ํ
์คํธ
$this->assertSame(2.0, $calc->divide(4, 2)); // ํต๊ณผ!
๋ผ์ธ ์ปค๋ฒ๋ฆฌ์ง 100%. ๊ทผ๋ฐ divide(4, 0)์? DivisionByZeroError ๐ฅ
์ปค๋ฒ๋ฆฌ์ง๋ "ํ
์คํธ๋์ง ์์ ๊ณณ"์ ์ฐพ๋ ์ง๋์ผ ๋ฟ,
"ํ
์คํธ๊ฐ ์ถฉ๋ถํ๋ค"๋ ์ฆ๋ช
์๊ฐ ์๋์ผ.
ํ์ค์ ์ธ ๋ชฉํ์น๋ ๋ณดํต ์ด๋.
ยท ํต์ฌ ๋๋ฉ์ธ ๋ก์ง : 85~95% ์งํฅ
ยท ์ ์ฒด ํ๋ก์ ํธ : 70~80%๋ฉด ๊ฑด๊ฐํ ํธ
ยท ๋ทฐ/ํ
ํ๋ฆฟ/์ค์ ํ์ผ : ์ปค๋ฒ๋ฆฌ์ง ๋์์์ ์ ์ธ
๐งฑ 8. ํ ์คํธ ํผ๋ผ๋ฏธ๋์ ํ์ค
Mike Cohn์ด ์ ์ํ ํ ์คํธ ํผ๋ผ๋ฏธ๋๋ ์ด๋ฐ ๊ตฌ์กฐ์ผ.
PHP ํ๋ก์ ํธ์์์ ์ค์ ๊ตฌ๋ถ
Unit ํ
์คํธ โ DB๋, ํ์ผ๋, ๋คํธ์ํฌ๋ ์ ๊ฑด๋๋ฆผ.
์์ PHP ๊ฐ์ฒด๋ง. ์์ฒ ๊ฐ๋ฅผ ๋ช ์ด ๋ง์ ๋๋ฆด ์ ์์ด์ผ ํด.
Integration ํ
์คํธ โ ์ค์ DB(๊ฐ๋ฅํ๋ฉด SQLite in-memory๋
๋์ปค๋ก ๋์ด MySQL)์ ๋ถ์ด์ Repository๋ Query๊ฐ ์ ๋๋์ง ํ์ธ.
ํธ๋์ญ์
์ผ๋ก ๊ฐ์ธ๊ณ ํ
์คํธ ๋๋๋ฉด ๋กค๋ฐฑํ๋ ํจํด์ด ๊ตญ๋ฃฐ์ด์ผ.
protected function setUp(): void
{
$this->pdo = new PDO('sqlite::memory:');
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)');
}
protected function tearDown(): void
{
$this->pdo = null; // ์ธ๋ฉ๋ชจ๋ฆฌ DB๋ ์ฐ๊ฒฐ ๋๊ธฐ๋ฉด ์๋ ์๋ฉธ
}
E2E / Feature ํ
์คํธ โ HTTP ์์ฒญ์ ์ค์ ๋ก ์ด์
์ํ ์ฝ๋์ ์๋ต ๋ณธ๋ฌธ์ ํ์ธ. Laravel์ $this->get('/api/users')๋
Symfony์ WebTestCase๊ฐ ์ด ์์ญ์ด์ผ.
โ๏ธ 9. CI์ ๋ฌผ๋ฆฌ๊ณ , ์ต๊ด์ผ๋ก ๋ง๋ค๊ธฐ
ํ
์คํธ๋ ์๋์ผ๋ก ๋์์ผ ์ด์๋จ์.
์ฌ๋์ด ๊ธฐ์ตํด์ ๋๋ฆฌ๋ ํ
์คํธ๋ ๊ฒฐ๊ตญ ์ ๋๊ฒ ๋๊ฑฐ๋ .
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
phpunit:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.2', '8.3', '8.4']
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: pcov
- run: composer install --prefer-dist --no-progress
- run: vendor/bin/phpunit --coverage-text
๊ฐ์ด ์ฐ๋ฉด ์ข์ ๋๊ตฌ๋ค
| ๋๊ตฌ | ์ญํ |
|---|---|
| PHPStan / Psalm | ์ ์ ๋ถ์. ์คํ ์์ด ํ์ ์ค๋ฅ ํ์ง |
| Pest | PHPUnit ์์ ์นํ ํํ๋ ฅ ์ข์ ๋ฌธ๋ฒ ๋ ์ด์ด |
| Infection | ๋ฎคํ ์ด์ ํ ์คํ . ํ ์คํธ์ "ํ์ง"์ ์ธก์ |
| PHP-CS-Fixer | ์ฝ๋ ์คํ์ผ ์๋ ์ ๋ฆฌ |
ํนํ Infection์ ์ง์ง ํฅ๋ฏธ๋ก์.
์ฝ๋๋ฅผ ์ผ๋ถ๋ฌ ๋ณํ(>๋ฅผ >=๋ก ๋ฐ๊พธ๋ ์)์ํจ ๋ค
ํ
์คํธ๊ฐ ๊ทธ๊ฑธ ์ก์๋ด๋์ง ํ์ธํด.
์ก์๋ด์ง ๋ชปํ๋ฉด "๊ทธ ํ
์คํธ๋ ์ฌ์ค ์๋ฌด๊ฒ๋ ์งํค์ง ๋ชปํ๊ณ ์๋ค"๋ ๋ป์ด์ง.
์ปค๋ฒ๋ฆฌ์ง ์ซ์๋ณด๋ค ํจ์ฌ ์ ์งํ ์งํ์ผ.
๐งฏ 10. ํํ ์ค์์ ํด๊ฒฐ์ฑ
โ ํ
์คํธ๋ผ๋ฆฌ ์๋ก ์์กดํ๋ค
A ํ
์คํธ๊ฐ ๋ง๋ ๋ฐ์ดํฐ๋ฅผ B๊ฐ ์ฐ๋ ๊ตฌ์กฐ. ์์ ๋ฐ๋๋ฉด ์์ฅ์ฐฝ.
โ ๊ฐ ํ
์คํธ๋ setUp()์์ ์๊ธฐ ์ํ๋ฅผ ์ค์ค๋ก ๋ง๋ค์ด์ผ ํด.
--order-by=random ์ต์
์ผ๋ก ์์๋ฅผ ์์ด ๋๋ ค๋ณด๋ฉด ๋ฐ๋ก ๋คํต๋.
โก ํ ํ
์คํธ์์ 20๊ฐ์ง๋ฅผ ๊ฒ์ฆํ๋ค
์ฒซ ๋ฒ์งธ assertion์ด ๊นจ์ง๋ฉด ๋๋จธ์ง๋ ์คํ๋ ์ ๋ผ.
โ ํ
์คํธ ํ๋๋น ๊ฐ๋
ํ๋. ์ด๋ฆ๋ง ๋ด๋ ๋ญ ๊ฒ์ฆํ๋์ง ์๊ฒ.
โข ํ
์คํธ๊ฐ 5๋ถ์ฉ ๊ฑธ๋ฆฐ๋ค
โ ์ญ์คํ๊ตฌ Unit ํ
์คํธ์ธ ์ฒํ๋ Integration ํ
์คํธ์ผ.
DB ๋ถ๋ ๋๋ค์ testsuite๋ฅผ ๋ถ๋ฆฌํ๊ณ , ํ์์ Unit๋ง ๋๋ฆฌ์.
โฃ private ๋ฉ์๋๋ฅผ Reflection์ผ๋ก ์ต์ง๋ก ํ
์คํธํ๋ค
โ ๊ทธ๊ฑด ์ ํธ์ผ. "์ด private ๋ก์ง์ด ์ฌ์ค์ ๋ณ๋ ํด๋์ค์ฌ์ผ ํ๋ค"๋.
public ์ธํฐํ์ด์ค๋ฅผ ํตํด ๊ฐ์ ์ ์ผ๋ก ๊ฒ์ฆ๋๋ ๊ฒ ์ ์์ด์ผ.
โค ๋ฒ๊ทธ ๊ณ ์น ๋ ํ
์คํธ๋ฅผ ์ ์ด๋ค
โ ๋ฒ๊ทธ ๋ฆฌํฌํธ๋ฅผ ๋ฐ์ผ๋ฉด ๋จผ์ ๊ทธ ๋ฒ๊ทธ๋ฅผ ์ฌํํ๋ ์คํจ ํ
์คํธ๋ฅผ ์ฐ์.
๊ทธ๊ฒ ํ๊ท ๋ฐฉ์ง(Regression) ํ
์คํธ๊ฐ ๋๊ณ ,
๊ฐ์ ๋ฒ๊ทธ๊ฐ ๋ ๋ฒ ๋์ค๋ ๊ฑธ ์๊ตฌํ ๋ง์์ค.
๐ฑ 11. ๊ทธ๋์, ์ด๋ป๊ฒ ์์ํ ๊น
"๊ธฐ์กด ๋ ๊ฑฐ์ ํ๋ก์ ํธ์ธ๋ฐ ํ
์คํธ๊ฐ 0๊ฐ์์. ์ด๋๋ถํฐ ์๋์ฃ ?"
๊ฐ์ฅ ๋ง์ด ๋ฃ๋ ์ง๋ฌธ์ด์ผ. ๋ต์ ๋ช
ํํด.
์ ๋ถ ๋ค ์ง๋ ค๊ณ ํ์ง ๋ง.
1๏ธโฃ ์๋ก ์ฐ๋ ์ฝ๋๋ถํฐ ํ
์คํธ๋ฅผ ๋ถ์ฌ. ๊ณผ๊ฑฐ๋ ์ผ๋จ ๋๋ฌ.
2๏ธโฃ ๋ฒ๊ทธ๊ฐ ๋ ์ง์ ์ ํ
์คํธ๋ฅผ ์ฌ์ด. ์ํ ๊ณณ์ด ๊ณง ์ค์ํ ๊ณณ์ด์ผ.
3๏ธโฃ ๊ณ์ฐ ๋ก์งยท๊ฒ์ฆ ๋ก์ง์ฒ๋ผ ์
์ถ๋ ฅ์ด ๋ช
ํํ ์์ ํจ์๋ถํฐ. ๊ฐ์ฑ๋น ์ต๊ณ .
4๏ธโฃ ๋ฆฌํฉํฐ๋งํ ์ฝ๋ ์ฃผ๋ณ์ ํน์ฑํ ํ
์คํธ(Characterization Test)๋ฅผ ๋จผ์ ๊น์.
ย ย ย ํ์ฌ ๋์์ด ์ณ๋ ๊ทธ๋ฅด๋ ์ผ๋จ "์ง๊ธ ์ด๋ ๊ฒ ๋์ํ๋ค"๋ฅผ ๊ณ ์ ์ํค๋ ๊ฑฐ์ง.
๊ทธ๋ฆฌ๊ณ ํ๋ ๋.
TDD๋ ์ข
๊ต๊ฐ ์๋๋ผ ๋๊ตฌ์ผ.
ํ๋กํ ํ์
๋ง๋ค ๋, UI ๋ง๋ค ๋, ํ ๋ฒ ์ฐ๊ณ ๋ฒ๋ฆด ์คํฌ๋ฆฝํธ์๋
๊ตณ์ด TDD๋ฅผ ๊ฐ์ํ ํ์ ์์ด.
๋์ ๋์ด ์ค๊ฐ๋ ๋ก์ง, ๊ถํ ๊ฒ์ฌ, ๋ณต์กํ ์ํ ์ ์ด,
์ฌ๋ฌ ์ฌ๋์ด ๊ณ์ ๊ฑด๋๋ฆด ์ฝ์ด ๋ชจ๋โ
์ฌ๊ธฐ์ ๋ฐ๋์ ํ
์คํธ๊ฐ ์์ด์ผ ํด. ๊ทธ๊ฒ ํ๋ก๋ค์์ด์ผ.
์์ฆ์ PHP ํ
์คํ
์ด๋ TDD ๊ฐ์ ์ฃผ์ ๋ ํผ์ ํ๊ธฐ๋ณด๋ค๋
์ค๋ฌด ๊ฒฝํ์์๊ฒ ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ๊ฑฐ๋ ์งง์ ๋ฉํ ๋ง์ ๋ฐ๋ ๊ฒ ํจ์ฌ ๋นจ๋ผ.
์ฌ๋ฅ๋ท์ฒ๋ผ ๊ฐ๋ฐ ๋ถ์ผ ์ฌ๋ฅ์ ์ฃผ๊ณ ๋ฐ๋ ํ๋ซํผ์์๋
PHPUnit ์ค์ ๋ถํฐ ๋ ๊ฑฐ์ ์ฝ๋ ํ
์คํธ ์ ๋ต๊น์ง ์ง์ ๋ฌผ์ด๋ณผ ์ ์์ด์,
๋ฌธ์๋ง ์ฝ์ ๋ ์๊ธฐ๋ "๊ทธ๋์ ์ค๋ฌด์์ ์?" ๊ฐ์ฆ์ ๊ฝค ์ ์ฑ์์ฃผ๋๋ผ.
๐ ๋ง๋ฌด๋ฆฌ : ์ด๋ก๋ถ์ ์๋๊ฐ
์ฒ์ TDD๋ฅผ ์์ํ๋ฉด ๋ฌด์กฐ๊ฑด ๋๋ ค.
"๊ธฐ๋ฅ ํ๋ ๋ง๋๋ ๋ฐ ์ฝ๋๋ฅผ ์ ๋ ๋ฐฐ๋ก ์ฐ์ง?" ์ถ๊ณ , ๋ต๋ตํด.
๊ทผ๋ฐ 3๊ฐ์์ฏค ์ง๋์
์๋ฌด๋ ๊ฑด๋๋ฆฌ๊ธฐ ๋ฌด์์ํ๋ ๊ฒฐ์ ๋ชจ๋์ ๋ฆฌํฉํฐ๋งํ๊ณ ,
ํฐ๋ฏธ๋์ OK (247 tests, 891 assertions)๊ฐ ์ด๋ก์์ผ๋ก ๋จ๋ ์๊ฐโ
๊ทธ ์๋๊ฐ์ ์ง์ง ๊ฒช์ด๋ด์ผ ์์.
๋ฐฐํฌ ๋ฒํผ ๋๋ฅด๋ ์์ด ์ ๋จ๋ฆฌ๋ ๊ฒฝํ, ๊ทธ๊ฑฐ ๊ฝค ์ค๋
์ฑ ์๊ฑฐ๋ . ๐
์ค๋์ ์์ฝ ๐
ยท PHPUnit์ PHP์ ์ฌ์ค์ ํ์ค ํ
์คํธ ํ๋ ์์ํฌ, Composer๋ก --dev ์ค์น
ยท ์ต์ ๋ฒ์ ์์ ์ด๋
ธํ
์ด์
๋์ PHP 8 ์ดํธ๋ฆฌ๋ทฐํธ(#[Test]) ์ฌ์ฉ
ยท TDD์ ๋ฆฌ๋ฌ์ Red โ Green โ Refactor, ์ฌ์ดํด์ ์งง๊ฒ
ยท assertEquals๋ณด๋ค assertSame์ ๊ธฐ๋ณธ์ผ๋ก
ยท Data Provider๋ก ๊ฒฝ๊ณ๊ฐ์ ํ ๋ฒ์ ๊ฒ์ฆ
ยท ๋ชจํน์ "๋ด๊ฐ ์์ ํ์ง ์์ ๊ฒ"์๋ง
ยท ์ปค๋ฒ๋ฆฌ์ง๋ ์ง๋์ผ ๋ฟ, ๋ชฉ์ ์ง๊ฐ ์๋๋ค
ยท ์์์ ์ ์ฝ๋์ ๋ฒ๊ทธ ์ง์ ๋ถํฐ, ์ ๋ถ ๋ค ํ๋ ค๊ณ ํ์ง ๋ง ๊ฒ
์, ์ด์ ํฐ๋ฏธ๋ ์ด๊ณ composer require --dev phpunit/phpunit ํ ์ค๋ง ์ณ๋ณด์.
์ฒซ ๋ฒ์งธ ๋นจ๊ฐ๋ถ์ด ๋น์ ์ ๊ธฐ๋ค๋ฆฌ๊ณ ์์ด. ๐ด โ ๐ข
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.