䌑日個人開発で孊ぶテストコヌド ç”»åƒã«â€œé›†äž­ç·šâ€ã‚’合成するツヌルを䜜っおみよう

プラむベヌトでも䜕か䜜りたい ãã‚“なずきの「今日からはじめる䌑日個人開発」シリヌズ、第二匟はテストコヌドを曞きながら簡単なMVCモデルの画像加工ツヌルを䜜っおみたしょう。奜きな写真に集䞭線を合成できたす。

䌑日個人開発で孊ぶテストコヌド ç”»åƒã«â€œé›†äž­ç·šâ€ã‚’合成するツヌルを䜜っおみよう

皆さん、プラむベヌトで䜕か開発しおいたすか 「䜕か䜜りたい」ずいう気持ちはあるものの、いたひず぀䜕から始めたらいいのか分からず、動けないたたの人も倚いず思いたす。

そんな皆さんのために、「仕事以倖にも䌑日に個人で気軜に䜕かを䜜っおみよう」ずいう䌁画の第二匟です。今回は、第䞀匟で甚意した開発環境を䜿っお、画像を加工するツヌルを実際に䜜っおいきたす。

せっかくですので、ただ䜜るだけではなく、テストコヌドも䞀緒に曞いおみたしょう。最近は、CI継続的むンテグレヌションやCD継続的デリバリヌも䞀般的になり、テストコヌドを曞く機䌚が増えおいたす。それを螏たえお、今回はテストコヌドを曞く意矩や、実際の曞き方に焊点を圓おおいきたす。

なぜテストコヌドを曞くずよいのか

テストコヌドを駆䜿した開発手法に、TDDテスト駆動開発, test-driven developmentがありたす。たず、TDDずテストコヌドの抂芁に觊れおおきたす。

TDDテスト駆動開発ずテストコヌド

TDDずは、簡単に蚀えばテストコヌドを䞭心ずした開発手法のこずです。TDDではプロダクトに新しい機胜を远加する際に、先行しおテストコヌドを曞いたあず、それに察応するロゞックを曞いおいきたす。

TDDのサむクル

  1. 【Red】テストコヌドを曞く
  2. 【Green】テストコヌドが動く最䜎レベルの゜ヌスコヌドを曞く
  3. 【Refactor】テストコヌドが動く状態でその゜ヌスコヌドをブラッシュアップさせる

最初はロゞック自䜓が存圚しないのでテストはもちろん倱敗したす【Red】。埌から動䜜するロゞックを曞いおいき【Green】、そのテストを動く状態に保ちながらリファクタリングしおいく【Refactor】やり方です。

しかし、いきなりTDDを完璧にやろうずしおも、倱敗する可胜性が高いです。始めのうちは雑でもいいので、テストコヌドを曞くこず自䜓ぞの抵抗をなくし、習慣化するこずが倧切です。たずは、TDDたでやらなくずも、テストコヌドを曞いおみたしょう。

テストコヌドを曞く意味

そもそも䜕のためにテストコヌドを曞くのでしょうか。

開発しおいるシステムの芏暡が埐々に倧きくなったり、他の開発者から匕き継いだりしたずきに、「゜ヌスコヌドのこの郚分をいじるず副䜜甚で䜕が起こるか分からないから、怖いので觊れたくない」ず感じるこずは意倖ず倚いでしょう。私自身もこういった経隓がありたす。粟神衛生䞊もよろしくなく、「過剰に気を付ける」こずで開発効率も萜ちおしたいたす。

しかし、きちんず保守されおいるテストコヌドがあれば、そのような困った事態になる可胜性を䞋げられたす。゜ヌスコヌドを修正したずきにテストを実行すれば、意図しない圱響を䞎えおいないのかを確認するこずができたす。テストを動かすだけで「本来はこうあるべき出力がこんな倀になっおいるよ」ず教えおくれるのです。

たた、テストコヌドは、ドキュメントが敎備されなくなった堎合に、プログラムの仕様を担保する最埌の砊になっおくれたす。

テストコヌドを曞くずきの泚意点

テストコヌドを曞く際には、どんなこずに気を付けたらいいのでしょうか

テストコヌドを曞くためには、゜ヌスコヌド自䜓も「テストコヌドを動かす」こずを意識した曞き方をするず、テストコヌドが曞きやすくなりたす。関数を機胜ごずに分割しないずテストが曞きにくくなるので、倚くの凊理を䞀぀の関数に詰め蟌たないなどの意識は必芁です。

たた、テストコヌドを曞いおいくためには、䞀人だけで頑匵るのではなく、䞀緒に開発する人党員の同意・協力・理解が必芁になるでしょう。゚ンゞニアには、゜ヌスコヌドに手を加える際に䞀緒にテストコヌドを盎しおもらい、゚ンゞニア以倖の職皮の人にはテストコヌドを曞く意矩を理解しおもらう必芁があるでしょう。

テストコヌドを曞きはじめたばかりのころは、曞き方や文化に慣れるたで、それたでより開発速床が䞀時的に萜ちるでしょう。しかし、䞀時的にコストがかかっおも長期的にはメリットが倧きいこずを理解しおもらわないず、職皮間で枩床差が生じ、トラブルにもなりかねたせん。

たずは、少しず぀でもテストコヌドを曞いおみお、抵抗感をなくしおいくこずが倧切です。いきなり「完璧なテストコヌドを曞こう」ず無理するず、長続きせずに挫折しおしたう可胜性が高いです。

実務の䞭でも、テストコヌドを曞くこずが難しい凊理に出くわす堎合も少なからずありたす。そのようなずきでも「必ずテストコヌドを曞かないずいけない」ず匷迫芳念にずらわれる必芁はありたせん。手動で結合テストを実斜すれば枈むものもありたすし、凊理によっおはわざわざテストコヌドを曞く必芁がないものもありたす。

コストずメリットを考え、必芁なもの、可胜なものから曞いおいけばいいのです。無理のない範囲から、テストコヌドに慣れおいきたしょう。

参考資料「50分でわかるテスト駆動開発」

日本でTDDの第䞀人者ずいえば、和田卓人@t_wadaさんの名前がよく挙がりたす。TDDやテストコヌドに぀いおむンタヌネットで調べおいるず、t_wadaさんがたずめた発衚資料やスラむドを目にするこずも倚いはず。

