Ở bài trước mình đã giới thiệu qua về Codeception để viết test tự động , bạn nào chưa xem thì có thể tham khảo ở link bên dưới .

https://vietnamlab.vn/blog/2016/12/21/gioi-thieu-ve-codeception-framework-testing/

Lần này mình sẽ giới thiệu về test tự động các chức năng lên kết với các service bên ngoài hoặc liên quan tới mail bằng cách sử dụng Codeception/PhantomJS . Với PhantomJS thì ta có thể test được các những trang có Javascript.(PhpBrowser thì không thể)

Xây dựng Codeception dựa trên nền Yii2

Cấu hình của Yii2

Project lấy Yii2 Advanced Framework làm cơ sở , bao gồm các module tiêu chuẩn frontend, backend, console, common. Lần này ta sẽ lấy frontend làm đối tượng để viết test, trong thư mục gốc của Project ta thiết lập 1 file codeception.yml như bên dưới. Trong mục include ta thiết lập module frontend, sau này nếu viết test cho các module khác thì ta có thể thêm vào tiếp theo.

codeception.yml

# global codeception file to run tests from all apps
include:
    - frontend
paths:
    log: logs
settings:
    colors: true

Để sinh ra các test template ta chạy command phía dưới trong các thư mục của từng module.

$ ../vendor/bin/codecept bootstrap

Lần này để có thể test kiểm tra các response khi request tới các URL, ta chuẩn bị các file config ở trong các module (codeception.yml) và ở acceptance(acceptance.suite.yml). Thiết lập sao cho có thể sử dụng được thư viện HTTP client của Yii2 như  phía dưới .

frontend/codeception.yml

namespace: frontend\tests  
actor: AcceptanceTester  
paths:  
    tests: tests
    log: tests/_output
    data: tests/_data
    helpers: tests/_support
settings:  
    bootstrap: _bootstrap.php
    colors: true
    memory_limit: 1024M
modules:  
    enabled:
        - Yii2:
            part: [httpclient]
            configFile: 'config/test.php'

Frontend thì được test dựa trên browser nên ta sử dụng WebDriver để test . Các môi trường Staging và Product có URL khác nhau chính vì vậy ta phân tách từng môi trường riêng biệt ra . Ngoài ra ta cũng có thể thêm nhiều module khác nhau, ví dụ như thêm REST module sẽ cho phép bạn có thể test dựa trên HTTP.

frontend/tests/acceptance.suite.yml

class_name: AcceptanceTester  
modules:  
    enabled:
        - WebDriver:
    config:
        WebDriver:
           browser: phantomjs
           host: '192.168.33.20'
           clear_cookies: true
           capabilities:
               phantomjs.page.settings.userAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36

env:
staging:
modules:
config:
WebDriver:
url: https://stg-gmo.example.com

prod:
    modules:
        config:
            WebDriver:
                url: https://gmo.example.com </code></pre>

Chuyển đổi qua lại các môi trường test

Ta có thể chuyển đổi môi trường test khi chạy thông qua option --env , thông tin về các môi trường được thiết lập trong file frontend/tests/acceptance.suite.yml phía trên.

vendor/bin/codecept run --env staging

Chuẩn bị data test

Trong Codeception ta tạo ra các file dự liệu test và cung cấp cho từng test case , và cũng có thể tạo dữ liệu test tùy theo từng môi trường khác nhau.

frontend/tests/acceptance/data/common.yml

staging:  
    user:
        user_id: 1000
        password: stagingpass

prod:
user:
user_id: 2000
password: prodpass

Ví dụ về sử dụng dữ liệu test trong Codeception.

frontend/tests/acceptance/DataProviderExampleCest.php

<?php

namespace frontend\tests\acceptance;

use Codeception\Example;
use frontend\tests\AcceptanceTester;
use Symfony\Component\Yaml\Yaml;

class DataProviderExampleCest
{
/**
* @dataprovider _provider
*/
public function checkDataProviderExample(AcceptanceTester $I, Example $example)
{
$example['user']['user_id'];
$example['user']['password'];
}

public function _provider()
{
    $common_data = Yaml::parse(file_get_contents(getcwd() . "/frontend/tests/acceptance/data/common.yml"));
    $env = getenv('env');

    return [$common_data[$env]];
}

}

Chạy test theo từng môi trường.(staging)

export env=staging;vendor/bin/codecept run --env $env

Gmail API và cách sử dụng

