Test tự động các chức năng liên quan tới Mail và các API bên ngoài bằng Codeception
Ở 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->wantTo('新規登録する');
$I->amOnPage('/');
$I->click('新規登録');
$I->fillField('email', $member['email']);
$I->click('次へ');
// メールが受信されるまで待つ
$I->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->amOnPage($registration_url);
$I->see('新規登録');
$I->fillField('password', $member['password']);
$I->fillField('password_confirm', $member['password']);
$I->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())->post($url, $params)->send()->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 => "Bearer {$access_token}"];
$content = json_decode((new Client())->get($url, $params, $headers)->send()->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 => "Bearer {$access_token}"];
$content = json_decode((new Client())->get($url, $params, $headers)->send()->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->fillField(self::FIELD_NAME_GOOGLE_LOGIN_FORM, $example['member']['login']);
$I->wait(self::WAIT_TIME_EMULATE_HUMAN_OPERATION);
$I->click('#'.self::FIELD_ID_GOOGLE_LOGIN_NEXT_BUTTON);
$I->wait(5);
$I->waitForElement('#passwordNext', 5);
$I->fillField(self::FIELD_NAME_GOOGLE_PASSWORD_FORM, $example['member']['g-password']);
$I->wait(self::WAIT_TIME_EMULATE_HUMAN_OPERATION);
$I->click('#'.self::FIELD_ID_GOOGLE_PASSWORD_NEXT_BUTTON);
$I->wait(5);
$I->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.