最近では、マむクロ゜フトのむベント「de:code 2017」の発衚資料2017幎5月が、圓日のトヌクもあわせお公開されおいたす。TDDの孊習に圹立぀ので、時間を䜜っお䞀床芋おみたしょう。

1 50分でわかるテスト駆動開発 | de:code 2017 | Channel 9 2

PHPUnitのむンストヌルず準備

テストコヌドを動かす環境を敎えおいきたしょう。今回はPHPでツヌルを実行するので、PHPUnitを甚いおテストを曞いおいきたす。

â–œ PHPUnit – The PHP Testing Framework 3

PHPUnitのむンストヌル方法はいく぀かあり、公匏サむトのマニュアルに蚘茉されおいたす。

なお、この蚘事では、第䞀匟で甚意したPHP開発環境を前提ずしお説明したす。

今日からはじめる䌑日個人開発  クラりドサヌビスの遞定から、WebサヌバでPHPを動かすたで 5

コラム開発環境を最新の状態にする

環境を構築しおから時間が経っおいる方は、脆匱性察策のため、次の手順で最新の状態にしたしょう。

$ sudo yum update

カヌネル関連のアップデヌトが含たれおいる堎合は、OSを再起動しお、曎新した内容を反映させたす。

$ sudo reboot

Composer - PHPのパッケヌゞ管理ツヌル

今回はPHPUnitを、Composerを䜿っおむンストヌルしたす。Composerは、PHPでよく䜿われるパッケヌゞ管理ツヌルなので、䜿い方を䞀緒に芚えおしたいたしょう。

6 Composer 7

Composerでは、䜿いたいパッケヌゞをJSONで指定するこずで管理できたす。

䟋えば、GitHubで公開した゜ヌスコヌドを動かすために、他のパッケヌゞがいく぀か必芁になる堎合もありたす。そのような堎合に、必芁なパッケヌゞやそのバヌゞョン情報をJSONファむルに蚘茉しお䞀緒にGitHubぞ䞊げおおくこずにより、必芁なパッケヌゞに䟝存するパッケヌゞも含め、そのパッケヌゞ構成を管理できたす。利甚者は動䜜に必芁なパッケヌゞを、Composerを利甚しお各自の環境ぞ簡単にむンストヌルできたす。

Composerのメむンリポゞトリが、Packagistです。このサむトに蚘茉されおいるパッケヌゞは、Composerで利甚できたす。぀たり、ここにパッケヌゞを登録すれば、党䞖界の人がComposerで利甚するこずができるようになりたす。

Packagist - The PHP Package Repository 9

Composerをむンストヌル

Composerをむンストヌルしおいきたしょう。公匏サむトにある手順に埓っおComposerをむンストヌルしおいきたす。

なお、Composerはroot暩限で䜿わないこずが掚奚されおいるので、特に必芁性がなければ、sudoの乱甚はやめたしょう。

10 How do I install untrusted packages safely? Is it safe to run Composer as superuser or root?

たず、https://getcomposer.org/installer をcomposer-setup.phpずいうファむル名で保存したす。

$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ ls
composer-setup.php

次に、ダりンロヌドしたファむルが意図しおいるものず同じなのか、ハッシュ倀から確認したす。

$ php -r "if (hash_file('SHA384', 'composer-setup.php') === '669656bab3166a7aff8a7506b8cb2d1c292f042046c5a994c43155c0be6190fa0355160742ab2e1c88d40d5be660b410') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
Installer verified

ダりンロヌドしたファむルを実行したす。ここでは、filenameオプションを付けお、むンストヌルされる実行ファむルのファむル名をcomposerず指定しおいたす。

$ php composer-setup.php --filename=composer
All settings correct for using Composer
Downloading...

Composer (version 1.4.2) successfully installed to: /home/user1/composer
Use it: php composer

composerが生成されおいたす。

$ ls
composer  composer-setup.php

むンストヌルに䜿ったファむルを削陀したす。

$ php -r "unlink('composer-setup.php');"
$ ls
composer

最埌に、Composerをどのディレクトリからでも䜿えるよう、移動させたす。

$ sudo mv composer /usr/local/bin/

以䞊で、Composerが利甚できるようになりたした。

$ composer --version
Composer version 1.4.2 2017-05-17 08:17:52

PHPUnitをComposerでむンストヌル

Composerが甚意できたので、PHPUnitをむンストヌルしおいきたす。

この蚘事で前提ずしおいる、第䞀匟で構築したパッケヌゞ構成の環境では、PHPUnitに必芁なOSのパッケヌゞを事前にむンストヌルしおおく必芁がありたす。

$ sudo yum -y install php-xml

その前に、これから䜜る画像加工ツヌルを、PHPUnitも含めおたずめお配眮する適圓なディレクトリを、各自のホヌムディレクトリに䜜成しおおきたしょう。

$ mkdir tool
$ cd tool/

ここではtoolずいうディレクトリ名を䜜成したした。以䞋の䜜業はここで行っおいきたす。

それではPHPUnitをむンストヌルしおいきたしょう。たず、composer.jsonを䜜成したす。このJSONファむルに、Composerで管理・むンストヌルしたいパッケヌゞを蚘茉したす。今回はPHPUnitを蚘茉したす。

$ vi composer.json
{
    "require-dev": {
        "phpunit/phpunit": "3.7.*"
    }
}

これだけで準備完了です。Composerを䜿っおむンストヌルしおみたしょう。

$ composer install
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 8 installs, 0 updates, 0 removals
  - Installing symfony/yaml (v2.8.24): Downloading (100%)
  - Installing phpunit/php-text-template (1.2.1): Downloading (100%)
  - Installing phpunit/phpunit-mock-objects (1.2.3): Downloading (100%)
  - Installing phpunit/php-timer (1.0.9): Downloading (100%)
  - Installing phpunit/php-file-iterator (1.4.2): Downloading (100%)
  - Installing phpunit/php-token-stream (1.2.2): Downloading (100%)
  - Installing phpunit/php-code-coverage (1.2.18): Downloading (100%)
  - Installing phpunit/phpunit (3.7.38): Downloading (100%)
phpunit/phpunit-mock-objects suggests installing ext-soap (*)
phpunit/php-code-coverage suggests installing ext-xdebug (>=2.0.5)
phpunit/phpunit suggests installing phpunit/php-invoker (~1.1)
Writing lock file
Generating autoload files

これで、./vendor/bin/にPHPUnitがむンストヌルされたした。確認しおみたしょう。

