Yêu cầu thg 4 11, 2019 3:03 SA 436 1 1
  • 436 1 1
+2

Viết test sao cho đúng

Chia sẻ
  • 436 1 1

Chào mọi người, mình hiện tại đang muốn viết test. Nhưng đang gặp vấn đề vì không biết test như nào cho chặt chẽ. Mình có ví dụ sau. Mình đang viết test cho 1 hàm trong repository

    /**
     * @inheritdoc
     */
    public function getPermission()
    {
        return $this->model->orderBy('updated_at', 'DESC')->get();
    }

Đây là đoạn mình viết test case

<?php

namespace Tests\Unit\RepositoryTest;

use DB;
use Mockery;
use Tests\TestCase;
use App\Models\Permission;
use Tests\SeedDataTrait;
use App\Repositories\PermissionRepository;
use Illuminate\Support\Collection as Collection;

class PermissionRepositoryTest extends TestCase
{
    use SeedDataTrait;

    /**
     * Setup
     *
     * @param  mixed $modelPermission
     *
     * @return void
     */
    public function setUp()
    {
        parent::setUp();
        $this->modelPermission = Mockery::mock(Permission::class);
        $this->repository = new PermissionRepository(
            $this->modelPermission
        );
    }

    /**
     * Test get permission
     *
     * @return void
     */
    public function testGetPermission()
    {
        $permission = factory(Permission::class)->create();
        $queryPermission = Permission::query();
        $this->modelPermission->shouldReceive('orderBy')->andReturn($queryPermission);
        $this->modelPermission->shouldReceive('get')->andReturn($permission);
        $result = $this->repository->getPermission();

        $this->assertInstanceOf(Collection::class, $result);
        $this->assertArrayHasKey('name', $result[0]);
    }
}

Ở đây mình tư duy test như sau

  1. Hàm orderBy trả về 1 QueryBuilder nên mình tao 1 seeder là 1 QueryBuilder
  2. Hàm get trả về 1 collecttion nên mình tạo 1 collection rồi return khi shouldReceive

Thắc mắc của mình là

  1. Đây là test kiểu Unit Test hay Integration Test ? Làm sao để phân biệt giữa 2 cái này ?
  2. Ở đây mình muốn test chặt chẽ hơn 1 chút, cụ thể là test đối với câu truy vấn SQL thuần thì phải thiết kế test như thế nào ? Tức là mình muốn so sách câu SQL select * from table order by updated_at as DESC thuần với câu truy vấn hiện tại của mình xem có khớp hay không ?

Rất vui khi được trao đổi với mọi người, Ai có examples test chuẩn có thể cho mình xin luôn ạ. Cảm ơn mọi người

thg 4 11, 2019 6:28 SA

Tư duy của mình nghĩ ở đây là:

  • Test cho function getPermission thì gồm công việc
  1. Dùng Factory tạo dữ liệu test
  2. Check xem nó có đủ thông tin các field cần lấy ra không ?
  3. Check số lượng bản ghi có đúng theo dữ liệu mình tạo ra và query không ?
  4. Thứ tự sắp xếp các bản ghi có đúng không ?
  • Đây chính là test cho repository (integration test)

1 CÂU TRẢ LỜI


Đã trả lời thg 4 12, 2019 1:08 SA
+2

Đây là test kiểu Unit Test hay Integration Test ? Làm sao để phân biệt giữa 2 cái này ?

Bạn có thể tham khảo thêm về vấn đề này ở đây

Hiểu đơn giản thì Unit Test là quá trình mà ở đó, tất cả các Test Case cho từng function/method riêng biệt được viết, còn Integration Test là công đoạn trong kiểm thử phần mềm mà ở đó, từng đơn vị phần mềm riêng biệt được kết hợp lại, và kiểm tra như một khối thống nhất.

Một function test của bạn sẽ là Integration test nếu nó thực hiện một trong những việc sau đây:

  • Trigger để chạy codes từ những functions khác trong project (từ function này gọi đến, và rồi chạy code ở function khác trong cùng project)
  • Truy vấn vào cơ sở dữ liệu
  • Sử dụng file system
  • Truy cập mạng
  • ...

Ở đây mình muốn test chặt chẽ hơn 1 chút, cụ thể là test đối với câu truy vấn SQL thuần thì phải thiết kế test như thế nào?

Bạn có thể dùng hàm DB::enableQueryLog() để lưu lại log của câu truy vấn. Sau khi gọi hàm của model xong, bạn dùng DB::getQueryLog() thì sẽ lấy được nội dung câu sql mà bạn vừa thưc hiện.

Đấy là cách viết đơn giản, theo kiểu integration test, chứ muốn viết theo kiểu unit test thì nó sẽ khó khăn hơn nhiều, khi bạn sẽ phải mock cả database connection, ngăn việc truy vấn database thực sự xảy ra 😂 Bạn có thể tham khảo cách viết này.

Ai có examples test chuẩn có thể cho mình xin luôn ạ

Không dám nói là chuẩn, nhưng bạn có thể xem qua project này https://github.com/framgia/laravel-test-examples . Mặc dù được viết cách đây 2 năm rồi, hiện cũng không còn được maintain mấy nữa, nhưng hy vọng có thể giúp ích đôi chút cho bạn (^^;)

Chia sẻ
Avatar No Naem @Naem
thg 4 12, 2019 1:43 SA

Đoạn code như ở repository ở trên theo anh nên test như thế nào cho hợp lý ạ :-? Phần ở trên đang dùng model để viết repo thì test truy vấn sql sẽ ntn ạ :-?

Avatar jquer @jquery123
thg 4 12, 2019 1:58 SA

