お茶漬けびより

"あなたに教わったことを、噛んでいるのですよ" 五等分の花嫁 7巻 「最後の試験が五月の場合」より

CLI で asp.net core の環境を構築する

NET Core CLIVSCodeASP.NET Core の環境を作っていきます。

目標は、以下の手順3 まで行うことです。

docs.microsoft.com

docs.microsoft.com

上記は、Visual Studio を使っていますが、ここでは、NET Core CLIVSCode のみです。 私の環境は Mac ですが、WindowsLinux でも問題ないと思います(Windows の方が問題ないかも……)。

.NET Core SDK のインストール

公式サイトからパッケージを落としてインストールします。以下のURL先で Download .NET Core SDK をクリックしてパッケージを落とします。

dotnet.microsoft.com

インストールしたら、ターミナルを立ち上げて dotnet --version で動作するか確認します。

$dotnet --version
3.1.102

プロジェクトの作成をする

プロジェクトの作成は CLI で行います。dotnet new でプロジェクトの作成が出来ますが、プロジェクトの種類は多数あるので、一度 --help でどんなものがあるか確認してみましょう。

dotnet new --help を叩くと私の環境では、以下のように表示されました(途中に表示されるオプションは省略しています)。

Templates                                         Short Name               Language          Tags
----------------------------------------------------------------------------------------------------------------------------------
Console Application                               console                  [C#], F#, VB      Common/Console
Class library                                     classlib                 [C#], F#, VB      Common/Library
WPF Application                                   wpf                      [C#]              Common/WPF
WPF Class library                                 wpflib                   [C#]              Common/WPF
WPF Custom Control Library                        wpfcustomcontrollib      [C#]              Common/WPF
WPF User Control Library                          wpfusercontrollib        [C#]              Common/WPF
Windows Forms (WinForms) Application              winforms                 [C#]              Common/WinForms
Windows Forms (WinForms) Class library            winformslib              [C#]              Common/WinForms
Worker Service                                    worker                   [C#]              Common/Worker/Web
Unit Test Project                                 mstest                   [C#], F#, VB      Test/MSTest
NUnit 3 Test Project                              nunit                    [C#], F#, VB      Test/NUnit
NUnit 3 Test Item                                 nunit-test               [C#], F#, VB      Test/NUnit
xUnit Test Project                                xunit                    [C#], F#, VB      Test/xUnit
Razor Component                                   razorcomponent           [C#]              Web/ASP.NET
Razor Page                                        page                     [C#]              Web/ASP.NET
MVC ViewImports                                   viewimports              [C#]              Web/ASP.NET
MVC ViewStart                                     viewstart                [C#]              Web/ASP.NET
Blazor Server App                                 blazorserver             [C#]              Web/Blazor
ASP.NET Core Empty                                web                      [C#], F#          Web/Empty
ASP.NET Core Web App (Model-View-Controller)      mvc                      [C#], F#          Web/MVC
ASP.NET Core Web App                              webapp                   [C#]              Web/MVC/Razor Pages
ASP.NET Core with Angular                         angular                  [C#]              Web/MVC/SPA
ASP.NET Core with React.js                        react                    [C#]              Web/MVC/SPA
ASP.NET Core with React.js and Redux              reactredux               [C#]              Web/MVC/SPA
Razor Class Library                               razorclasslib            [C#]              Web/Razor/Library/Razor Class Library
ASP.NET Core Web API                              webapi                   [C#], F#          Web/WebAPI
ASP.NET Core gRPC Service                         grpc                     [C#]              Web/gRPC
dotnet gitignore file                             gitignore                                  Config
global.json file                                  globaljson                                 Config
NuGet Config                                      nugetconfig                                Config
Dotnet local tool manifest file                   tool-manifest                              Config
Web Config                                        webconfig                                  Config
Solution File                                     sln                                        Solution
Protocol Buffer File                              proto                                      Web/gRPC

二列目に Short Name があります。これを引数に指定します。ちなみにプロジェクトだけでなく、一部のファイルは new で作成できます。上記は、ファイルのテンプレートも混ざっているので見辛いです。プロジェクトだけに絞りたいときは、dotnet new --type project を実行します。

Templates                                         Short Name               Language          Tags
----------------------------------------------------------------------------------------------------------------------------------
Console Application                               console                  [C#], F#, VB      Common/Console
Class library                                     classlib                 [C#], F#, VB      Common/Library
WPF Application                                   wpf                      [C#]              Common/WPF
WPF Class library                                 wpflib                   [C#]              Common/WPF
WPF Custom Control Library                        wpfcustomcontrollib      [C#]              Common/WPF
WPF User Control Library                          wpfusercontrollib        [C#]              Common/WPF
Windows Forms (WinForms) Application              winforms                 [C#]              Common/WinForms
Windows Forms (WinForms) Class library            winformslib              [C#]              Common/WinForms
Worker Service                                    worker                   [C#]              Common/Worker/Web
Unit Test Project                                 mstest                   [C#], F#, VB      Test/MSTest
NUnit 3 Test Project                              nunit                    [C#], F#, VB      Test/NUnit
xUnit Test Project                                xunit                    [C#], F#, VB      Test/xUnit
Blazor Server App                                 blazorserver             [C#]              Web/Blazor
ASP.NET Core Empty                                web                      [C#], F#          Web/Empty
ASP.NET Core Web App (Model-View-Controller)      mvc                      [C#], F#          Web/MVC
ASP.NET Core Web App                              webapp                   [C#]              Web/MVC/Razor Pages
ASP.NET Core with Angular                         angular                  [C#]              Web/MVC/SPA
ASP.NET Core with React.js                        react                    [C#]              Web/MVC/SPA
ASP.NET Core with React.js and Redux              reactredux               [C#]              Web/MVC/SPA
Razor Class Library                               razorclasslib            [C#]              Web/Razor/Library/Razor Class Library
ASP.NET Core Web API                              webapi                   [C#], F#          Web/WebAPI
ASP.NET Core gRPC Service                         grpc                     [C#]              Web/gRPC
Solution File                                     sln                                        Solution

project の他に、item, other などがあります。item はファイルのテンプレートっぽいです。

今回作成するプロジェクトは、ASP.NET Core なので、webapp を指定します。dotnet new webapp -o FirstApp で実行します。-o は出力先のディレクトリで、存在しない場合は自動で作成されます。ディレクトリとプロジェクト名を別にしたい場合は、-n でプロジェクト名を指定します。

プロジェクトのテンプレートを見ると Angular や React などがありますが、これはクライアント側の構成です。今回の webapp では、Blazor になります。Blazor については、

docs.microsoft.com

を読めば何となく分かると思います。Blazor の良し悪しについては、

qiita.com

で判断できると思います。良し悪しというより自分に向いているか向いていないのかですね。今回は、クライアント側ではなくサーバ側の ASP.NET に慣れることが目的なので、Blazor のままで進めます。

ここでサーバを実行できる環境が整ったので、一度、動作確認してみましょう。プロジェクト直下で、dotnet run と実行します。localhost:5001localhost:5000 にアクセスするとWebアプリが表示されると思います。

動作確認をしたので画面の内容を編集して、結果が反映されるか確認してみましょう。

プロジェクト直下で code . と実行し、VSCode を開きます。index.cshtml を開き、<div> の中身を以下のように書き換えます。@Model.Time が Blazor 特有の変数みたいです。

<div class="text-center">
    <h2>It's @Model.Time right now on ther server!</h2>
</div>

次に、@Model.Time の値を初期化する処理を追加します。index.cshtml.cs を開きます。OnGet メソッドを以下のように変更します。また、変数 Time を追加します。

public string Time { get; set; }
public void OnGet()
{
    Time = DateTime.Today.ToShortTimeString();
}

サーバを実行し直すと、反映されていることが分かると思います。時間は、常に 0:00 です。これは、Today の返す値が、日付 0:00 と返すためです。本当にそうなっているのかデバッグで確認してみましょう。

VSCodeデバッグ

VSCodeデバッグを開き(通常だと左側の虫のマーク)、VSCodelaunch.json を作成する機能を呼び出し、 .NET Core を選択しlaunch.json を作成します。

.NET Core Launch (web) を選択して、デバッグを開始します。Time = DateTime.Today.ToShortTimeString(); の箇所でブレークポイントを貼り、サイトにアクセスします。すると、ブレークポイントで止まると思います。デバッグしても起動に失敗する場合、ターミナル側でサーバを立ち上げっぱなしにしていないか確認してください。

ブレークポイントの位置では、まだ Time に値は入っていないので、null です。ステップを一つ進める(Step Over)と値が入り、0:00 になっているのが確認できます(Time にカーソルを当てると表示されます)。また、VSCode の下側に DEBUG CONSOLE が表示されていると思いますが、そこでコードを実行することができます。例えば、DateTime.Today と実行すると {2020/03/07 0:00:00} と表示されます(値は、日付によって変わります)。これを見ると 0:00 が表示される理由が分かりますね。

では、現在の時刻が表示されるように修正します。例えば、TodayNow に変更してみます。デバッグを起動している場合は、Restart のアイコンをクリックすれば起動しなおします。確認すると現在の時刻が表示されていると思います。

DB を追加する

DB を追加し、DB を操作する画面を追加します。まず、DB に追加する情報を持つモデルを作成します。今回は、Game というモデルにします。Models ディレクトリを作成し、その下で Game.cs を作成します。中身は、以下になります。

public class Game
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int PublicationYear { get; set; }
    public int MinimumPlayers { get; set; }
    public int MaximumPlayers { get; set; }
}

次に、DBの環境を整えます。私は、C# .NET に詳しくないのでよく分かっていないのですが、 .NET では、 Entity Framework で DB を簡単に操作するようにするのが一般的みたいです。なので、この Entity Framework を使えるようにします。.NET Core では、 Entity Framework Core(EF Core) を使います。

.NET CLI では、フレームワークやライブラリをコマンドで追加できます。NuGet のパッケージを追加するときもコマンドを使います。EF Core を追加する場合、tool コマンドを使います。追加したツールの一覧は、dotnet tool list --global で確認出来ます。初めての場合は、何も追加されていないと思います。

EF Core を dotnet tool install --global dotnet-ef でインストールします。インストールが完了したら、再度一覧を表示して、追加されていることを確認します。私の環境では、以下のようになりました。

パッケージ ID                         バージョン      コマンド
-----------------------------------------------------------------------
dotnet-ef                        3.1.2      dotnet-ef

また、dotnet ef --version で使えるか確認します。

Entity Framework Core .NET Command-line Tools
3.1.2

さて、EF Core を追加しました。Visual Studio の場合は、テンプレートで DB を操作するページを作成できます(以下のURL先を参照)。

docs.microsoft.com

同じことをしたい場合、もう少し環境を整える必要があります。dotnet tool install -g dotnet-aspnet-codegenerator で EF に関するコードを作成してくれるコードジェネレータツールを追加します。追加したら、dotnet aspnet-codegenerator -h で動作確認します。といってもオプションに -h はないっぽいですが。

Blazor のページを Razor Page と呼ぶそうですが、この Razor Page を追加するには、次のコマンドで追加できます。ただ、現状はまだ失敗します。

dotnet aspnet-codegenerator razorpage -m Game -dc AppDbContext -outDir Pages/Games

aspnet-codegenerator ツールを使うには、以下のパッケージが必要です。

追加するときは、dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design のようにします。dotnet add package 追加したいパッケージ名です。

Microsoft.EntityFrameworkCore.SqlServer は、ツールを使うために必要です。Mac やおそらく Linux は、DBとして SqlServer は使えません(どうにかすれば使えるのかもしれませんが)。ですので、別途 DB のツールを追加する必要があります。今回は、 Microsoft.EntityFrameworkCore.InMemory を使います。

またNuget のパッケージですので、ここで追加したパッケージは、別のプロジェクトで必要なとき、同じようにパッケージを追加するコマンドを実行する必要があります。追加されているパッケージは、dotnet list package で確認できます。

上記3つを入れていると以下のように出力されるはずです。InMemory を追加している場合は、それも表示されているはずです。

プロジェクト 'FirstApp' に次のパッケージ参照が含まれています
   [netcoreapp3.1]:
   最上位レベル パッケージ                                            要求済み    解決済み
   > Microsoft.EntityFrameworkCore.SqlServer               3.1.2   3.1.2
   > Microsoft.EntityFrameworkCore.Tools                   3.1.2   3.1.2
   > Microsoft.VisualStudio.Web.CodeGeneration.Design      3.1.1   3.1.1

では、dotnet aspnet-codegenerator razorpage -m Game -dc AppDbContext -outDir Pages/Games を実行して、成功するか確認しましょう。成功した場合、以下のように出力されます。

Building project ...
Finding the generator 'razorpage'...
Running the generator 'razorpage'...
Generating a new DbContext class 'AppDbContext'
Attempting to compile the application in memory with the added DbContext.
Attempting to figure out the EntityFramework metadata for the model and DbContext: 'Game'
Added DbContext : '/Data/AppDbContext.cs'
Added Razor Page : /Pages/Games/Create.cshtml
Added PageModel : /Pages/Games/Create.cshtml.cs
Added Razor Page : /Pages/Games/Edit.cshtml
Added PageModel : /Pages/Games/Edit.cshtml.cs
Added Razor Page : /Pages/Games/Details.cshtml
Added PageModel : /Pages/Games/Details.cshtml.cs
Added Razor Page : /Pages/Games/Delete.cshtml
Added PageModel : /Pages/Games/Delete.cshtml.cs
Added Razor Page : /Pages/Games/Index.cshtml
Added PageModel : /Pages/Games/Index.cshtml.cs
RunTime 00:00:09.50

成功したら、Data ディレクトリが作成され、AppDbContext.cs が追加されていると思います。また、Pages/Games には、Create, Delete, Details, Edit, Index の cshtml, cs ファイルが追加されていると思います。

SqlServer ではなく、InMemory を使う場合

上記のジェネレータツールを実行すると SqlServer を使うようにコードが変更されています。ですので、これを使用したい DB に合わせる必要があります。

今回は、InMemory を使うため Startup.cs を開き、以下のように変更します。optionsUse...UseInMemoryDatabase に変えるだけです。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase(Configuration.GetConnectionString("AppDbContext")));
}

以上です。

DB を操作するページを作成する

最後に Programs.cs を以下のように変更します。

...
using Microsoft.Extensions.DependencyInjection;

...

public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    using (var scope = host.Services.CreateScope()) {
        var services = scope.ServiceProvider;
        try
        {
            var context = services.GetRequiredService<AppDbContext>();
            context.Database.EnsureCreated();
        }
        catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "An error occurred creating the DB.");
        }
    }
    host.Run();
}

これで完了です。dotnet run で実行して、localhost:5000/Games にアクセスしてみてください。テーブルの追加や削除、編集の画面が出てくると思います。

参考

以下の文献を参考にしました(途中で挙げているものは抜いています)。

docs.microsoft.com

docs.microsoft.com

docs.microsoft.com

docs.microsoft.com

使用できる DB は、これを見ると分かります。

docs.microsoft.com

コマンドのドキュメントです。

docs.microsoft.com

docs.microsoft.com

より理解を深めるために読むと良さそうです(未読)。

docs.microsoft.com

NuGet について

docs.microsoft.com

おわりに

Android 開発の入門の息抜きに始めたときは、本当に VSCode だけで出来るか不安だったのですが、何とか最後まで行けました。コードジェネレータあたりで詰まりかけましたが……。どのツールも強力なので使いこなせると便利そうです。ただ、学習コスト高そうなので、自動化のような目的がないなら Visual Studio でいい気がします。

ASP.NET .NET Framework の知識がなさすぎて、中身のコードややってることを全然理解できていないので、暇があれば調べてみたいですね。ASP.NET 面白そうですし。

最後にこの手順を行った時のソースコードを載せておきます。

github.com

2020年の抱負

f:id:pickles-ochazuke:20200104221710j:plain

明けましておめでとうございます。

普段は年が変わっても抱負とか立てずに(といっても内心は立てているのだけど)その年を過ごすのですが、なんとなく今年はいくつか抱負が思いついたので、その記録も兼ねてここに書いておきます。

心変わりしたこと

抱負の前に、最近心変わりしたことも書いておきます。

今年は、初詣をしました。前にしたのはいつか記憶にないぐらいには初詣をしない質です。というのは、自分は神頼みな行為があまり好きではないからです。良くも悪くも自分の努力を否定することになる感じが嫌いでした。例えば、試験に合格しますようにと願ってそれで合格したときに、「お願いごとが叶った」。というような考えになり、その試験を合格するために自分がしてきた努力がなかったことになるように感じるからです。そういう考えがあったので、お願いごとや占いごとは好きではありませんでした。

でも、年が明ける前か、明けたあとぐらいにふと、願いごとは自分に対する決意のようなもの。占いは悩みごとを別の視点から見るための道具にするといいんじゃないかなと感じ始めました。それで今年は初詣に行き、参拝とおみくじをしてきました。先の考えだとしっくりきたので、今後も初詣は続けようかなと思います。

以上が最近心変わりしたことでした。

今年の抱負

今年の抱負は以下です。

  • AtCoder を始める
  • 本をたくさん読む
  • トランペットを始める
  • 551 の豚まんを食べる
  • スノボーをする
  • Android アプリを作れるようになる

AtCoder を始める

一度だけやったことがあります。いつも忙しいのを言い訳にしていなかったのですが、自分のコードを書く能力が低いと感じていたので、今年は挑戦してみようかなと思いました。といってもやっぱり忙しいというか他にもやりたいことがあるので、今年は、緑になれたら十分かなと思っています(これでもハードルが高い可能性がある)。

本をたくさん読む

本は買うのですが、積むことが多いのでいい加減消化したいのと、読まないと技術的な能力って変わらないなと感じるので抱負にしました。具体的な数字は書いていないですが、最低でも月1冊。ペースが上がれば二週に1冊とか目指したいです。かといってインプットばかりじゃなくアウトプットもしたほうが良いと思うので、そこらへんは注意したい。

トランペットを始める

大学生のときに8ヶ月ほどやっていたので初めてではないですが、吹くの楽しかったのでまた始めたいなと思い抱負にしました。レンタルで月1万ぐらいのがあるみたいなのでまずはそれで試してみようかなと。3月ぐらいから始めようかな(今の時期は寒くて唇がしぬ)。できるかできないかは金の問題。

551 の豚まんを食べる

生まれてからずっと関西に住んでるけど食べたことないので。今のうちに食べないと後悔する気がするので抱負にしました。冬の間に食べたほうが幸せになれそうなので近いうちに食べようと思います。

スノボーを始める

毎年したいしたい言ってしていないのでいい加減に始めようかと。スキーはしたことあるのだけど(2回。どちらも学校の修学旅行)、スノボーはないです。これは1月か2月にしないといけない。お金はないのでとりあえず一回できたらいいかなと。

Android アプリを作る

Android のアプリを作れるようになりたい。3回ぐらい勉強して挫折(飽きる)しているので本腰入れてしないといけないかなと。とりあえず技術書典に応募したら受かったので、Android の本を書こうと思います。これは自分を追い込んで進めていく。これもお金がかかる。

おわりに

以上が抱負でした。果報は寝て待てのような状態になれる一年にしたいです。

Kotlin 勉強メモ(Kotlin 入門までの助走読本)

まちカドまぞく面白いです。二回見る程度には面白いです。

最近、Android 開発できるようになろうとチマチマ勉強中です。過去に一度か二度勉強したのですが途中で挫折しました。でも今回は少しずつですが理解が進んでいる気がします。

Android 開発では、今や開発言語は Kotlin です。Android 開発しているのに Kotlin 知らない人なんていません。GoogleAPI の提供を Kotlin 優先にするって言ってます。

jp.techcrunch.com

だから Kotlin を勉強しています。

というのもありますが、単純に書いてて楽しいです。Kotlin

(もちろん、Android 開発は必ず Kotlin でするべきということではありません。環境によりけりです)

参考書籍

Kotlin を勉強するにあたって参考にした書籍は Kotlin 入門までの助走読本です。

drive.google.com

プログラミング経験者が Kotlin を学ぶのに最適な本です。あまり詳細に踏み込まず、かといって浅すぎない。Kotlin がどんな言語かなんとなく理解できる良い本です。

過去に速習 Kotlin という本も読んだことがありますが、これよりも浅いように感じました。ただ、助走読本のほうは、Java からの移行や今後どうやって Kotlin を取り入れるかなどの情報もあるのでどちらの方が良いというのは決められないです。

速習 Kotlin: Javaより簡単!新Android開発言語を今すぐマスター 速習シリーズ

速習 Kotlin: Javaより簡単!新Android開発言語を今すぐマスター 速習シリーズ

まだ読んでいませんが、さらに Kotlin を知るなら Kotlin イン・アクションは必読だと思います。

Kotlinイン・アクション

Kotlinイン・アクション

今回は、Kotlin 入門までの助走読本 を読んでメモしたことを羅列していきます。

はじめに

Kotlin の環境構築は IntelliJ IDEA をいれれば済むはずですが、それも面倒な場合、とにかく1秒でも早く実行したいという人は、Kotlin Playground(正式名称わかりません) がオススメです。

play.kotlinlang.org

www.jetbrains.com

Kotlin Playground は、オススメですがたまに全然動かなくなります(実行しすぎ?)。片手間に IntelliJ IDEA を入れるのをオススメします。

(?)がついている箇所は深く検証していなくて不明確な部分です。

以下、Kotlin 入門までの助走読本のメモです。

変数

val, var で宣言します。

  • val は変数の再代入が不可
  • var は変数の再代入が可
  • 型は省略可能
 val num = 1 // val num: Int = 1 と同じ
 num = 2 // コンパイルエラー
 var num2: Int = 2
 num2 = 4 // 可能

if 式

式としても文としても書けます。

val value = if (true) { 32 } else { 64 } // value = 32

when 式

switch のように書けます。

  • else がないとコンパイルエラーになる
  • 左辺(左側)でも使える
val str = when (1) {
  1 -> "one"
  2 -> "two"
  else -> "other"
} // str = "one"

for 文

for (i in 0..2) {
  print(i)
 } // 012

downTo を使うと逆順になります

for (i in 2 downTo 0) {
  print(i)
} // 210

step を使うとその値で飛ばします(downTo でも使えます)

for (i in 0..2 step 2) {
  print(i)
 } // 02
  • while, do-while もある

コレクション

  • reversed で逆順になる
  • reversedreversedArray は、 List で返すか、Array で返すかの違い(基本は reversed で返す)
  • ミュータブル読み取り専用がある。基本は読み取り専用
    • 読み取り専用であって、イミュータブルではない
  • 生成はファクトリ関数を使う
  • forEach を使う場合、it で要素にアクセスできる

以下が詳しいです。

qiita.com

null 許容型

型宣言で ? を使うことで null 許容型になります。デフォルトは null 非許容です

  • ?.エルビス演算子) を使うと null じゃないときに処理を行う。
    • このとき、渡す値として使うと null のときは、null が返る
val str: String? = null
val value2 = str?.length // null が渡る
println(value2) // null が出力される
  • 値を渡す場合、受け取る側も null 許容型でないといけない
    • null チェックが行われても型は変わらない(null 許容型のまま)
      • ただし、null チェックのブロック内では、null 非許容型として扱える
        • null の代入は可能

!! 演算子

  • null 許容型非許容型に強制変換するが、危険
  • requireNotNull 関数を検討すること

関数

fun double(a: Int): Int {
  return a * 2
} // double(2) == 4
  • 引数は val なので(?)、再代入が不可
fun double(a: Int): Int {
  a = 0 // コンパイルエラー
  return a * 2  
}

以下の書き方も可能です。

fun double(a: Int): Int = a * 2
  • 戻り値の型が定義されていない場合、Unit 型になる
  • 戻り値がある場合、型推論が可能なら省略可能
fun double(a: Int) = a * 2 // 戻り値の型は Int
fun double(a: Int) = a * 2.0 // 戻り値の型は Double
  • デフォルト値が書ける
fun double(a: Int = 0) = a * 2 // double() == 0
  • 引数名を指定すると順不同で書ける
fun multiple(a: Int, b: Int) = a * b
val c = multiple(b = 2, a = 4) // c == 8
  • 型パラメータを使える
fun<T> toString(a: T): String = "$a"

ラムダ式

val a = {a: Int -> a * 2}
println(a(2)) // 4

次のようにも書けます。

val a : (Int) -> Int = {a -> a * 2}
  • デフォルト引数は使えない(?)
  • ラムダ式をその場で実行できる
  • 引数なしの場合、-> の左側は何も書かない
println({ -> "Hello World!"}()) // Hello World!

高階関数

引数と戻り値の型に関数が指定できます。

fun execute(a: (Int)->Int): Int {
  return a(4)
}
val func = {a: Int -> a * 2}
println(execute(func)) // 8
fun execute(a: Int): (Int) -> Int {
  return {a: Int -> a * 4}
}
println(execute(2)(3))

上記の例はあまり意味がありませんが、例えばロック処理を行うようなときに使えるようです。

クラス

class MyClass {}
class MyClass // 同じ意味
val c = MyClass()
  • 明示的なコンストラクタの定義
class MyClass(v: Int) {}

上記の場合、vMyClass が持つプロパティにはなりません(コンストラクタ内でしか使えない)。

 class MyClass(v: Int) {
   val value = v
 } // v は、メンバの初期化にしか使えない
  • val, var をつけるとプロパティを定義できる
 class MyClass(val value: Int) {
   fun printValue() = println(value)
 }
  • init でイニシャライザを作れる
class MyClass(var value: Int) {
  init {
    value = 0
  }
  fun printValue() = println(value)
} // コンストラクタで数値を入れても init で初期化される
  • デフォルト引数が使える(関数と同じなので、コードは省略)
  • セカンダリコンストラクタを定義する場合、 constructor キーワードを使う
class MyClass() {
  constructor(v: Int) : this() {
    print("Secondary")
  }
} // MyClass(10) のようにするとセカンダリが呼ばれる
  • セカンダリではプロパティを定義できない
  • this は必須。プライマリコンストラクタを呼び出している

  • アノテーションや可視性修飾子をつける場合、constructor キーワードをつける(?)

    • 可視性はなくても付けられる(?)
  • メソッドは、クラス内部で関数のように定義する

class MyClass(val value: Int) {
  fun printValue() = println(value)
} // MyClass().printValue()

プロパティ

  • val で定義すると読み取り専用になる(getter)
  • var で定義すると読み取り書き込み可能になる(getter, setter)

次のように呼び出します。

class MyClass(v: Int = 10) {    
  val value: Int = v
 } // MyClass().value で呼び出せる
  • get, set は自分で作れる
class MyClass(v: Int = 10) {    
  var value: Int
  get() {
    println("Called GetValue")
    return 100
  }
  set(v: Int) {
    println("No Set")
  }
}
  • get, set 内でそのプロパティにアクセスすると無限ループするので注意
  • プロパティにアクセスしたい場合、(罰金)バッキングフィールド field を使う
  • get のアクセス修飾子は、プロパティと同じにしないといけない
  • set のアクセス修飾子は、異なる修飾子を指定できる
class MyClass(n: String = "") {
  private var name = n
    get() {
      return field
    }
    private set(value) {
      field = value
    }
}

可視性修飾子

public

  • デフォルト
  • どこからでもアクセス可能

internal

  • 同一モジュール内のみアクセス可能
  • コンパイル単位、Maven や Gradle のプロジェクト単位

protected

  • 同一クラス内とサブクラス内からアクセス可能

private

  • 同一クラスのみアクセス可能

オブジェクト(詳細は割愛)

  • class ではなく、object キーワードを使う。
  • シングルトンになる

コンパニオンオブジェクト(詳細は割愛)

  • Kotlin は static なメンバを定義できない
  • コンパニオンオブジェクトはその代わり

データクラス(詳細は割愛)

  • データを保持するためだけのクラス

継承

  • クラスはデフォルトでは継承できない
  • open 修飾子をつけると継承できるようになる
open class MyClass()
class ExMyClass(): MyClass()
  • 多重継承はできない
  • オーバライドする場合、元のメソッドをオーバライド可能にする必要がある(open 修飾子をつける)
  • オーバライドする側は、override をつける
open class MyClass() {
  open fun print() = println("MyClass")
}
class ExMyClass(): MyClass() {
  override fun print() = println("ExMyClass")
}

インタフェース

  • interface キーワードを使う
interface MyInterface {
  val value: Int
  fun print() = println("MyInterface")
} // プロパティも定義できる

open class MyClass(): MyInterface {
  override var value = 10
  override fun print() = println("MyClass")
}
  • プロパティも定義できるが、継承先で必ず初期化しないといけない
  • インタフェースをインタフェースに継承できる
interface ExMyInterface : MyInterface {
  override fun print() = println("ExMyInterface")
}
  • object: 式で匿名クラスを作れる
interface MyInterface {
  val value: Int
  fun print() = println("MyInterface")
  fun printValue() = println(value)
}

interface ExMyInterface : MyInterface {
  override fun print() = println("ExMyInterface")
}
    
object: ExMyInterface{
  override val value = 10
}.print() // ExMyInterface が出力される

パッケージ(詳細は割愛)

  • Java と同じ機能
package mypackage.myclass
class MyClass
  • import でアクセスできるようになる

アクセス制限

public

  • どこからでもアクセス可能
  • デフォルト

internal

  • 同一モジュール内でアクセス可能
  • コンパイル単位、Maven や Gradle のプロジェクト単位

private

  • 同一ファイル内のみ可能

その他

Angular のコンポーネントのテスト

f:id:pickles-ochazuke:20190812094957j:plain

マジカルミライ2019を見てきました。初めてのライブでもあったので新鮮な体験でした。

f:id:pickles-ochazuke:20190812094934j:plain

前回と被ってそうですが、Angular のコンポーネントのテストの入門的な話です。

コンポーネントのテスト

Angular のテストは以下のコマンドで行います

ng test

ng new コマンドでプロジェクトを作成すると app ディレクトリ の直下に app.component.html, app.component.spec.ts, app.component.ts などが作成されます。 今回、テストを行うために必要なファイルは先に挙げた3つなので、これだけを編集していきます。

自分の環境では、HTML ファイル(app.component.html)を pug ファイル(app.component.pug)にしていますが、今回は特に難しいことはしていないので、知らなくても問題ないと思っています(一応、 HTML に書き換えた内容も載せますが、表示の確認をしていないので間違いがあるかもしれません)

今回テストする HTML(app.component.pug) の内容は以下になります。

span#title {{title}}

span タグに title という ID がついており、コンポーネントtitle という変数をバインドしています。 HTML だと以下のようになります。

<span id="title"> {{title}} </span>

コンポーネント側(app.component.ts)は以下

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.pug',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'angular-try';
}

これをテストしていきます。

テストをするには、テストコードが必要ですが、Angular では、テストコードはコンポーネントを作ったときに一緒に作成されます(app.component.spec.ts)。 最初は以下のようになっています(流し見する程度でいいです)。

このテストを実行するといくつか失敗します。コンポーネント側と View 側を変更しているためです。

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title 'angular-try'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('angular-try');
  });

  it('should render title in a h1 tag', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('Welcome to angular-try!');
  });
});

Angular のテストは、 Jasminekarma で動いています。

コード内の describeitbeforeEach は、本題ではないので、ここでは詳しく説明しません(説明するほど知識がありません……)。

一応簡単にいうと、 describe 内にある it 関数が 1 単位のテストで、 beforeEachit が呼ばれる前に行う初期化処理です(自動で呼ばれます)。

TestBed

beforeEach では、以下のような処理が呼ばれています。

TestBed.configureTestingModule({
  declarations: [
    AppComponent
  ],
}).compileComponents();

TestBed は、 @NgModuleエミュレータです。そのコンポーネントを作成するために必要なモジュールやプロパティを用意して、 configureTestingModule で読み込ませてからコンポーネントを作る必要があります。 compileComponents コンパイルをする処理ですが、CLI で実行している場合、コンパイルはしてくれているので通常必要ありません。

今回は特別な機能を使っていないのでデフォルトのままでいいです。

TestBed は Angular のテストの中でも最も重要な機能です。ここでは、全部紹介しきれませんが、一度調べてみることをオススメします。

AppComponent の作成

次は、テストを一つずつ見ていきます。 以下は、 AppComponent が正しく作成できているか確認しているテストです。

it('should create the app', () => {
  const fixture = TestBed.createComponent(AppComponent);
  const app = fixture.debugElement.componentInstance;
  expect(app).toBeTruthy();
});

TestBed.createComponent(AppComponent) では、 beforeEach 内で設定された TestBedAppComponentインスタンスを作成しています。作成したコンポーネントインスタンスをそのまま返すわけではなく、 ComponentFixture という型のインスタンスを返します。コンポーネントには、この fixture を介して操作を行います。

デフォルトでは、 fixture.debugElement.componentInstanceコンポーネントにアクセスしていますが、 fixture からコンポーネントインスタンスが取得できるため、 debugElement はなくても問題ありません。

AppComponent のテスト

2 つ目のテストを見てみると、コンポーネントにアクセスしている様子がわかります。

it(`should have as title 'angular-try'`, () => {
  const fixture = TestBed.createComponent(AppComponent);
  const app = fixture.debugElement.componentInstance;
  expect(app.title).toEqual('angular-try');
});

appコンポーネントインスタンスです。コンポーネントが持つ変数が期待通りの値になっているか確認しています。

View 側のテスト

3 つ目のテストでは、コンポーネントと紐付いている HTML 要素のテストをしています。また、このテストは失敗していると思います。

it('should render title in a h1 tag', () => {
  const fixture = TestBed.createComponent(AppComponent);
  fixture.detectChanges();
  const compiled = fixture.debugElement.nativeElement;
  expect(compiled.querySelector('h1').textContent).toContain('Welcome to angular-try!');
});

View 側をテストしたい場合は、querySelector を使ってエレメントを取り出し、期待の結果になっているか確認ができます。

実際の Angular では、変更を検知して結果を View に反映してくれますが、テストの場合は手動で変更を伝える必要があります。それが fixture.detectChanges() です。

これを実行しない場合、バインドされている部分が変化せずテストが失敗します。

テストを通す

では、最後のテストが失敗していると思いますので、このテストを通していきます。

まず、テストを確認します。

it('should render title in a h1 tag', () => {
  const fixture = TestBed.createComponent(AppComponent);
  fixture.detectChanges();
  const compiled = fixture.debugElement.nativeElement;
  expect(compiled.querySelector('h1').textContent).toContain('Welcome to angular-try!');
});

コンポーネントを作成するところは、別のテストで問題なく通っているので、最後のテストしている部分を見ます。

現在の HTML は以下のようになっています。

span#title {{title}}

h1 のタグは存在せず、 span タグになっています。また、表示する文字列は、title 変数の内容だけなので、 angular-try になるはずです。テストを以下のように書き換えます。

expect(compiled.querySelector('span').textContent).toContain('angular-try');

これはテストが通るはずです。最後に、テストの名称を変更します。変更したテストは以下のようになります。

it('should render title in a span tag', () => {
  const fixture = TestBed.createComponent(AppComponent);
  fixture.detectChanges();
  const compiled = fixture.debugElement.nativeElement;
  expect(compiled.querySelector('span').textContent).toContain('angular-try');
});

まとめ

以上、コンポーネントのテストを簡単に説明しました。Angular では、コンポーネントのテストを行うときは、以下のようになると思います。

  1. テスト対象のコンポーネントに必要なデータ(メタデータ)を TestBed に設定する
  2. 設定した TestBedコンポーネントを作成し、fixture を取得する。
  3. fixture を介して、コンポーネント、 View にアクセスし、期待する結果になっているか確認する。

上記を行うために主に必要な機能は以下です。

  • TestBed.configureTestingModule(): TestBed を設定する
  • TestBed.createComponent(AppComponent): 指定されたコンポーネントクラスのインスタンスを作成する
  • ComponentFixture\.detectChanges(): バインドしている部分を View 側に反映させる
  • fixture.debugElement.nativeElement: View の要素にアクセスし、期待する値になっているか確認する

今回 HTML 側を変更するとテスト側も変更する必要が出ました。これでは、気軽に HTML 側を変更するのが難しくなってしまいます。これを解決するには、PageObject というパターンを使うことである程度解決することが出来ます。

次は、今回のテストをもう少し変更に強いテストに変えていきます。

Angular でテストを書く(サービス編)

Mac に慣れません。身体が Windows でできているので Mac を受け付けないのかもしれません。 最近、五等分の花嫁にハマっておりスマホの壁紙やキーボードの見た目を五等分の花嫁仕様にして楽しんでいます。Kindle で買った後に物理的に欲しくなったので全巻買いに行ったら8巻がありませんでした(そのとき9巻まだ出てなかった)。

f:id:pickles-ochazuke:20190421223158j:plain

全員可愛くて好きですがあえて一番を上げるなら三玖です。そろそろ本題に入ります(9巻の表紙ヤバいですね!)。

はじめ

Angular は標準でテストを行う機能があるので、簡単に実行することができます。

テストは、 ng test で行うことができます。

プロジェクトの作成

ng new angular-test で作成しました。

router はなし、CSSは標準のCSSを選択しました。今回はどっちも関係ないのでたぶんどっちでもいいです。

ng test でテストが可能なことを確認します。

とりあえず、放り投げた値を計算して画面に表示するようにします。

ng serve --open

html を以下のように変更します。

<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
</div>
<div>
  5 + 4 = 9
</div>

これから、このコンポーネントで足し算をする画面を作成して、そのテストを書くことを目指します。

テストの作成(コンポーネント

実は初めからテストコードが用意されています。 app.component.spec.ts がそれです。テストファイルは、.spec.ts という拡張子(?)です。

中身を見ると以下のようなコードが書かれています。

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title 'angular-test'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('angular-test');
  });

  it('should render title in a h1 tag', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('Welcome to angular-test!');
  });
});

初めて目にすると「う゛っ……」ってなるかもしれません。自分はなりました。

今回の話では見る必要はないので、読み飛ばしても大丈夫ですが、気になる人は、以下を一読すると参考になるかも。

少し目を慣らすために、まずは大きく分類していきます。 自分は、このテストコードは、大きな項目とその中にある小さな項目の集まりだと思っています。 大きな項目は、describe で始まる関数で、小さな項目は、it で始まる関数です。 どちらも一つ目の引数に文字列が渡され、二つ目の引数に無名関数が渡されています。

文字列は、テストを実行した時に、実行者がなんのテストを行って、どのテストが失敗したか、成功したのかを簡単に判別するための文字です。要は何をテストしているのかを説明するような文章を書くといいでしょう。当然日本語が書けるし、書くべきです。周りの母国語が英語なら英語にするべきだろうけど。

describe は小さなテストを集めた親みたいなもので、テスト自体はその中にある it がテスト内容になります。 it の中では、大抵以下の順番でテストを書きます。

  1. テストの前準備
  2. テスト対象の処理を実行
  3. 処理結果が期待する結果になっているか判定

といった感じです。 ここで、テストを書いていると各 it で共通の初期化処理があることに気づいたりします。そういった共通処理は、beforeEach に書きます。beforeEach は、it を実行する時に最初に勝手に呼ばれます。

期待値の比較は、いろいろメソッドが存在しますが、基本は、expect(比較対象).比較方法(期待値) のように書きます。 上のコードを見れば何となくわかるかもしれないですが。

初めから用意されているテストコードは、Angular 固有のコンポーネントクラス(呼び方あってる?)に対してテストを行うテストになっています。実は、コンポーネントのテストは面倒くさいし難しいです(設計に問題があったのだろうか……)。まずはサービスで慣れていくのがいいと思います。

サービスとそのテストの作成

サービスでテストコードを書く前に、画面に変更を加えて渡した値を足し算してくれるようにしたいと思います。

<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">

<div>
  {{right_operand}} + {{left_operand}} = {{result}}
</div>
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = 'angular-test';
  right_operand = 10;
  left_operand = 20;
  result = this.right_operand + this.left_operand;
}

コンポーネントに存在する right_operand, left_operand, result をビューにバインドして表示しているだけです。 これぐらいなら既存のテストコードに追加するだけでいいんですが、厄介なのが画面のために追加した機能がある場合。 例えば、ダイアログを追加したり、他のコンポーネントを追加したりすると、テストの準備だけで大変になったりします。

ロジック部分だけテストしたいのに、一切使わないものを作らないといけないのは煩わしいので、足し算のようなロジックや、保持しておくデータは積極的にサービスに追いやります。

ng generate service app でサービスを作成します。

以下のようなコードが作成されます。

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AppService {

  constructor() { }
}

テストも作られているので、そちらも見てみます。

import { TestBed } from '@angular/core/testing';

import { AppService } from './app.service';

describe('AppService', () => {
  beforeEach(() => TestBed.configureTestingModule({}));

  it('should be created', () => {
    const service: AppService = TestBed.get(AppService);
    expect(service).toBeTruthy();
  });
});

短くてまだ理解しやすそうです。コンポーネントのテストが面倒くさいというのが少し理解できましたでしょうか。 以下のようなテストを追加します。

  it('1 + 1 の結果は 2 であるべき', () => {
    const service: AppService = TestBed.get(AppService);
    expect(1+1).toEqual(5000);
  });

追加されて、成功しましたか? いまいち成功したかわからなければ、失敗を書いてみましょう。 例えば、最後をexpect(1+1).toEqual(5000); のようにします。 失敗すると、赤いバーが表示され、5 specs, 1 failure と表示されます。

動作確認ができたので、実際にサービスに組み込んでテストをします。 まずはテストを変更しましょう。サービスには、足し算する対象を二つ保持し、それらを足し合わせた結果を持ちたいサービスだとします。以下のようなテストを考えました。

  it('right_operand に値を渡すと渡した値を持つ', () => {
    const service: AppService = TestBed.get(AppService);

    service.right_operand = 10;
    
    expect(service.right_operand).toEqual(10);
  });

残念ながら、テストは失敗しません(コンパイルエラーは起こりますが)。エラーを起こさないようにサービス側に変数を追加します。変数は private にしましょう。

export class AppService {

  private rightOperand: number = 0;

  constructor() { }
}

まだエラーが出ます。private なので アクセサ(setter) を作ってやる必要があります。以下のようになりました。

export class AppService {
  private rightOperand: number = 0;

  constructor() { }

  public set rightOperand(v : number) {
    this._rightOperand = v;
  }
}

まだエラーが出ます。Expected undefined to equal 10. どうやら戻り値が未定義になっていて、undefined10 を比較しているようです。 今度は、getter を作ってやりましょう。

export class AppService {
  private _rightOperand: number = 0;

  constructor() { }

  public set rightOperand(v : number) {
    this._rightOperand = v;
  }
  
  public get rightOperand() : number {
    return this._rightOperand;
  }
}

テストが成功しました! leftOperand にも同じ実装を施して、最後に足し算をテストします。 以下のようなテストを作りました。

  it('3 + 4 = 7 であるべき', () => {
    const service: AppService = TestBed.get(AppService);

    service.leftOperand = 3;
    service.rightOperand = 4;

    expect(service.addOperand()).toEqual(7);
  });

とても簡単なのでサービス側の実装は、載せません。

最後に、コンポーネント側のオペランドたちをサービスに置き換えます。

export class AppComponent {
  title = 'angular-test';

  constructor(private appService: AppService) {
    appService.rightOperand = 30;
    appService.leftOperand = 20;
  }
}
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
</div>

<div>
  {{appService.rightOperand}} + {{appService.leftOperand}} = {{appService.addOperand()}}
</div>

これで Angular のテストを行える知恵を身につけることができました!

参考

angular.jp