https://vietnamlab.vn/blog/2017/05/31/su-dung-google-gmail-api-de-thao-tac-voi-mail/

Mình có viết 1 bài chi tiết về cách sử dụng Gmail API bạn nào chưa rõ thì có thể tham khảo.

Các bước cần chuẩn bị để sử dụng Gmail API

  • Tạo Project từ Google API Console.
  • Chọn Project vừa được tạo.
  • Tạo OAuth Client ID từ trang API Manager.
  • Lấy Client ID và Client Secret.

Test các chức nằng liên kết với các service bên ngoài.

Tôi sẽ giới thiệu về việc làm sao để test các chức năng mà có sử dụng các API bên ngoài. Ví dụ như viết test case cho chức năng liên quan tới Gmail Khi đăng ký 1 tài khoản mới . Thường ta phải vô mail để xác nhận việc đăng ký , với những chức năng như vậy để test được ta phải sử dụng Gmail API.

Test case cho chức năng đăng ký thành viên mới có sử dụng Gmail API

frontend\tests\RegistrationCest.php

<?php

namespace frontend\tests\acceptance;

use Codeception\Example;
use frontend\tests\AcceptanceTester;
use frontend\tests\helpers\GmailAPIHelper;

class RegistrationCest extends BaseRegistrationCest
{
const WAIT_TIME_GMO_MAIL_SEND = 3;
const REGEX_DETECT_REGISTRATION_URL = '#(https://w+)#';

/**
 * @dataprovider _provider
 */
public function checkRegistration(AcceptanceTester $I, Example $example)
{
    $gmail = $example['gmail'];
    $member = $example['member'];

    $I-&gt;wantTo('新規登録する');
    $I-&gt;amOnPage('/');
    $I-&gt;click('新規登録');

    $I-&gt;fillField('email', $member['email']);
    $I-&gt;click('次へ');
    // メールが受信されるまで待つ
    $I-&gt;wait(self::WAIT_TIME_GMO_MAIL_SEND);

    $access_token = GmailAPIHelper::getAccessToken($gmail['client_id'], $gmail['client_secret'], $gmail['refresh_token']);
    $message_id = GmailAPIHelper::getLatestMessageId($access_token);
    $content = GmailAPIHelper::getMessageContent($message_id, $access_token);
    // メールの本文から登録URLを抽出する
    $registration_url = self::detectRegistrationUrl($content);

    $I-&gt;amOnPage($registration_url);
    $I-&gt;see('新規登録');
    $I-&gt;fillField('password', $member['password']);
    $I-&gt;fillField('password_confirm', $member['password']);
    $I-&gt;click('同意して登録する');
}

private static function detectRegistrationUrl($content)
{
    preg_match(self::REGEX_DETECT_REGISTRATION_URL, $content, $m);

    return $m[1];
}

}

Để có thể sử dụng được Gmail API ta phải lấy đươc access token , từ cái mail ID mới nhất ta lấy được nội dung mail. Tham khảo ở link phía trên để hiểu rõ hơn về Gmail API.

frontend\tests\helpers\GmailAPIHelper.php

<?php

namespace frontend\tests\helpers;

use yii\httpclient\Client;

