이 글은 테스트에 관한 글이 아닙니다. 기능을 개발하는 동안 제어를 유지하는 워크플로를 채택하는 것입니다. 테스트는 단지 엔진일 뿐이며 이 프로세스의 행복한 결과입니다.
이 워크플로는 제가 코딩하는 방식을 완전히 변화시켰으며 항상 제 얼굴에 미소를 짓게 했습니다. 내 희망은 그것이 당신에게도 동일하게 작용한다는 것입니다. 결국 모든 비즈니스 규칙을 충족하는 완전히 개발된 기능과 이를 1초 이내에 검증하는 테스트 모음을 갖게 됩니다.
시연에는 PHP를 사용했지만 이 워크플로는 모든 언어에 완벽하게 적용 가능합니다.
새로운 기능을 개발할 때가 되면 어디서부터 시작해야 할지 알기 어려운 경우가 많습니다. 비즈니스 로직, 컨트롤러, 아니면 프런트 엔드부터 시작해야 합니까?
언제 멈춰야 할지 아는 것도 마찬가지로 까다롭습니다. 명확한 프로세스가 없으면 진행 상황을 측정하는 유일한 방법은 수동 테스트를 통해서입니다. 지루하고 오류가 발생하기 쉬운 접근 방식입니다.
그거 아시죠? 설명할 수 없는 일이 일어나는 그 파일. 한 줄을 변경해야 하는데 시도할 때마다 전체 프로젝트가 중단되는 것 같습니다. 테스트의 안전망이 없으면 리팩토링은 협곡 위로 줄타기를 걷는 것과 같습니다.
이런 혼란을 피하기 위해 엔드투엔드 테스트를 통해 모든 기능을 테스트하기로 결정했습니다. 좋은 생각이에요! 테스트 스위트가 끝날 때까지 기다리는 동안 커피 5잔을 마실 수 있는 충분한 시간이 있다는 것을 깨닫기 전까지는 말이죠. 생산성? 창밖으로.
즉각적인 피드백을 만들기 위해 모든 수업에 대해 세분화된 테스트를 작성하기로 결정했습니다. 그러나 이제는 모든 변경으로 인해 일련의 손상된 테스트가 발생하고 결국 실제 변경 작업을 수행하는 것보다 테스트를 수정하는 데 더 많은 시간을 소비하게 됩니다. 이는 실망스럽고 비효율적이며 모든 리팩터링을 두려워하게 만듭니다.
피드백은 소프트웨어 개발의 모든 단계에서 핵심입니다. 프로젝트의 핵심을 개발하는 동안 비즈니스 규칙 중 하나라도 위반되었는지 즉시 알 수 있는 즉각적인 피드백이 필요합니다. 리팩토링 중에 코드가 깨졌는지, 안전하게 진행할 수 있는지 알려주는 도우미가 있다는 것은 매우 큰 장점입니다.
프로젝트가 할 수 있는 것, 즉 프로젝트 행동이 가장 중요한 측면입니다. 이러한 동작은 사용자 중심이어야 합니다. 구현(기능을 작동시키기 위해 생성된 코드)이 변경되더라도 사용자의 의도는 동일하게 유지됩니다.
예를 들어 사용자가 제품을 주문하면 알림을 받고 싶어합니다. 사용자에게 알리는 방법에는 이메일, SMS, 메일 등 여러 가지가 있습니다. 사용자의 의도는 변하지 않지만 구현은 종종 바뀔 것입니다.
테스트가 사용자의 의도를 나타내고 코드가 더 이상 해당 의도를 충족하지 못하는 경우 테스트는 실패해야 합니다. 그러나 리팩토링 중에도 코드가 사용자의 의도에 부합한다면 테스트가 중단되어서는 안 됩니다.
새로운 기능을 만드는 과정을 단계별로 안내받는다고 상상해 보세요. 테스트를 먼저 작성하면 올바른 기능을 코딩하는 데 도움이 됩니다. 아직 완전히 명확하지는 않지만 코딩하는 동안 테스트가 GPS로 작동하길 원합니다. 이 접근 방식을 사용하면 다음 단계를 생각할 필요 없이 GPS 지침을 따르기만 하면 됩니다. 이 개념이 여전히 불분명하다고 느껴지더라도 걱정하지 마세요! 워크플로우 부분에서 자세히 설명하겠습니다. ?
서문에서 언급했듯이 이 글은 테스트에 관한 것이 아니라 기능을 만드는 것에 관한 것입니다. 그러기 위해서는 기능이 필요합니다. 더 구체적으로 말하면, 예제로 정의된 승인 테스트를 수반하는 기능이 필요합니다. 각 기능에 대해 팀은 허용 기준이나 규칙을 설정하고 각 규칙에 대해 하나 이상의 예를 만들어야 합니다.
⚠️ 이 예시는 기술적인 측면이 설명되지 않은 사용자/도메인 중심이어야 합니다. 예제가 잘 정의되었는지 확인하는 간단한 방법은 다음과 같이 자문하는 것입니다. "내 예제가 어떤 구현(웹 프런트 엔드, HTTP API, 터미널 CLI, 실제 생활 등)에서도 작동할까요?"
예를 들어 "장바구니에 제품 추가" 기능을 개발하려는 경우 예시는 다음과 같습니다.
이러한 예는 테스트의 기초가 됩니다. 보다 복잡한 시나리오의 경우 추가적인 세부 사항을 위해 Give/When/Then 패턴을 사용하지만 단순한 시나리오에서는 필요하지 않습니다.
즉각적인 피드백과 행동에 대한 집중, 그게 꽤 어려운 일이지 않나요? Symfony MVC 서비스 아키텍처를 사용하면 이를 달성하는 것이 어려울 수 있습니다. 이것이 바로 제가 Symfony 프로젝트를 구성하는 또 다른 방법이라는 기사에서 자세히 설명한 도메인 중심 아키텍처를 사용하는 이유입니다.
행동에 집중하려면 테스트를 올바른 방식으로 구성해야 합니다. 먼저 사용자 작업 이전의 시스템 상태를 설명합니다. 다음으로 시스템 상태를 변경하는 사용자 작업을 실행합니다. 마지막으로 시스템 상태가 내 기대와 일치한다고 주장합니다.
이런 방식으로 테스트하면 시스템에서 사용자 작업이 어떻게 처리되는지는 신경쓰지 않습니다. 작업이 성공했는지 여부만 확인합니다. 이는 사용자 작업의 구현을 변경해도 테스트가 중단되지 않음을 의미합니다. 그러나 구현이 예상대로 시스템 상태를 업데이트하지 못하면 테스트가 중단됩니다. 이것이 바로 우리가 원하는 것입니다!
즉각적인 피드백을 얻으려면 시스템의 특정 부분을 조롱해야 합니다. 도메인 중심 아키텍처는 도메인이 외부 라이브러리와 상호 작용하는 인터페이스에만 의존하기 때문에 이를 가능하게 합니다. 이를 통해 이러한 종속성을 위조하는 것이 매우 쉬워지고 기능 테스트가 매우 빠르게 실행될 수 있습니다. 물론 실제 구현도 통합 테스트를 사용하여 테스트됩니다(이 기사에서는 아님).
이 워크플로에서 테스트는 내 GPS입니다! 목적지를 설정하고, 목적지에 따라 안내를 받고, 도착하면 알림을 받습니다. 테스트는 팀에서 제공하는 예시를 기반으로 구체화됩니다.
비즈니스 로직과 사용자 의도를 테스트하기 위해 기능 테스트를 사용합니다.
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket() { } }
이 파일에는 이 기능에 대한 모든 테스트가 포함되어 있지만 첫 번째 테스트부터 시작해 보겠습니다.
첫 번째 부분에서는 애플리케이션의 상태를 설명합니다. 여기에 ID가 "1"인 고객을 위한 빈 바구니가 있습니다.
$customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]);
핸들러와 명령은 사용자 의도를 나타냅니다. 이렇게 명확하고 모두가 이해할 수 있습니다.
$commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId));
? : 이 프로젝트에서는 명령과 쿼리를 분리하기로 결정했지만 AddProductToBasketUseCase가 있을 수 있습니다.
마지막으로 최종 결과가 어떤 모습일지 설명할 차례입니다. 내 장바구니에 딱 맞는 제품이 있을 것으로 기대합니다.
$expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]);
첫 번째 예제를 코드로 변환하는 테스트는 다음과 같습니다.
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket(): void { // Arrange $customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]); // Act $commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId)); // Assert $expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]); } }
이 시점에서 이 테스트에 사용한 것이 아직 존재하지 않기 때문에 내 IDE는 모든 곳에서 오류를 표시하고 있습니다. 그러나 이것이 정말로 오류입니까, 아니면 단순히 목적지를 향한 다음 단계입니까? 나는 이러한 오류를 지침으로 취급하며, 테스트를 실행할 때마다 다음에 수행할 작업을 알려줍니다. 이렇게 하면 지나치게 생각할 필요가 없습니다. GPS만 따라가는데.
? 팁: 개발자 환경을 개선하려면 변경 사항이 있을 때마다 자동으로 테스트 모음을 실행하는 파일 감시자를 활성화할 수 있습니다. 테스트 모음은 매우 빠르기 때문에 모든 업데이트에 대해 즉각적인 피드백을 받을 수 있습니다.
이 예에서는 각 오류를 하나씩 해결하겠습니다. 물론 자신감이 있다면 지름길을 택할 수도 있습니다. ?
자, 테스트를 실행해 볼까요! 각 오류에 대해 다음 단계로 넘어가는 데 필요한 최소한의 조치를 취하겠습니다.
❌ ? : "오류: "AppTestsFunctionalBasket" 클래스를 찾을 수 없음"
이 기능에 대한 첫 번째 테스트이므로 대부분의 클래스가 아직 존재하지 않습니다. 첫 번째 단계에서는 Basket 객체를 만듭니다.
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket() { } }
❌ ? : "오류: "AppTestsFunctionalInMemoryBasketRepository" 클래스를 찾을 수 없음"
InMemoryBasketRepository를 만듭니다.
$customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]);
❌ ? : "오류: "AppTestsFunctionalAddProductToBasketCommandHandler" 클래스를 찾을 수 없음"
카탈로그/애플리케이션 폴더에 AddProductToBasketCommandHandler를 생성합니다. 이 핸들러는 "장바구니에 제품 추가" 기능의 시작점이 됩니다.
$commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId));
도메인 중심 아키텍처를 존중하기 위해 저장소에 대한 인터페이스를 생성합니다. 이와 같이 종속성을 반전시킵니다.
$expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]);
❌ ? : "TypeError: AppCatalogApplicationCommandAddProductToBasketAddProductToBasketCommandHandler::__construct(): 인수 #1($basketRepository)은 AppCatalogDomainBasketRepository, AppCatalogInfrastructurePersistenceInMemoryInMemoryBasketRepository 유형이어야 합니다. 주어진"
이제 InMemory 구현은 명령 핸들러에 삽입될 수 있도록 인터페이스를 구현해야 합니다.
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket(): void { // Arrange $customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]); // Act $commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId)); // Assert $expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]); } }
❌ ? : _"오류: "AppTestsFunctionalAddProductToBasketCommand" 클래스를 찾을 수 없습니다."
_
a 명령은 항상 DTO이므로 명령을 생성하므로 읽기 전용으로 표시하고 모든 속성을 공개로 설정합니다.
// src/Catalog/Domain/Basket.php namespace App\Catalog\Domain; class Basket { public function __construct( public readonly string $id, public readonly string $customerId, public array $products = [], ) { } }
❌ ? : "두 개체가 동일하다고 주장하는 데 실패했습니다."
테스트가 컴파일 중입니다! 아직 빨간색이라 아직 끝나지 않았으니 계속 해보자! ?
비즈니스 로직을 코딩할 차례입니다. 테스트를 작성할 때와 마찬가지로 아직 아무것도 존재하지 않더라도 핸들러가 명령을 처리할 것으로 예상되는 방식을 작성합니다. 컴파일되지는 않지만 다시 한 번 테스트를 통해 다음 단계로 이동할 수 있습니다.
// src/Catalog/Infrastructure/Persistence/InMemory/InMemoryBasketRepository.php namespace App\Catalog\Infrastructure\Persistence\InMemory; class InMemoryBasketRepository { public function __construct( public array $baskets ) { } }
❌ ? : "오류: 정의되지 않은 메서드 호출 AppCatalogInfrastructurePersistenceInMemoryInMemoryBasketRepository::get()"
저장소 인터페이스에서 get 메소드를 생성합니다.
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket() { } }
❌ ? : "PHP 치명적인 오류: 클래스 AppCatalogInfrastructurePersistenceInMemoryInMemoryBasketRepository에는 1개의 추상 메서드가 포함되어 있으므로 추상으로 선언하거나 나머지 메서드(AppCatalogDomainBasketRepository::get)를 구현해야 합니다."
이제는 InMemory 저장소에 구현합니다. 테스트용이므로 정말 간단한 구현을 만들어 보겠습니다.
$customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]);
❌ ? : "오류: 정의되지 않은 메서드 AppCatalogDomainBasket::add() 호출"
add 메소드를 생성하고 이를 장바구니 객체에 구현합니다
$commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId));
❌ ? : 오류: 정의되지 않은 메서드 AppCatalogInfrastructurePersistenceInMemoryInMemoryBasketRepository::save()
호출
여기에서도 마찬가지로 인터페이스에서 메서드를 만들고 이를 인메모리 저장소에 구현합니다.
$expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]);
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket(): void { // Arrange $customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]); // Act $commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId)); // Assert $expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]); } }
✅ ? :
목적지 도착! ? ? ?
이 워크플로를 통해 개발자 경험을 게임화했습니다. 빨간 테스트에 도전하고 나면 초록색으로 바뀌는 걸 보니 도파민이 팡팡 터지죠??
시험에 합격하기 위해 일부러 조금 빠르게 진행했습니다. 이제 테스트를 개선하고 더 나은 방식으로 코드를 구현하는 데 시간을 할애할 수 있습니다.
테스트에서는 예제를 번역했지만 번역 중에 사용자의 의도를 잃었습니다. 몇 가지 기능을 사용하면 원래 예제에 훨씬 더 가깝게 만들 수 있습니다.
예:
테스트:
// src/Catalog/Domain/Basket.php namespace App\Catalog\Domain; class Basket { public function __construct( public readonly string $id, public readonly string $customerId, public array $products = [], ) { } }
이 글의 초점은 여기가 아니어서 자세히 다루지는 않겠지만, 리치 도메인 모델을 이용하면 도메인 모델을 좀 더 정확하게 표현할 수 있을 것 같습니다.
테스트가 녹색이므로 이제 원하는 만큼 리팩터링할 수 있으므로 테스트가 내 편입니다. ?
글 시작 부분에서 보셨듯이, 하나의 기능에는 이를 설명할 수 있는 예가 많이 있습니다. 이제 모두 구현할 시간입니다.
// src/Catalog/Infrastructure/Persistence/InMemory/InMemoryBasketRepository.php namespace App\Catalog\Infrastructure\Persistence\InMemory; class InMemoryBasketRepository { public function __construct( public array $baskets ) { } }
저는 다양한 테스트를 통해 이 워크플로를 여러 번 따랐으며 코드의 다양한 부분이 비즈니스 규칙에 맞게 발전했습니다. 첫 번째 테스트에서 보았듯이 내 개체는 매우 단순하고 때로는 기본적이었습니다. 그러나 새로운 비즈니스 규칙이 도입되면서 훨씬 더 스마트한 도메인 모델을 개발해야 했습니다. 내 폴더 구조는 다음과 같습니다.
도메인 모델의 캡슐화를 유지하면서 테스트를 단순화하기 위해 빌더 및 스냅샷 패턴을 도입했습니다.
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket() { } }
$customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]);
$commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId));
사용방법 :
$expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]);
나는 비즈니스 로직에 전적으로 집중하고 있기 때문에 테스트는 모든 단계에서 나를 안내하고 새 코드를 추가할 때마다 무엇을 해야 하는지 알려줍니다. 또한 모든 것이 제대로 작동하는지 확인하기 위해 더 이상 수동 테스트를 수행할 필요가 없으므로 효율성이 크게 향상됩니다.
이 기사에서는 주로 클래스를 만드는 매우 간단한 예를 보여주었습니다. 그러나 비즈니스 논리가 더 복잡해지면 각각의 새로운 규칙으로 인해 도메인 모델이 더 복잡해집니다. 리팩토링 중 내 목표는 테스트를 친환경적으로 유지하면서 도메인 모델을 단순화하는 것입니다. 정말 어려운 일이지만 마침내 성공했을 때는 믿을 수 없을 만큼 만족스럽습니다.
여기에 표시된 내용은 전체 워크플로의 첫 번째 부분에 불과합니다. 앞서 언급했듯이 완전한 기능을 갖추려면 실제 어댑터를 구현해야 합니다. 이 경우에는 실제 데이터베이스에 대한 테스트 우선 접근 방식을 사용하여 리포지토리(바구니, 고객 및 제품)를 생성하는 것이 포함됩니다. 완료되면 종속성 주입 구성에서 실제 구현을 구성하기만 하면 됩니다.
저는 프레임워크에 의존하지 않고 순수 PHP를 사용했으며 모든 비즈니스 로직을 캡슐화하는 데만 집중했습니다. 이 전략을 사용하면 더 이상 테스트 성능에 대해 걱정할 필요가 없습니다. 수백 또는 수천 개의 테스트가 있더라도 단 몇 초 만에 실행되어 코딩하는 동안 즉각적인 피드백을 제공합니다.
성능에 대한 힌트를 드리기 위해 10,000번 반복된 테스트 모음은 다음과 같습니다.
이 워크플로는 여러 개념의 교차점에 있습니다. 필요한 경우 더 자세히 살펴볼 수 있도록 그 중 일부를 소개하겠습니다.
기능을 설명하는 가장 좋은 방법은 필요한 만큼 많은 예를 제공하는 것입니다. 팀 회의는 기능 작동 방식에 대한 구체적인 예를 식별하여 기능을 깊이 이해하는 데 도움이 될 수 있습니다.
Given-When-Then 프레임워크는 이러한 예를 공식화하는 훌륭한 도구입니다.
이러한 예를 코드로 변환하려면 Gherkin 언어(PHP의 Behat 포함)가 도움이 될 수 있습니다. 그러나 저는 개인적으로 PHPUnit 테스트에서 직접 작업하고 이러한 키워드를 사용하여 함수 이름을 지정하는 것을 선호합니다.
'우리가 원하는 것은 무엇인가?' 부분은 F.I.R.S.T라는 약어로 요약될 수 있습니다.
이제 테스트가 제대로 이루어졌는지 확인할 수 있는 체크리스트가 생겼습니다.
이러한 모든 아키텍처는 구현 세부 사항에서 비즈니스 로직을 분리하는 것을 목표로 합니다. 포트 및 어댑터, 육각형 또는 클린 아키텍처를 사용하든 핵심 아이디어는 비즈니스 논리 프레임워크를 독립적이고 테스트하기 쉽게 만드는 것입니다.
이 점을 염두에 두고 구현하면 다양한 구현이 가능하며 가장 적합한 구현은 상황과 선호도에 따라 달라집니다. 이 아키텍처의 가장 큰 장점은 비즈니스 로직을 분리함으로써 훨씬 더 효율적인 테스트가 가능하다는 것입니다.
당신은 이미 테스트 피라미드에 익숙할 것 같습니다. 대신 다이아몬드 표현을 사용하는 것을 선호합니다. Thomas Pierrain이 설명하는 이 전략은 사용 사례 중심이며 애플리케이션의 동작에 중점을 둡니다.
이 전략은 또한 애플리케이션의 블랙박스 테스트를 장려하여 애플리케이션이 생성하는 방식보다는 생성되는 결과에 중점을 둡니다. 이 접근 방식을 사용하면 리팩토링이 훨씬 쉬워집니다.
워크플로 전체에서 보셨듯이 테스트와 명령 처리기에서 저는 항상 원하는 것이 존재하기도 전에 작성했습니다. 나는 몇 가지 "소원"을 빌었습니다. 이러한 접근 방식을 희망적 사고 프로그래밍이라고 합니다. 이를 통해 구현 방법을 결정하기 전에 시스템의 구조와 시스템과 상호 작용하는 이상적인 방법을 상상할 수 있습니다. 테스트와 결합하면 프로그래밍을 안내하는 강력한 방법이 됩니다.
이 패턴은 구조 테스트를 효과적으로 수행하는 데 도움이 됩니다. 먼저 시스템이 올바른 초기 상태로 설정됩니다. 다음으로 명령이나 사용자 작업과 같은 상태 수정자가 실행됩니다. 마지막으로 작업 후 시스템이 예상된 상태인지 확인하기 위한 어설션이 수행됩니다.
우리는 수용 테스트 중심 개발과 결합된 도메인 중심 아키텍처가 어떻게 개발 경험을 변화시킬 수 있는지 살펴보았습니다. 즉각적인 피드백, 강력한 테스트, 사용자 의도에 대한 집중은 코딩을 더욱 효율적일 뿐만 아니라 더욱 즐겁게 만듭니다.
이 워크플로를 시도하면 테스트가 녹색으로 바뀔 때마다 미소를 지을 수 있습니다 ✅ ➡️ ?
당신의 완벽한 작업 흐름은 무엇인지, 이 작업에서 어떤 점을 개선하고 싶은지 댓글로 알려주세요!
위 내용은 단 몇 초 안에 모든 비즈니스 로직을 테스트하세요의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!