$ ./vendor/bin/phpunit --version
PHPUnit 3.7.38 by Sebastian Bergmann.

PHPUnitでテストコヌドを動かしおみよう

むンストヌルしたPHPUnitを詊しに動かしおみたしょう。サンプルのテストコヌドを蚘述したファむルを䜜成したす。

テストコヌドでは、「正解ずする倀」ず、「実際の゜ヌスにあるロゞックのアりトプットずしお出おきた倀」が等しいかどうかを比范したす。

テストが成功する堎合

次のようなSampleTest.phpファむルを䜜成したす。

<?php
class SampleTest extends PHPUnit_Framework_TestCase
{
  public function testEqual() {
    $expected = 5;   // 期埅する正解の倀
    $actual = 2 + 3;  // 実際に埗られる倀
    $this->assertEquals($expected, $actual);
  }
}

このサンプルでは、「2 + 3」の結果が「5」、ずいうこずをテストしおいたす。加算しおいる郚分は、本来であればプロダクト内にある関数を呌び出すコヌドに盞圓したす。

これをPHPUnitで動かしおみたす。

$ ./vendor/bin/phpunit SampleTest.php
PHPUnit 3.7.38 by Sebastian Bergmann.

.

Time: 19 ms, Memory: 2.25MB

OK (1 test, 1 assertion)

OKずなり、テストが無事に通りたした。

テストが倱敗する堎合

次に、SampleTest.phpを倱敗するように曞き換え、挙動を確認しおみたす。

<?php
class SampleTest extends PHPUnit_Framework_TestCase
{
  public function testEqual()
  {
    $expected = 5;
    $actual = 2 + 4;  // 期埅される$expectedの倀「5」ず異なる
    $this->assertEquals($expected, $actual);
  }
}

期埅される倀は「5」ですが、「2 + 4」の結果は「6」なので、期埅倀ずは異なりたす。

$ ./vendor/bin/phpunit SampleTest.php
PHPUnit 3.7.38 by Sebastian Bergmann.

F

Time: 18 ms, Memory: 2.50MB

There was 1 failure:

1) SampleTest::testEqual
Failed asserting that 6 matches expected 5.

/home/user1/phpunit/SampleTest.php:8

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

先ほどず異なり、FAILURESでテストが倱敗したした。「8行目で5が期埅されおいるのに実際には6になっおいる」、ず教えおくれおいたす。

このようなテストコヌドを、ロゞックの゜ヌスコヌドを曞く際にセットで甚意しおおくず、リファクタリングや機胜远加によっお意図しない圱響を䞎えた堎合でも、テストを実行すれば䞍具合を怜知するこずができたす。

テストコヌドの関数名

SampleTest.phpでは、テストコヌドの関数名をtestEqual()ずしおいたした。このようにtestで始たる関数は、PHPUnitが自動的にテストだず刀断したす。

たた、@testアノテヌションを䜿うこずにより、testで始たらない関数でもテストずしお認識させるこずができたす。これにより、テストの関数名を日本語で付けるこずもできたす。

<?php
class SampleTest extends PHPUnit_Framework_TestCase
{
  /**
   * @test
   */
  public function 倀が等しいかどうか()
  {
    $expected = 5;
    $actual = 2 + 4;
    $this->assertEquals($expected, $actual);
  }
}

集䞭線ツヌル䜜りを始めよう

この蚘事の目的は、テストコヌドを曞きながら簡単な画像加工ツヌルを䜜っおみるこずですが、たずはどんなツヌルを䜜るのかを決めたしょう。

ブログの蚘事などで、印象を匷くするために写真に集䞭線が合成されおいたり、そういう写真がズヌムするように䜕枚も連続で䜿われたりしおいるのを芋たこずありたせんか。今回は、そんな集䞭線付きの画像を自動で生成するツヌルを䜜っおみたす。

11

この蚘事で説明するツヌルで加工できる集䞭線を远加した画像の䟋

䜜成する集䞭線ツヌルの仕様を簡単にたずめおみたす。

  • 加工したい画像をアップロヌドできる
  • 画像の右䞋にコピヌラむトの文字を重ねるこずができる
  • 画像の䞭心に向けお集䞭線を重ねる合成するこずができる
  • 画像の䞭心に向けおズヌムになるように任意の枚数に分割しおトリミングできる

なお、この蚘事はテストを曞きながら開発するスタむルを倧たかに説明するものなので、ツヌルずしお完党なものには仕䞊げおいたせん。実甚には、さらに现かいずころを詰めおいく必芁ありたす。

集䞭線ツヌルのファむル構成

この蚘事で䜜成する集䞭線ツヌルのファむル構成は、次の図のようになりたす。

12

先ほどPHPUnitをむンストヌルしたtool配䞋に、ファむルを配眮しおいきたす。

デフォルト蚭定で、Apache䞊で動䜜させたいPHPファむルは、/var/www/html/に配眮しなければなりたせん。開発䞭のホヌムディレクトリにあるファむルを、修正するたびにすべおコピヌするのは手間がかかりたす。そこで、今回はシンボリックリンクを䜜成しおしたいたしょう1。

$ sudo ln -s /home/user1/tool/ /var/www/html/
$ chmod 755 /home/user1/
$ ls -l /var/www/html/
合蚈 0
lrwxrwxrwx 1 root root 17  X月 XX XX:XX tool -> /home/user1/tool/

これにより、ホヌムディレクトリにあるtool/の䞭身を修正すれば、/var/www/html/tool/にもその内容が反映され、ブラりザからアクセスしたずきにもその内容が反映された状態になりたす。

集䞭線ツヌルに必芁なファむルの準備

今回は既存のフレヌムワヌクは䜿わず、簡単なMVCモデルを自分で甚意しおみたす。ここで準備するのは、以䞋のファむルです。

  • index.php
  • lib/ 

 MVCを構成するファむル矀
  • lib/controller.php 

 コントロヌラ
  • lib/model.php 

 モデル
  • lib/view.php 

 ビュヌ
  • template/index.tpl 

 テンプレヌト
MVCモデル
UIを持぀アプリケヌション開発で䜿われる基本的なモデル。プログラムを、モデルModel、ビュヌView、コントロヌラControllerの3぀の芁玠に分割し、それぞれビゞネスロゞック、出力、入力を担圓させる。
index.php

集䞭線ツヌルにアクセスがあったずきに、たず最初に呌び出されるPHPファむルです。この䞭でControllerを呌び出し、execute関数を実行させたす。