@thangtd90 Em cảm ơn anh vì câu trả lời ạ, Em cũng đang follow theo laravel test của framgia. Nhưng trong phần repository đang thiết kế khác em một chút nên đang k follow theo test repository trong đó được. Cụ thể là ở đây em inject trực tiếp Model vào để sử dụng Eloquent còn ví dụ của Framgia đang dùng Query\Builder

<?php

namespace App\Repositories;

use App\Models\Permission;
use App\Repositories\BaseRepository;
use App\Repositories\Contracts\PermissionRepositoryInterface;

/**
 * Class RoleRepository
 *
 * @package App\Repositories
 */
class PermissionRepository extends BaseRepository implements PermissionRepositoryInterface
{
    /**
     * @var Permission
     */
    protected $model;

    /**
     * RoleRepository constructor.
     *
     * @param Permission $model
     */
    public function __construct(Permission $model)
    {
        parent::__construct($model);
    }
}

Nếu coi unit test em viết hiện tại là Bad Practices thì sửa như thế nào để nó thành Good Practices anh nhỉ.

Avatar Tran Duc Thang @thangtd90
thg 4 12, 2019 4:18 SA

@Naem @jquery123 Như anh đã chia sẻ ở trên thì để viết được Unit Test cho phần mà có liên quan đến Model rất là khó, đặc biệt là mọi người yêu cầu test cả các hàm mà có query Database. Để thực hiện được điều đó thì đòi hỏi các em phải mock cả Database connection cơ.

Cái project https://github.com/framgia/laravel-test-examples/ này là trước đây một người bạn làm cùng với anh viết ra. Mấy cái đoạn mock Database connection để viết unit test cho những phần liên quan đến Model cũng là do anh ý thiết kế, thật sự rất hay và đáng bái phúc 😄

Nếu em đang thiết kế repo theo kiểu là một tầng ở phía trước Model như hiện tại, và muốn test những query gọi từ model như vậy thì em có thể tham khảo đoạn này https://github.com/framgia/laravel-test-examples/blob/master/tests/Unit/Http/Controllers/CityControllerTest.php#L32 . Đây là phần viết Unit Test cho Controller, có các đoạn test cho các hàm CRUD ở Controller, trong đó có dùng model đấy. Hơi phức tạp một chút, giờ anh đọc lại cũng không hiểu lắm =)) Nhìn chung là flow là mock database connection, chạy hàm cần test, và expect rằng đã có câu lệnh select, update, create hay delete được gọi, với arguments cụ thể là gì, và kết quả sẽ ra sao.

P/S: Đoạn test ban đầu mà @jquery123 có viết thì cũng rất gần với khái niệm Unit Test rồi, tuy nhiên nó vẫn chưa phải là một hàm test tốt, khi mà em chỉ test được là hàm orderBy với get sẽ được gọi thôi. Chứ nội dung kết quả của hàm get thì em chưa test được. Giả sử như đoạn test của em đang pass, giờ anh vào sửa code trong repository, đoạn $this->model->orderBy('updated_at', 'DESC')->get();, anh sửa DESC thành ASC, hoặc gọi thêm một vài hàm nữa như limit chẳng hạn, thì nó vẫn tiếp tục pass 😜

Avatar jquer @jquery123
thg 4 12, 2019 5:47 SA

@thangtd90 Anh vừa nhắc em mới nhớ thêm 1 vấn đề nữa khi viết Test. Như em hiểu thì mình sẽ viết test dựa vào spec dự án đưa ra nhưng ở đây hình như mình đang test theo hướng dựa vào logic code, dẫn tới việc khi sửa code đồng nghĩa với việc sửa test, Vây có phải mình đã đi sai đường chăng ? Rất mong được trao đổi với anh và mọi người 😅😅

Avatar kopitop @kopitop
thg 4 12, 2019 6:26 SA

@jquery123 Ở đây mình muốn test chặt chẽ hơn 1 chút, cụ thể là test đối với câu truy vấn SQL thuần thì phải thiết kế test như thế nào ? Tức là mình muốn so sách câu SQL select * from table order by updated_at as DESC thuần với câu truy vấn hiện tại của mình xem có khớp hay không ?

Bản thân mục đích test của em đang bị chệch hướng rồi, bản thân eloquent lẫn mysql đều là các interface/ và implementation có sẵn, có nghĩa là em không có trách nhiệm test chúng nó 😄

thg 4 12, 2019 6:34 SA

Với cùng một đoạn code, không phải cứ áp dụng được nhiều kỹ thuật test vào là nó sẽ trở nên "tốt". Tùy vào mục đích, chi phí và hàng tá các tiêu chí khác mà bạn có thể áp dụng kỹ thuật test phù hợp.

Chỉ xét riêng trong ví dụ này, tôi sẽ không viết unit test cho repository, hãy sử dụng integration test cho phần này.

Avatar kopitop @kopitop
thg 4 12, 2019 6:37 SA

1 cái nữa là em đang test repository nhưng trong test em lại dùng eloquent làm active record

good practice :

  • mock database connection (ví dụ in-memory chẳng hạn) -> mock kết quả trả về từ hàm model->orderBy('updated_at', 'DESC')->get();
  • spy hàm orderBy('updated_at', 'DESC') ,đặt các expect cho nó được gọi mấy lần và những tham số có như mình kỳ vọng không
  • so sánh kết quả trả về từ repository với kỳ vọng của mình

-> bản thân model là các library dùng sẵn nên mình chỉ nên dùng nó như 1 spy chứ không nên mock , vì sao ? bởi vì nghiễm nhiên model nhận đúng tham số thì nó phải trả về 1 kết quả đúng -> không đúng thì đi kiện thằng viết library nhé

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí