مفهوم Mocks و Stubs در phpunit

پرسیده شده
فعالیت 1074 روز پیش
دیده شده 758 بار
0

سلام

من قصد دارم پروژه ای رو که با سوکت پیاده سازی میشه طبق روند TDD توسعه بدم. متوجه شدم که برای تست نویسی آن از مفاهیم Mock و Stub استفاده میشه، ولی هر چی خود مستندات و مقاله می خونم متوجه نمیشم که چرا و چطور از اینها استفاده کنم!

و اصن فرق بین این دو چیه؟

 

ممنون میشم اگه راهنمایی بفرمایید.

فایل پیوست

محمدرضا رحیمی
محمدرضا رحیمی

30 اردیبهشت 00

1
حذف شده

سلام و احترام

 

خیلی ساده بخوام بگم فرق بین mock و stubها چیه توی phpunit اینکه که stubs ها برای متدها هستند ولی mock ها برای آبجکت ها هستند.

mockها از stubsها استفاده میکنند تا بتونند تعیین کنن که متدها وقتی صدا زده میشن چه چیزی رو با چه پارامترهایی برگردونن

اما stubs ها به طور خاص جنسشون اینطوریه که زمانی که ما یک متد رو صدا میزنیم اونو Verify میکنن

 

mock و stub یک پیاده سازی الکی هستند که ما بتونیم موقع تست کردن باهاشون ارتباط برقرار کنیم اما ینی چی؟

 

به stubها میتونیم به عنوان ورودی های کدها صداشون کنیم که زیر بار تست هستن، وقتی ما صداشون میزنیم به شکل مشخصی و با در نظر گرفتن پارامترها رفتار میکنن مثلا یه چیزی رو return میکنن یا یک exceptionی رو throw میکنن یا یک محاصباتی انجام میدن و..

حالا ما stub ها رو استفاده میکنیم که شبیه سازی کنیم کدی که داریم با کدی که زیر تست هستش، برای مثال به کد پایین نگاه کنین، ما با متد createStub اومدیم و یک stub برای کلاس SomeClass ساختیم، حالا ما مثلا میتونیم مثل مثلا بگیم که متد doSomething رو صدا بزن و بعد این متد باید foo رو بگردونه و.. و وقتی توی کد مطمئنیم که متد همچین رفتاریو داره میایم و ادامه برای اون رفتار تست مینویسم که حالا توی کد پایین از assertSame استفاده کرده

<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class StubTest extends TestCase
{
    public function testStub(): void
    {
        // Create a stub for the SomeClass class.
        $stub = $this->createStub(SomeClass::class);

        // Configure the stub.
        $stub->method('doSomething')
             ->willReturn('foo');

        // Calling $stub->doSomething() will now return
        // 'foo'.
        $this->assertSame('foo', $stub->doSomething());
    }
}    

 

اما mock، بزا یه مثال برای mock هم بزنم که خوب متوجش بشیم

اول اگه از mock استفاده نکنیم چه اتفاقی میوفته

 

فرض کنید همچین کلاسی داریم

<?php

class Person
{
    public function greeting()
    {
        return 'Hello World';
    }
}

تستش میشه یه همچین چیزی 

use PHPUnit\Framework\TestCase;

class MockPerson extends TestCase
{
    public function test_greeting()
    {
        $test = new Person();
        $this->assertEquals('Hello World', $test->greeting());
    }
}

حالا فرض کنید یه خورده میخوایم کلاسمون پیچیده تر کنیم و بهش مثلا کوئری دیتابیس رو هم اضافه کنیم، مثلا یک کلاس به اسم database اضافه میکنیم

<?php

class Database
{
    public function getPersonByID($id)
    {
        // do some stuff in the db to get a person by their ID
        return sql("select * from person where id = $id limit 1;")[0];
    }
}

و کلاس person رو یه تغییری میدیم

<?php
class Person
{
    public $db = null;
function __construct($db) 
    {
        $this->db = $db;
    }
public function greeting($id)
    {
        $friend = $this->db->getPersonByID($id);
        $friendName = $friend->name;
        return "Hello $friendName";
  }
 }

 

و تستش هم میشه شبیه به این