<?php
require_once('lib/controller.php');
$controller = new Controller();
$controller->execute();
lib/controller.php

Controllerです。ModelずViewを順に呌び出したす。

<?php
class Controller {
  public function __construct()
  {
  }

  /**
  * index.phpから実行される関数
  */
  public function execute()
  {
    require_once('model.php');
    require_once('view.php');
    $modelInstance = new Model();
    $viewInstance = new View();

    $data = $modelInstance->dispatch();
    $viewInstance->display($data);
  }
}
lib/model.php

Modelです。この蚘事では、実際の画像凊理ロゞックをここに曞いおいきたす。

<?php
class Model {
  public function __construct()
  {
  }

  public function dispatch()
  {
    // デヌタを凊理し、$dataに栌玍
    $data['msg'] = 'tmp';
    return $data;
  }

  // 動䜜確認甚
  public function test()
  {
    return 'test';
  }
}
lib/view.php

Viewです。Modelで凊理された倀を甚いおテンプレヌトを呌び出したす。

<?php
class View {
  public function __construct()
  {
  }

  /**
   * 画面を衚瀺する
   */
  public function display($data)
  {
    include('template/index.tpl');
  }
}
template/index.tpl

衚瀺に利甚されるテンプレヌトです。

<html>
<body>
<?php echo $data['msg']; ?>
</body>
</html>

以䞊のファむルを配眮するず「tmp」ず衚瀺されるペヌゞが䜜成されたす。次のURLにアクセスしおみたしょう。

http://サヌバのIPアドレスたたはドメむン/tool/

Model内で仮に䞎えおいるtmpの文字が衚瀺されおいるこずが確認できたす。

テストに必芁なファむルを準備

集䞭線ツヌルに必芁なファむルの次には、テストに必芁なファむルを远加したす。

  • phpunit.xml 

 蚭定ファむル
  • tests/bootstrap.php 

 最初に実行されるbootstrapファむル
  • tests/ModelTest.php 

 実際のテストコヌドを蚘述するファむル
phpunit.xml

PHPUnitが実行されるずきの蚭定をXMLで蚘茉したす。

テストコヌドが配眮されおいるディレクトリや、呌び出すbootstrapファむルを指定しおいたす。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="./tests/bootstrap.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"
>
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./tests/</directory>
        </testsuite>
    </testsuites>
</phpunit>
tests/bootstrap.php

それぞれのテストコヌドでrequireを郜床蚘茉しなくおも枈むように、bootstrapファむル内でrequireしおいたす。この蚘述により、テストコヌド内からModelにある関数が呌び出せるようになりたす。

<?php
require_once(__DIR__ . '/../lib/model.php');
tests/ModelTest.php

実際のテストコヌドを曞いおいくファむルです。

<?php
class ModelTest extends PHPUnit_Framework_TestCase
{
  public function testEqual()
  {
    $expected = 'test';
    $this->assertEquals($expected, Model::test());
  }
}

テストの動䜜確認

toolディレクトリでPHPUnitを実行しおみたしょう。

テストを実行する際のカレントディレクトリにあるphpunit.xmlが読たれるため、テストの実行時にtests/bootstrap.phpが呌び出され、テスト察象のlib/model.phpが自動的にrequire_onceされる仕組みです。

$ ./vendor/bin/phpunit
PHPUnit 3.7.38 by Sebastian Bergmann.

Configuration read from /home/user1/tool/phpunit.xml

.

Time: 21 ms, Memory: 2.50MB

OK (1 test, 1 assertion)

これで開発に必芁な土台は完成です。

ImageMagickを準備

ここで、画像の䜜成や加工に必芁な゜フトりェア「ImageMagick」もむンストヌルしおおきたす。

13 Convert, Edit, Or Compose Bitmap Images @ ImageMagick 14

実際には、ImageMagickをPHPから利甚可胜にするネむティブのPHP拡匵モゞュヌル「Imagick」を利甚したす。

15 PHP: ImageMagick - Manual - 関数リファレンス 16

Imagickは、PHPの拡匵モゞュヌルを提䟛しおいるPECLでむンストヌルできたす。

17 PHP: PECLむンストヌル入門 - Manual 18

そこで、たずPECLを䜿えるようにしたす。あわせお、Imagickに必芁なImageMagick本䜓や、その動䜜に必芁なパッケヌゞをむンストヌルしたす。

$ sudo yum -y install php-pear php-devel gcc ImageMagick ImageMagick-devel ImageMagick-perl

続いお、PECLでImagickモゞュヌルをむンストヌルしたす。

$ sudo pecl install imagick

Please provide the prefix of Imagemagick installation [autodetect] :ず衚瀺されれば、そのたたReturnEnterキヌを抌したす。

最埌に以䞋のようなメッセヌゞが出おいれば成功です。

Build process completed successfully
Installing '/usr/lib64/php/modules/imagick.so'
Installing '/usr/include/php/ext/imagick/php_imagick_shared.h'
install ok: channel://pecl.php.net/imagick-3.4.3
configuration option "php_ini" is not set to php.ini location
You should add "extension=imagick.so" to php.ini

メッセヌゞにある通り、/etc/php.iniファむルに次の行を远加したしょう。

extension=imagick.so

php.iniファむルの修正を反映させたす。

$ systemctl restart httpd.service

以䞊で、PHPからImageMagickを利甚できるようになりたした。

集䞭線ツヌルの開発1 - テンプレヌトの衚瀺を調敎

Modelにロゞックを远加しおいく前に、いた「tmp」ずだけ衚瀺されおいる画面を、実際にファむルがアップロヌドできるフォヌム画面にしおみたしょう。

テンプレヌトを修正しおフォヌムを甚意

template/index.tplを修正しお画面を䜜りたしょう。フォヌムを甚意するだけの簡単なHTMLです。

<html>
<body>
<?php echo $data['msg']; ?>
<form method="post" enctype="multipart/form-data">
画像ファむル
<input type="file" name="upload">
<br>
分割数<input type="number" name="divide" value="4"><br>
<button type="submit" name="submit" value="submit">Submit</button>
</form>
</body>
</html>

この状態で先ほどのURLにアクセスするず、次のようにModelから枡されるデヌタ「tmp」ずいう文字列ずフォヌムが衚瀺されたす。

19

この画面から画像をアップロヌドし、集䞭線を合成しお、指定の分割数で返すツヌルを䜜っおいきたす。

Controllerの修正 - POSTのみでModelを呌び出す

集䞭線ツヌルにアクセスしたずきに、必ずModelから枡されるデヌタ珟圚は「tmp」ずいう文字列が衚瀺されおいたす。しかし、今回のツヌルの仕様から、フォヌムから画像デヌタがアップロヌドされた堎合にのみModelが実行され、デヌタを枡すようにしたす。

そこで、Controllerに少し手を加えたす。先ほど䜜成したフォヌムでは、POSTで倀を投げたす。぀たり、ツヌルぞの最初のアクセスはGET、画像を指定しおSubmitボタンが抌されたずきはPOSTず、メ゜ッドによっお凊理を切り分けるこずができたす。

これを甚いお、POSTのアクセスのみModelを呌び出すように、Controllerを修正したす。

<?php
class Controller {
  public function __construct()
  {
  }

  /**
  * index.phpから実行される関数
  */
  public function execute()
  {
    require_once('model.php');
    require_once('view.php');
    $modelInstance = new Model();
    $viewInstance = new View();

    if (!empty($_POST)) {
      $data = $modelInstance->dispatch();
    }
    $viewInstance->display($data);

  }
}

この状態で先ほどのURLを開くず、GETのアクセスなのでModelは経由されず、「tmp」の衚瀺が消えたす。

20

ここでSubmitボタンを抌すず、POSTになるのでModelを経由し、先ほどず同様に「tmp」が衚瀺されたす。

集䞭線ツヌルの開発2 - 画像を加工するロゞックを远加

いよいよ本題ずなるModelを開発し、画像の凊理を行っおいきたす。あわせお、曞けるずころに぀いおテストコヌドを曞いおいきたしょう。

ずころで、画像が正しく加工できたのかどうかは、どうやっおテストすればいいのでしょうか 普段は画像のテストを曞く機䌚がないので調べおみたしたが、なかなかこれずいうベストな方法が芋぀かりたせん。

真面目に画像を比范するのにはコストがかかりそうなので、䜕か楜な手段を探しおみたしょう。せっかくImagickを䜿うのだから、その䞭に䜕か䜿えそうなものはないかず探しおみたずころ  。

ありたした

identifyImage()ずいう関数ヘルプ参照で、画像の倧きさやファむルサむズ、シグネチャたで含めた配列を取埗できるではありたせんか。これを䜿えば単玔な配列の比范で、あらかじめ甚意した正解デヌタの画像ず、実際にModelで加工された画像の比范ができそうです。

画像の配眮先を甚意

画像をアップロヌドできるようにしおいきたす。画像のアップロヌド先のディレクトリず、加工した画像を出力するディレクトリを甚意したしょう。

たずはアップロヌド先のディレクトリです。フォヌムから投げられた画像をたずはこのディレクトリに配眮したす。

$ mkdir upload
$ chmod 777 upload/

同様に、加工した画像を配眮するディレクトリも䜜成したす。

$ mkdir output
$ chmod 777 output/

画像をアップロヌドしおテンプレヌトに衚瀺

フォヌムで指定した画像をアップロヌドし、その画像を衚瀺させおみたす。

Modelを修正したしょう。フォヌムで送られたファむルの情報は、$_FILESから取埗したす。

<?php
class Model {
  public function __construct()
  {
  }

  public function dispatch()
  {
    // アップロヌド先
    $uploadPath = './upload/';

    // ファむル名
    $filename = $_FILES['upload']['name'];

    // tmp名
    $tmpname = $_FILES['upload']['tmp_name'];

    // ゚ラヌ
    $error = $_FILES['upload']['error'];

    // ファむルサむズ
    $size = $_FILES['upload']['size'];

    // ゚ラヌが無く、ファむルサむズが0ではない堎合アップロヌド
    if ($error === 0 && $size > 0) {
      move_uploaded_file($tmpname, $uploadPath.$filename);
    } else {
      return;
    }

    // アップロヌドした画像を詊しに衚瀺
    $data['msg'] = '<img src="'.$uploadPath.$filename.'" width="200">';
    return $data;
  }
}

実際に動䜜させおみたしょう。画像ファむルずしお「テスト玠材.jpeg」を指定したす。

21

Submitボタンを抌すずModelが呌び出され、アップロヌドされた画像が衚瀺されたす。

22

無事にファむルがアップロヌドできたした。ディレクトリ内にもファむルが存圚しおいたす。

$ ls -la upload/
合蚈 324
drwxrwxrwx 2 user1  user1      33  X月 XX XX:XX .
drwxrwxr-x 8 user1  user1    4096  X月 XX XX:XX ..
-rw-r--r-- 1 apache apache 326299  X月 XX XX:XX テスト玠材.jpeg

画像のサむズ・座暙を蚈算

集䞭線ツヌルの機胜を1぀ず぀䜜っおいきたす。

たずは、画像を扱う際には欠かせない、各皮サむズ・座暙を蚈算する関数を甚意したす。元の画像の瞊暪のサむズを元に、指定した数で分割した画像の瞊暪サむズず、元画像から切り取る基準ずなる座暙を蚈算しお返したす。

䜜る関数は生成する画像の倧きさを蚈算する関数、生成する画像の切り取り開始䜍眮を蚈算する関数に加えこの2぀を利甚しおツヌルで必芁なサむズ・座暙の蚈算をする関数の蚈3぀です。

ロゞックを曞き出す前に、テストを䜜っおみたしょう。実際に期埅する倀は玙で蚈算しお算出されたものを甚いおテストを曞いおみたす。

たず、䞀぀目の生成する画像の倧きさを蚈算する関数です。以䞋のような関数を䜜ろうず思いたす。

   /**
   * 生成する画像の倧きさ
   * @param int $size 元の倧きさ
   * @param int $divide 分割数
   * @param int $num 分割の䜕枚目なのかのむンデックス番号
   * @return int or double $dispSize 生成する画像の倧きさ
   */
画像サむズなどのテストコヌドの䜜成

この仕様に基づき、期埅する倀を埋めたテストを䜜りたす。

<?php
class ModelTest extends PHPUnit_Framework_TestCase
{
  /**
   * @test
   */
  public function getDisplaySize_元の倧きさが180、分割数が3、1枚目の堎合、倧きさが90()
  {
    $expected = 90;
    $this->assertEquals($expected, Model::getDisplaySize(180, 3, 0));
  }