class GmailAPIHelper
{
const HTTP_HEADER_OAUTH2_AUTH_ACCESS_TOKEN = 'Authorization';

const GOOGLE_API_URL_OAUTH2 = 'https://accounts.google.com/o/oauth2';
const GOOGLE_API_URL_OAUTH2_ISSUE_TOKEN = self::GOOGLE_API_URL_OAUTH2 . '/token';

const GOOGLE_API_SCOPE_GMAIL_READONLY = 'https://www.googleapis.com/auth/gmail.readonly';

const GOOGLE_API_OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE = 'authorization_code';
const GOOGLE_API_OAUTH2_GRANT_TYPE_REFRESH_TOKEN = 'refresh_token';

const GMAIL_API_MESSAGES_URL = 'https://www.googleapis.com/gmail/v1/users/%s/messages/';
const GMAIL_API_MESSAGE_URL = 'https://www.googleapis.com/gmail/v1/users/%s/messages/%s';
const GMAIL_API_REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';

const GMAIL_TARGET_USER = 'me';
const GMAIL_MESSAGES_MAX_RESULTS = 1;

public static function getAccessToken($client_id, $client_secret, $refresh_token)
{
    $url = self::GOOGLE_API_URL_OAUTH2_ISSUE_TOKEN;
    $grant_type = self::GOOGLE_API_OAUTH2_GRANT_TYPE_REFRESH_TOKEN;
    $params = compact('client_id', 'client_secret', 'grant_type', 'refresh_token');
    $content = json_decode((new Client())-&gt;post($url, $params)-&gt;send()-&gt;content, true);

    return $content['access_token'];
}

public static function getLatestMessageId($access_token)
{
    $url = sprintf(self::GMAIL_API_MESSAGES_URL, self::GMAIL_TARGET_USER);
    $maxResults = self::GMAIL_MESSAGES_MAX_RESULTS;
    $params = compact('maxResults');
    $headers = [self::HTTP_HEADER_OAUTH2_AUTH_ACCESS_TOKEN =&gt; "Bearer {$access_token}"];

    $content = json_decode((new Client())-&gt;get($url, $params, $headers)-&gt;send()-&gt;content, true);

    return $content['messages'][0]['id'];
}

public static function getMessageContent($message_id, $access_token)
{
    $url = sprintf(self::GMAIL_API_MESSAGE_URL, self::GMAIL_TARGET_USER, $message_id);
    $params = [];
    $headers = [self::HTTP_HEADER_OAUTH2_AUTH_ACCESS_TOKEN =&gt; "Bearer {$access_token}"];

    $content = json_decode((new Client())-&gt;get($url, $params, $headers)-&gt;send()-&gt;content, true);

    return base64_decode($content['payload']['body']['data']);
}

}

Test chức năng đăng nhập thông qua tài khoản Google account.

frontend\tests\acceptance\social\google\LoginCest.php

<?php

namespace frontend\tests\acceptance\social\google;

use Codeception\Example;
use frontend\tests\AcceptanceTester;
use frontend\tests\helpers\CleanUpHelper;
use Symfony\Component\Yaml\Yaml;
/**

  • Googleログイン用のベースクラス
    */
    abstract class LoginCest
    {
    const FIELD_NAME_GOOGLE_LOGIN_FORM= 'identifier';
    const FIELD_NAME_GOOGLE_PASSWORD_FORM = 'password';
    const FIELD_ID_GOOGLE_LOGIN_NEXT_BUTTON = 'identifierNext';
    const FIELD_ID_GOOGLE_PASSWORD_NEXT_BUTTON = 'passwordNext';

    const WAIT_TIME_EMULATE_HUMAN_OPERATION = 1;

    public function checkLogin(AcceptanceTester $I, Example $example)
    {
    $I->wantTo('Googleアカウントを使ってログインする');
    $I->amOnUrl("https://{$example['hosts']['gmo_id']}");
    $I->amOnPage('/');
    $I->click('ログイン');
    $I->click('Googleでログイン');
    $I->wait(self::WAIT_TIME_EMULATE_HUMAN_OPERATION);

     $I-&gt;fillField(self::FIELD_NAME_GOOGLE_LOGIN_FORM, $example['member']['login']);
     $I-&gt;wait(self::WAIT_TIME_EMULATE_HUMAN_OPERATION);
    
     $I-&gt;click('#'.self::FIELD_ID_GOOGLE_LOGIN_NEXT_BUTTON);
     $I-&gt;wait(5);
    
     $I-&gt;waitForElement('#passwordNext', 5);
     $I-&gt;fillField(self::FIELD_NAME_GOOGLE_PASSWORD_FORM, $example['member']['g-password']);
     $I-&gt;wait(self::WAIT_TIME_EMULATE_HUMAN_OPERATION);
    
     $I-&gt;click('#'.self::FIELD_ID_GOOGLE_PASSWORD_NEXT_BUTTON);
     $I-&gt;wait(5);
    
     $I-&gt;see('現在のポイント');
    

    }

    public function _provider()
    {
    $common_data = Yaml::parse(file_get_contents(getcwd() . "/frontend/tests/acceptance/data/common.yml"));
    $google_common_data = Yaml::parse(file_get_contents(getcwd() . "/frontend/tests/acceptance/data/social/google/common.yml"));
    $env = explode('-', getenv('env'))[0];

     return [array_merge($common_data[$env], $google_common_data[$env])];
    

    }
    }

Kết  luận

Việc cài đặt thêm module PhantomJS giúp ta viết test được dễ dàng hơn rất nhiều với những trang có Javascript , tuy vẫn có những trường hợp không viết test được , nhưng phần lớn nó đã giúp ta tiết kiệm được khá nhiều thời gian trong việc test các ứng dụng.