class MockPerson extends TestCase
{
    public function test_greeting()
    {
        $db = new Database();
        $test = new Person($db);
        $this->assertEquals('Hello Bob', $test->greeting(2));
    }
}

اما یه مشکل اینجا هستش، برای اینکه ما جلوگیری کنیم از کوئری های بیخود توی تستمون میتونیم کلاس database رو mock کنیم و داخل کدمون استفاده کنیم، برای مثال با متد getMockBuilder میتونیم از کلاسی که میخوایم یک mock درست کنیم و با متد setMethods میگیم که ما قراره یه همچین متدی به اسم getPersonByID رو mock کنیم و داشته باشیم

اول یه تغییری توی person میدیم

<?php
class Person
{
    public $db = null;
function __construct($db) 
    {
        $this->db = $db;
    }
public function greeting($id)
    {
        $friend = $this->db->getPersonByID($id);
        $friendName = $friend->name;
        return "Hello $friendName";
  }
 }

و بعد توی تست

use PHPUnit\Framework\TestCase;
class MockTest extends TestCase
{
    public function test_greeting()
    {
        $dbMock = $this->getMockBuilder(Database::class)
            ->setMethods(['getPersonByID'])
            ->getMock();
            
		$mockPerson = new stdClass();
		$mockPerson->name = 'Bob';
		
        $dbMock->method('getPersonByID')->willReturn($mockPerson);
        
		$test = new Person($dbMock);
		
        $this->assertEquals('Hello Bob', $test->greeting(2));
    }
}

حالا توی خط بعدی اومدیم یک مشخصات از person به شکل fake ایجاد کردیم منظورم این بخش از کده


$mockPerson = new stdClass();
$mockPerson->name = 'Bob';

حالا توی خط بعدیش میایم میگیم که این person ساخته شده fake رو به ما بگردون با همون getPersonByID که ما اینجا میگیم که به دیتایس کوئری نزدن و از همون mock ما برگردون

حالا توی خط بعدی طبق ساختاری که کلاس person داشت کلاس batabase ماک شده رو میدیم به کلاس database تا داخل خودش پردازش هاشو انجام بده و مقدار رو برگردونه:

$test = new Person($dbMock);

و در نهایت توی خط بعدیش هم آیدی اون کاربر که توی دیتابیس هست رو میدیم و تست ما انجام میشه

میبینید که mock هم یک نوع و یک روش دیگه هستش توی تست نویسی که استفاده میشه

 

فایل پیوست

امیر صالحی

توسط

امیر صالحی

30 اردیبهشت 00

-1
حذف شده

سلام , تا اونجایی که متوجه شدم mock  یعنی مسخره کردن یا به چالش کشیدن یک قسمت از برنامه هست که باید وظیفه خودش رو انجام بده.

 

مثلا :

 یک سرویس به نام میمون داریم که کارش بالا رفتن درخت هست.

یک سرویس به نام ماهی داریم که کارش شنا کردن هست.

 

به طور مثال میمون رو به چالش می کشونیم : این میمون نمی تونه از درخت یک متری بالا بره. (به حالت تمسخر)

این چالش کاملا منطقی و درست هست و برنامه ی میمون باید طوری نوشته بشه که از پس این چالش بر بیاد.

 

ولی اگر بگیم : هه هه .. این میمون نمی تونه شنا کنه (به حالت تمسخر )

 

خب این چالش درست نیست , چون میمون برای شنا کردن طراحی نشده , و اصل SRP نقض می شه.

 

php stub همون توضیحات کامل و جامعی هست که برای هر متد و کلاس تعریف می کنیم. همون PHP annotation هست.

 

من فکر می کنم این دو اصل می گه که متد ها  و کلاس ها رو طوری annotation بزاریم که از پس اون چالش ها بر بیاد.

 

فایل پیوست

Faraz salehi

توسط

Faraz salehi

30 اردیبهشت 00

0
حذف شده

اگر مثال تون رو با کد می گفتید بهتر بود.

ولی تا جایی که من خواندم ارتباطی با annotation نداره.

فایل پیوست

محمدرضا رحیمی

توسط

محمدرضا رحیمی

30 اردیبهشت 00