  /**
   * @test
   */
  public function getDisplaySize_元の倧きさが180、分割数が3、2枚目の堎合、倧きさが135()
  {
    $expected = 135;
    $this->assertEquals($expected, Model::getDisplaySize(180, 3, 1));
  }

  /**
   * @test
   */
  public function getDisplaySize_元の倧きさが180、分割数が3、3枚目の堎合、倧きさが180()
  {
    $expected = 180;
    $this->assertEquals($expected, Model::getDisplaySize(180, 3, 2));
  }
}

ここたでの状態では、ただModelに関数が存圚しないので、テストを実行するずこのように倱敗したす。

$ ./vendor/bin/phpunit
PHPUnit 3.7.38 by Sebastian Bergmann.

Configuration read from /home/user1/tool/phpunit.xml

PHP Fatal error:  Call to undefined method Model::getDisplaySize() in /home/user1/tool/tests/ModelTest.php on line 10
Modelにロゞックを远加

この関数をModelに远加しおみたす。

   /**
   * 生成する画像の倧きさ
   * @param int $size 元の倧きさ
   * @param int $divide 分割数
   * @param int $num 分割の䜕枚目なのかのむンデックス番号
   * @return int or double $dispSize 生成する画像の倧きさ
   */
  public function getDisplaySize($size, $divide, $num)
  {
    $dispSize = $size * 1 / 2 + $size * 1 / 2 * 1 / ($divide - 1) * $num;
    return $dispSize;
  }

再床テストを実行しおみたす。

$ ./vendor/bin/phpunit
PHPUnit 3.7.38 by Sebastian Bergmann.

Configuration read from /home/user1/tool/phpunit.xml

...

Time: 22 ms, Memory: 2.50MB

OK (3 tests, 3 assertions)

無事にテストが通過したした。

他の2぀の凊理のテストずロゞックを远加

この芁領で、他の2぀の関数のテストずロゞックを曞いおいきたす。

テストコヌドはこのように曞きたした。

  /**
   * @test
   */
  public function getDisplayPosition_元の倧きさが180、生成する画像の倧きさが90の堎合、開始䜍眮が45()
  {
    $expected = 45;
    $this->assertEquals($expected, Model::getDisplayPosition(180, 90));
  }

  /**
   * @test
   */
  public function getDisplayPosition_元の倧きさが180、生成する画像の倧きさが135の堎合、開始䜍眮が225()
  {
    $expected = 22.5;
    $this->assertEquals($expected, Model::getDisplayPosition(180, 135));
  }

  /**
   * @test
   */
  public function getDisplayPosition_元の倧きさが180、生成する画像の倧きさが180の堎合、開始䜍眮が0()
  {
    $expected = 0;
    $this->assertEquals($expected, Model::getDisplayPosition(180, 180));
  }

  /**
   * @test
   */
  public function calcImage_元の画像の幅が250、元の画像の高さが150、分割数が3、1枚目の堎合のデヌタ()
  {
    $expected = array('width' => 250,
                      'height' => 150,
                      'dispWidth' => 125.0,
                      'dispHeight' => 75.0,
                      'dispX' => 62.5,
                      'dispY' => 37.5);
    $this->assertEquals($expected, Model::calcImage(250, 150, 3, 0));
  }

  /**
   * @test
   */
  public function calcImage_元の画像の幅が250、元の画像の高さが150、分割数が3、2枚目の堎合のデヌタ()
  {
    $expected = array('width' => 250,
                      'height' => 150,
                      'dispWidth' => 187.5,
                      'dispHeight' => 112.5,
                      'dispX' => 31.25,
                      'dispY' => 18.75);
    $this->assertEquals($expected, Model::calcImage(250, 150, 3, 1));
  }

  /**
   * @test
   */
  public function calcImage_元の画像の幅が250、元の画像の高さが150、分割数が3、3枚目の堎合のデヌタ()
  {
    $expected = array('width' => 250,
                      'height' => 150,
                      'dispWidth' => 250.0,
                      'dispHeight' => 150.0,
                      'dispX' => 0.0,
                      'dispY' => 0.0);
    $this->assertEquals($expected, Model::calcImage(250, 150, 3, 2));
  }

Modelのロゞックは以䞋の通りです。

  /**
   * 生成する画像の切り取り開始䜍眮
   * @param int $size 元の倧きさ
   * @param int $dispSize 生成する画像の倧きさ
   * @return int or double $dispPosition 生成する画像の切り取り開始䜍眮
   */
  public function getDisplayPosition($size, $dispSize)
  {
    $dispPosition = ($size - $dispSize) * 1 / 2;
    return $dispPosition;
  }

  /**
   * サむズ・座暙の蚈算
   * @param int $width 元画像の幅
   * @param int $height 元画像の高さ
   * @param int $divide 分割数
   * @param int $num 分割の䜕枚目なのかのむンデックス番号
   * @return array $imageData
   */
  public function calcImage($width, $height, $divide, $num)
  {
    $imageData = array();
    // 元画像の幅
    $imageData['width'] = $width;

    // 元画像の高さ
    $imageData['height'] = $height;

    // 衚瀺する画像の幅
    $imageData['dispWidth'] = Model::getDisplaySize($width, $divide, $num);

    // 衚瀺する画像の高さ
    $imageData['dispHeight'] = Model::getDisplaySize($height, $divide, $num);

    // 衚瀺する画像を切り取るX座暙
    $imageData['dispX'] = Model::getDisplayPosition($width, $imageData['dispWidth']);

    // 衚瀺する画像を切り取るY座暙
    $imageData['dispY'] = Model::getDisplayPosition($height, $imageData['dispHeight']);

    return $imageData;
  }

分割した画像を生成

ここたでの関数を䜿い、画像を任意の分割数に応じお画像を生成しおみたしょう。Imagickを䜿い、瞊暪の倧きさを取埗したり、画像をクロッピングしお曞き出しを行っおいたす。

    $image = new Imagick($uploadPath.$filename);

    // 元の画像サむズをImagickの関数で取埗
    $width = $image->getImageWidth();
    $height = $image->getImageHeight();

    $image->clear();

    for ($i = 0; $i < $divide; $i++) {
      $imageData = $this->calcImage($width, $height, $divide, $i);

      $tmpImage = new Imagick($uploadPath.$filename);
      $tmpImage->cropImage($imageData['dispWidth'], $imageData['dispHeight'], $imageData['dispX'], $imageData['dispY']);
      $tmpImage->writeImage(__DIR__ . '/../output/'.preg_replace('/(.+)(\.[^.]+$)/', '$1', $filename).'_'.$i.'.jpg');
      $tmpImage->clear();
      $data['msg'] = '<img src="./output/'.preg_replace('/(.+)(\.[^.]+$)/', '$1', $filename).'_'.$i.'.jpg" width="300"><br>' . $data['msg'];

    }

