Laravel & Masonite TDD徹底比較(前編)

さて、3月もやってきました。毎月1本Masoniteの記事です。
今回のテーマは、LaravelとMasoniteで全く同じ処理をTDDで作って比較してみよう！です。作るものについては簡単に、ログイン画面だけ作ってみようと思います。
ほとんど同じアーキテクチャを持つフレームワークを、ただ言語がPHPとPythonで違うだけで、どれだけ違いがあるのか、またどれほど同じなのか。じっくり見ていきたいと思います。

Masoniteとはなんぞや？とお思いの方は、まずはこちらの記事を読んでいただければ幸いです。
[LaravelそっくりなPython製WebフレームワークMasoniteの紹介](https://qiita.com/dumblepy/items/65b975302618cb6a8f64)

また、TDD(テスト駆動開発)についてご存じない方は、こちらを見ていただければいいと思います。
[テスト駆動開発](https://ja.wikipedia.org/wiki/テスト駆動開発)
[50 分でわかるテスト駆動開発](https://channel9.msdn.com/Events/de-code/2017/DO03)

ではさっそく、それぞれの言語のインストールから。

## インストール
### anyenvのインストール
複数の言語を1台のPCにインストールするので、anyenvを入れてからphpenv、pyenvを使ってそれぞれの言語をインストールしてみようと思います。

```bash
git clone https://github.com/riywo/anyenv ~/.anyenv  
echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bash_profile  
echo 'eval "$(anyenv init -)"' >> ~/.bash_profile  
anyenv install --init
exec $SHELL -l
```

### PHP、Pythonのインストール
phpenv、pyenvをインストールします

```bash
anyenv install phpenv pyenv
```

そしてphpenv、pyenvでそれぞれ最新版を検索し、インストールします

```bash
mkdir php
cd php
phpenv install -l #インストールできるバージョン一覧を表示
phpenv install 7.3.2
phpenv local 7.3.2
php --version

PHP 7.3.2 (cli) (built: Mar  3 2019 02:51:55) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.2, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.2, Copyright (c) 1999-2018, by Zend Technologies
    with Xdebug v2.7.0rc1, Copyright (c) 2002-2019, by Derick Rethans
```
```bash
mkdir python
cd python
pyenv install -l #インストールできるバージョン一覧を表示
pyenv install 3.7.2
pyenv local 3.7.2
python -V

Python 3.7.2
```

現在のディレクトリ構造はこのようになっています

```
.
├── php
│   └── .php-version
└── python
    └── .python-version
```

## プロジェクト作成
### Laravel
まず、phpのパッケージ管理ソフトであるcomposerをインストールします。

```
apt install -y composer
composer #確認
```
↓

```
Download composer.phar ...
All settings correct for using Composer
Downloading...

Composer (version 1.8.4) successfully installed to: /tmp/composer.phar
Use it: php /tmp/composer.phar

Move composer.phar to /home/user/.anyenv/envs/phpenv/versions/7.3.2/composer
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.8.4 2019-02-11 10:52:10
```
phpディレクトリ内でComposerを使用しLaravelインストーラをダウンロードして、laravelコマンドを使ってprojectという名前のプロジェクトを作ります。

```
composer global require laravel/installer
laravel new project
```

### Masonite
pythonディレクトリ内でvenvを作り、pipを使用しmasonite-cliをダウンロードして、craftコマンドを使ってprojectという名前のプロジェクトを作ります。

```bash
python -m venv .venv #venvを作る
source .venv/bin/activate #venvを有効化
pip install masonite-cli #Masoniteをインストール
craft new project #projectという名前でMasoniteプロジェクト作成
cd project/
craft install #Masoniteの依存パッケージ類をインストール
```

LaravelとMasoniteがインストールできたら、それぞれ組み込みサーバーを立ち上げて、ブラウザからアクセスしてみましょう。Laravelは8001番ポート、Masoniteは8002番ポートを使うことにします。
それぞれphp/projectディレクトリ、python/projectディレクトリでコマンドを実行します。


```php/project.
php artisan serve --port 8001
```
```python/project.
craft serve -p 8002
```

![Screenshot from 2019-03-03 17-12-51.jpg](https://qiita-image-store.s3.amazonaws.com/0/295826/f936e038-9459-2fd3-3b35-9007889cc600.jpeg)

## さぁ、TDDを始めるぞ
### ログイン画面を作る
/loginにアクセスした時に表示される、ログイン画面を作りましょう。

#### テスト
まずは、ユニットテストを作ります。Laravelではコマンドでテストが作れるのですが、Masoniteではまだテストを作るコマンドは用意されていません。

##### Laravel

```php/project.
php artisan make:test LoginTest --unit
```
php/project/tests/Unit/LoginTest.phpが自動生成されました。phpunitコマンドを使ってテストを実行してみましょう。

```php/project.
vendor/bin/phpunit tests/Unit/LoginTest.php
```
現在は`$this->assertTrue(true);`しかテストに書かれていないので、成功すると思います。`/loginにアクセスした時にアクセスできる`というテストを書いてみましょう。

```php/project/tests/Unit/LoginTest.php
class LoginTest extends TestCase
{
    /**
     * A basic unit test example.
     *
     * @return void
     */
    public function test_loginにアクセスした時にアクセスできる()
    {
        $response = $this->get('/login')->assertStatus(200);
    }
}
```

ここで再度テストを実行すると、まだルーティングを作っていないので、期待通り失敗します。

##### Masonite
ではMasoniteでもテストを作っていきます。

```python/project/tests/unit/test_login.py
from masonite.testing.UnitTest import UnitTest

class TestLogin(UnitTest):
    def test_example(self):
        assert True
```
テストを動かしてみましょう。

```python/project.
python -m pytest tests/unit/test_login.py
```
現在は`assert True`しかテストに書かれていないので、成功すると思います。`/loginにアクセスした時にアクセスできる`というテストを書いてみましょう。

```python/project/tests/unit/test_login.py
from masonite.testing.UnitTest import UnitTest

class TestLogin(UnitTest):
    def test_loginにアクセスした時にアクセスできる(self):
        assert self.route('/login')
```
ここで再度テストを実行すると、まだルーティングを作っていないので、期待通り失敗します。

では、/loginにアクセスすると、LoginControllerのindexメソッドが呼ばれて、loginビューが表示される、という処理を作っていきます。

#### ビューを作る
##### Laravel
まず、ビューを作ります。LaravelのテンプレートエンジンであるBladeには、テンプレートの継承と言って、ビューの中にビューを埋め込むという処理ができます。これによって、jsやcssのインポート処理、ヘッダーやフッターの定義などは一箇所で済ませることができます。
Laravelにはビューを作るコマンドは無いので、手動でファイルを作ります。

```php/project/resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>@yield('title')</title>
    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">
  </head>
  <body>
    <div id="content">
      @yield('content')
    </div>
  </body>
</html>
```
```php/project/resources/views/login.blade.php
@extends('layouts.app')

@section('title', 'ログイン')

@section('content')
  <p>Laravelログイン画面</p>
@endsection
```

##### Masonite
LaravelのBladeと同じく、Masoniteが採用しているテンプレートエンジンであるJinja2も、継承を使ったテンプレートの埋め込みが使えます。
Laravelの時と同じくlayouts/appとloginを作ります。Masoniteはコマンドでビューが作れます。

```python/project.
craft view layouts/app
craft view login
```
python/project/resources/templates/layouts/app.html
python/project/resources/templates/login.html
が自動生成されました。ここにHTMLを書いていきます。

```python/project/resources/templates/layouts/app.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}{% endblock %}</title>
    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">
  </head>
  <body>
    <div id="content">
      {% block content %}{% endblock %}
    </div>
  </body>
</html>
```
```python/project/resources/templates/login.html
{% extends "layouts/app.html" %}

{% block title %}ログイン{% endblock %}

{% block content %}
  <p>Masoniteログイン画面</p>
{% endblock %}
```

#### コントローラーを作る

##### Laravel
コマンドでコントローラーを作ります

```php/project.
php artisan make:controller LoginController
```
indexメソッドを作り、login.blade.phpを返す処理を書きます。

```php/project/app/Http/Controllers/LoginController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class LoginController extends Controller
{
    public function index(){
        return view('login');
    }
}
```

##### Masonite
コマンドでコントローラーを作ります

```python/project.
craft controller Login
```
indexメソッドを作り、login.htmlを返す処理を書きます。

```python/project/app/http/controllers/LoginController.py
""" A LoginController Module """

class LoginController:
    """LoginController
    """

    def index(self):
        return view('login.html')
```

#### ルーティングを張る
##### Laravel
php/project/routes/web.phpに、/loginにgetでアクセスされたらLoginコントローラーのindexメソッドを呼び出す処理を書きます。

```php/project/routes/web.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::get('/login', 'LoginController@index'); //←追加
```

##### Masonite
python/project/routes/web.pyに、/loginにgetでアクセスされたらLoginコントローラーのindexメソッドを呼び出す処理を書きます。

```python/project/routes/web.py
"""Web Routes."""

from masonite.routes import Get, Post
from masonite.helpers.routes import get #←追加

ROUTES = [
    Get().route('/', 'WelcomeController@show').name('welcome'),
    get('/login', 'LoginController@index') #←追加
]
```

#### テスト
ではここで再度テストを実行してみましょう。

```php/project.
vendor/bin/phpunit tests/Unit/LoginTest.php
```
```python/project.
python -m pytest tests/unit/test_login.py
```
ルーティング、コントローラー、ビューの一連の処理を書いたために、テストが成功すると思います。

```php/project.
PHPUnit 7.5.6 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 199 ms, Memory: 16.00 MB

OK (1 test, 1 assertion)
```
```python/project.
================================ test session starts ================================
platform linux -- Python 3.7.2, pytest-3.10.1, py-1.8.0, pluggy-0.9.0
rootdir: /to/your/dir/python/project, inifile:
collected 1 item                                                                    

tests/unit/test_login.py .                                                    [100%]
======================= 1 passed, 0 warnings in 0.37 seconds ========================
```

また、組み込みサーバーを起動して/loginにアクセスすると、正しくアクセスできるようになっているでしょう。

```php/project.
php artisan serve --port 8001
```
```python/project.
craft serve -r -p 8002
```
![Screenshot from 2019-03-03 21-52-12.jpg](https://qiita-image-store.s3.amazonaws.com/0/295826/cab17dd2-fde6-be8f-6184-0ee466c691a8.jpeg)

## まとめ
今回はLaravelとMasoniteについて、インストール、テスト、ビュー、テンプレートエンジン、コントローラー、ルーティングについて比較しました。テストにおける若干の違いは否めませんが、大きな違いはまだ無いように思えます。
後編ではマイグレーション、モデル、ORM、バリデーションについてもTDDをベースに比較してみたいと思います。好ご期待！