模擬旨在測試真實物件的行為。
它們模擬依賴關係,因此您不必呼叫可能顯著減慢單元測試速度的外部資源。
您可以定義期望並驗證它們。
例如,您可以確保某個方法被呼叫特定次數和/或使用某些參數:
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { public function testMockExample(): void { $depencencyMock = $this->createMock(MyDependency::class); $dependencyMock->expects($this->exactly(2)) ->method('someMethod') ->with('some parameter'); $classToTest = new ClassToTest($dependencyMock); } }
willReturn() 確保與回傳類型的相容性:
// In code class MyClass { public function getNum(): int { } } // In tests $myClassMock = $this->createMock(MyClass::class); $myClassMock->expects($this->once()) ->method('getNum') ->willReturn(2);
如果你想根據輸入參數來測試動態行為,也可以使用 willReturnCallback。
由於模擬僅模仿真實行為,因此很容易錯過重點。讓我們討論一下常見的不良做法:
❌不要這樣做:
$colorServiceMock = $this->createMock(ColorService::class); $colorServiceMock->method('hexToName') ->willReturn('red'); $color = (new MyClass($colorServiceMock))->getColorName('ff0000');
✅ 相反,加入一些期望:
$colorServiceMock->expects($this->once()) ->method('hexToName') ->with('00f00') ->willReturn('green'); $color = (new MyClass($colorServiceMock))->getColorName('00f00');
記住模擬的目的是驗證交互作用。
讓我們來測試一下實作 SomeInterface 的 MyClass。
❌不要這樣做:
$myclassMock = $this->createMock(MyClass::class);
✅ 相反,模擬介面:
$myclassMock = $this->createMock(SomeInterface::class);
模擬關注行為。介面通常不會改變,因為您應該修改實現,而不是契約。
Tomas Votruba 精美地解釋了這個問題:從過度模擬測試中提取價值的 5 種方法
很容易忽略組件之間的緊密耦合:
$productRepositoryMock = $this->createMock(ProductRepository::class); $invoiceRepositoryMock = $this->createMock(InvoiceRepository::class); $emailServiceMock = $this->createMock(EmailService::class); $overComplexService = new OverComplexService($productRepositoryMock, $invoiceRepositoryMock, $emailServiceMock);
上面的範例打破了關注點分離,而模擬則延續了這種不良做法。
模擬是強大的工具,但單元測試還不夠。您需要各種其他類型的測試(例如整合、e2e)。
除了不良做法之外,還有其他跡象可能表明項目中誤用或過度使用了模擬:
Martin Fowler 寫了一篇精彩的文章,解釋了為什麼 Mock 不是 Stub。
讓我們看看您可能會使用它們的具體情況:
這裡有一些測試案例,其中模擬更有意義:
您可以非常方便地使用 PHPUnit 建立存根:
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { public function testMockExample(): void { $depencencyMock = $this->createMock(MyDependency::class); $dependencyMock->expects($this->exactly(2)) ->method('someMethod') ->with('some parameter'); $classToTest = new ClassToTest($dependencyMock); } }
以下是一些測試案例,其中存根更有意義:
簡而言之,存根並不是為了檢視真實物件的行為,而是狀態。
單元測試的主要目的是確保每個單元/組件按預期工作,但除了實際程式碼之外,您還必須維護這些測試。
存根可以簡化測試設置,對於不需要追蹤方法呼叫和互動的簡單場景非常有效。
它可以透過集中某些測試來防止不必要的複雜性。
Mock 可以追蹤方法呼叫及其參數。
不要忘記回傳代表真實行為的值。否則,你可能會產生一種錯誤的安全感。
應謹慎使用模擬,以避免不必要的維護複雜性。
以上是PHP:我該嘲笑還是該走?的詳細內容。更多資訊請關注PHP中文網其他相關文章!