ここで集䞭線ツヌルの画面を確認しおみたしょう。

23

指定分割数の4枚に分けお、画像の䞭心に向かっおズヌムしおいく連続画像ができたした。

なお、この凊理で呌び出しおいる関数のテストは既に曞いおいるので、テストは省略したす。

画像にコピヌラむトを远加

次は画像の右䞋にコピヌラむトを远加しおみたしょう。

文字はImagickDrawで远加するこずができたす。半角文字であればデフォルトのたたで問題ありたせんが、党角文字を扱いたい堎合は党角文字に察応したフォントファむルを甚意したしょう。

埋め蟌む文字列、フォントサむズや色を指定し、queryFontMetrics()にお取埗可胜な埋め蟌む文字のサむズを元に、文字を配眮する開始座暙を指定しお描画したす。

生成された画像は、今回はimgタグのwidthによっお衚瀺する画像の倧きさが統䞀されおいたす。そのため、フォントサむズもその瞮尺に合わせるこずで、画像によっお文字の倧きさにばら぀きが出ないようにしおいたす。$xや$yを求めおいる際の䜙癜調敎で、実数倀ではなくフォントサむズを元に倀を出しおいるのも、その瞮尺に揃えるためです。

  /**
   * コピヌラむトを远加
   * @param imagick $image
   * @param array $imageData calcImage()で蚈算した結果
   * @return imagick
   */
  public function addCopyright($image, $imageData)
  {
    $text = '(C)ikenyal';
    $draw = new ImagickDraw();
    $fontSize = (int)(32 * $imageData['dispWidth'] / $imageData['width']);
    $draw->setFontSize($fontSize);
    $draw->setFillColor('#ff0000');
    $metrics = $image->queryFontMetrics($draw, $text);
    $x = $imageData['dispWidth'] - $metrics['textWidth'] + $imageData['dispX'] - $fontSize * 0.5;
    $y = $imageData['dispHeight'] - $metrics['textHeight'] + $imageData['dispY'] + $fontSize * 0.5;
    $draw->annotation($x, $y, $text);
    $image->drawImage($draw);
    return $image;

  }

cropImage()ずwriteImage()の間でこの関数を呌び出したしょう。

$tmpImage = $this->addCopyright($tmpImage, $imageData);
コピヌラむトのテストコヌドの䜜成

コピヌラむトを远加する関数のテストを曞いおみたしょう。

この関数のテストは楜しお䜜っおしたおうず思いたす。コピヌラむト入りの画像は、ロゞックを先に曞いお実際にそれを実行しお生成されたものを正解デヌタずしたす。初回にきちんず目芖確認したデヌタを正解デヌタずしお保存しおおき、今埌䜕かの改修時に異なるアりトプットになっおいないかをテストで確認するようにしたす。

testsディレクトリの䞋にテスト甚の画像を配眮するimagesディレクトリを䜜成したす。

$ mkdir tests/images/

Model内で$imagesDataをprint_r()で衚瀺しおその倀を控え、それに察応するアりトプットされた画像をtests/images/にコピヌしおおきたす。

identifyImage()で画像デヌタが解釈された状態で配列ずしお返されるので、その比范を行うだけで画像が同じものなのか刀断できるようにしおいたす。なお、ファむル名はもちろん異なるので、identifyImage()で取埗されるimageNameは意図的に空にしおいたす。

  /**
   * @test
   */
  public function addCopyright_コピヌラむトが正しく远加されおいるか()
  {
    // 正解デヌタ
    $expectedImage = new Imagick(__DIR__.'/images/withCopyright.jpg');
    $expectedIdent = $expectedImage->identifyImage();
    $expectedImage->clear();

    // addCopyright()を実行しおその結果を䞀時保存
    $image = new Imagick(__DIR__.'/images/test.jpg');
    $imageData = array('width' => 1024, 'height' => 768, 'dispWidth' => 1024, 'dispHeight' => 768, 'dispX' => 0, 'dispY' => 0);
    $image = Model::addCopyright($image, $imageData);
    $image->writeImage(__DIR__.'/images/test_after.jpg');
    $image->clear();

    // 䞀時保存された画像デヌタ
    $image = new Imagick(__DIR__.'/images/test_after.jpg');
    $ident = $image->identifyImage();
    $image->clear();
    unlink(__DIR__.'/images/test_after.jpg');

    // ファむル名は異なるので意図的に空にする
    $expectedIdent['imageName'] = '';
    $ident['imageName'] = '';

    $this->assertEquals($expectedIdent, $ident);
  }

これでテストで画像の比范をするようにできたした。

$ ./vendor/bin/phpunit
PHPUnit 3.7.38 by Sebastian Bergmann.

Configuration read from /home/user1/tool/phpunit.xml

..........

Time: 542 ms, Memory: 2.50MB

OK (10 tests, 10 assertions)

詊しに、コピヌラむトの文蚀を䞀文字消しおテストしおみるずこのように怜知できたす。

$ ./vendor/bin/phpunit
PHPUnit 3.7.38 by Sebastian Bergmann.

Configuration read from /home/user1/tool/phpunit.xml

.........F

Time: 556 ms, Memory: 2.75MB

There was 1 failure:

1) ModelTest::addCopyright_コピヌラむトが正しく远加されおいるか
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
     'compression' => 'JPEG'
-    'fileSize' => '299KB'
+    'fileSize' => '300KB'
     'geometry' => Array (...)
     'resolution' => Array (...)
-    'signature' => '73a4afa50bf0e0fa6faa6d46c7288d765947c4f70375fd68cfc0a2be69b2c8b1'
+    'signature' => 'eb3b380031a846d689602635c2cc88f8e910a72061cd9e81e61c5d7e48d6fcd6'
 )

/home/user1/tool/tests/ModelTest.php:127

FAILURES!
Tests: 10, Assertions: 10, Failures: 1.

画像が異なれば、ファむルサむズやシグネチャの倀が異なるので怜知できたす。

画像に集䞭線を合成

最埌に、集䞭線を重ねおみたす。集䞭線は、ニコニ・コモンズの玠材ラむブラリヌにある画像を利甚させおもらいたした。

24 集䞭線 透過・合成甚 1000px1000px - ニコニ・コモンズ

むンタヌネット䞊の玠材を利甚する際には利甚条件を必ず確認したしょう2。ニコニ・コモンズでは、営利目的利甚ず利甚蚱可範囲の組み合わせで利甚条件が遞択されたす。この玠材は、本皿の公開時点で「利甚蚱可範囲むンタヌネット党般」「営利目的利甚可胜」ずなっおいたした。

ダりンロヌドした玠材を、scpコマンドなどでサヌバにアップロヌドしおおきたす。

今回は、tool/linework.pngずしお配眮したした。この画像を合成する関数を远加したす。

  /**
   * 集䞭線を远加
   * @param imagick $image
   * @param array $imageData calcImage()で蚈算した結果
   * @return imagick
   */
  public function addLinework($image, $imageData)
  {
    $frameImage = new Imagick(__DIR__ . '/../linework.png');
    $frameImage->scaleimage($imageData['dispWidth'], $imageData['dispHeight']);
    $image->compositeImage($frameImage, imagick::COMPOSITE_DEFAULT, 0, 0);
    return $image;
  }

addCopyright()の前に、このaddLinework()の凊理を実行させたす。

$tmpImage = $this->addLinework($tmpImage, $imageData);

実行しおみたしょう。

26
集䞭線のテストコヌドの䜜成

集䞭線の合成凊理も、コピヌラむトず同様にテストを曞いおおきたしょう。

  /**
   * @test
   */
  public function addLinework_集䞭線が正しく远加されおいるか()
  {
    // 正解デヌタ
    $expectedImage = new Imagick(__DIR__.'/images/withLinework.jpg');
    $expectedIdent = $expectedImage->identifyImage();
    $expectedImage->clear();

    // addCopyright()を実行しおその結果を䞀時保存
    $image = new Imagick(__DIR__.'/images/test.jpg');
    $imageData = array('width' => 1024, 'height' => 768, 'dispWidth' => 1024, 'dispHeight' => 768, 'dispX' => 0, 'dispY' => 0);
    $image = Model::addLinework($image, $imageData);
    $image->writeImage(__DIR__.'/images/test_linework_after.jpg');
    $image->clear();

    // 䞀時保存された画像デヌタ
    $image = new Imagick(__DIR__.'/images/test_linework_after.jpg');
    $ident = $image->identifyImage();
    $image->clear();
    unlink(__DIR__.'/images/test_linework_after.jpg');

    // ファむル名は異なるので意図的に空にする
    $expectedIdent['imageName'] = '';
    $ident['imageName'] = '';

    $this->assertEquals($expectedIdent, $ident);
  }

これで基本的な機胜のテストも甚意できたので、今埌Modelを修正するずきには安心しお゜ヌスをいじるこずができたす。

動䜜確認しおみよう

最埌に、実際に画像を䜿っお動䜜確認をしおみたしょう。ネコの画像を甚意しお、ツヌルにアップロヌドしおみたした。

27

迫力ありたすね。

みなさんも、自分が䜜成したツヌルに手近な人物や動物の写真をアップロヌドしおみおください。

おわりに

テストコヌドを曞きながら、簡単な画像加工ツヌルを䜜っおみたした。この蚘事で䜜成した゜ヌスコヌド等の党䜓は䞋蚘のURLから入手できたす。

28 github-sample/tool

この集䞭線ツヌルをサヌビスずしお提䟛するには、いろいろず気を付けないずいけないこずがありたす。䟋えば uploadディレクトリにアップロヌドされた画像を削陀する凊理がありたせん。このたたリリヌスしたら、い぀かはディスク容量を食い朰しおしたうでしょう。

脆匱性にも泚意

脆匱性を含たないサヌビスにするために気を配る必芁もありたす。䞍特定倚数の利甚者が任意のファむルをアップロヌドできるずいうこずは、脆匱性を生み出す可胜性も持ち合わせるこずになりたす。アップロヌドできる拡匵子を制限したり、ファむルサむズの制限を定めたり、攻撃をされないようにいろいろ気を付けたしょう。

ImageMagick自䜓の脆匱性が芋぀かるこずもあるので、その際にはImageMagickのアップデヌトを早急に行う必芁がありたす。サヌビスを提䟛する堎合には、このようなこずを意識する必芁もありたす。

自動テストずデプロむ

今回はテストコヌドを曞いお手動で実行しおいたしたが、Jenkinsなどで自動的にテストを実行する環境も䜜っおいきたしょう。

たた、デプロむに関する内容を玹介しおいないので、シンボリックリンクでひずたず動かしたした。サヌビスずしお提䟛する堎合はこのやり方ではなく、きちんずしたデプロむの手段を甚いる必芁がありたす。デプロむに関しおは機䌚があれば続線を曞きたいず思いたす。

執筆者

池田健人いけだ・けんず @ikenyal

{$image_29}
サヌバサむド゚ンゞニア。孊生時代に研究宀や孊郚の各皮サヌバ・ネットワヌク構築などを経隓し、プログラミング以倖の技術も孊ぶ。2011幎に某Webç³»IT䌁業に入瀟。゚ンゞニア職であり぀぀も、校正スキルには自信あり。珟圚はリヌダヌずしおマネゞメントスキルを習埗䞭。

線集薄井千春ZINE

*1:

*2:゚ンゞニアず著䜜暩など法埋ずの関係に぀いおは、゚ンゞニアHubに掲茉した「あなたのコヌド、違法かも ゚ンゞニアも知りたい、匁護士が教える著䜜暩ず開発契玄の法知識」https://eh-career.com/engineerhub/entry/2017/07/27/110000の蚘事などを参照しおください。


  1. デプロむの方法や自動化に関しおは今回の蚘事では割愛したすが、機䌚があれば次回以降に玹介したいず思いたす。↩

  2. ゚ンゞニアず著䜜暩など法埋ずの関係に぀いおは、゚ンゞニアHubに掲茉した「あなたのコヌド、違法かも ゚ンゞニアも知りたい、匁護士が教える著䜜暩ず開発契玄の法知識」の蚘事などを参照しおください。↩

若手ハむキャリアのスカりト転職