株式会社クリアコード > ククログ

ククログ


Rubyのテスティングフレームワークの歴史(2014年版)

2014年12月にRuby 2.2がリリースされる予定です*1

Ruby 2.2にはRuby 1.9.1のときに外されたtest-unitというテスティングフレームワークが再びバンドルされる予定です。Rubyのテスティングフレームワーク周りに詳しくない人にはよくわからない状況でしょう。そこで、Rubyのテスティングフレームワークの歴史を説明することで状況を整理します。

名称の整理

この説明の中ではたくさんのテスティングフレームワークが登場します。似たようなものもあるため、最初にテスティングフレームワークの名称を整理します。この説明の中で登場する名称は次の通りです。

  • RubyUnit
  • Lapidary
  • rubyunit
  • Test::Unit
  • test/unit
  • test-unit
  • miniunit
  • minitest
  • RSpec

違いがわかりますか?ざっくり説明すると次の通りです。

  • RubyUnit
    • Lapidaryと同じくらいの時期に作られたRuby用のテスティングフレームワーク。 たぶん一番最初に作られたRuby用のテスティングフレームワーク。
  • Lapidary
    • たぶん一番最初に作られたRuby用のテスティングフレームワーク。 RubyUnitと同じくらいの時期に作られたRuby用のテスティングフレームワーク。
  • rubyunit
    • Test::Unitが提供するRubyUnit互換API。
  • Test::Unit
    • LapidaryとRubyUnitを統合したテスティングフレームワーク。Ruby本体に入っていた時期がある。
  • test/unit
    • Ruby本体に入っているtest/unit/以下のファイル。時期によって示すものが違う。(後述)
  • test-unit
    • Ruby本体に入ったTest::Unitが本体から分離され、gemとして開発が続いているテスティングフレームワーク。
  • miniunit
    • minitestの前身のテスティングフレームワーク。
  • minitest
    • Test::Unitに変わってRuby本体に入ったテスティングフレームワーク。
  • RSpec
    • Ruby本体に入ったことがないテスティングフレームワーク。

それでは、歴史を振り返りながらRubyのテスティングフレームワークを整理しましょう。

Ruby 1.6の時代

Ruby 1.6の頃はRubyはテスティングフレームワークをバンドルしていませんでした。

テスティングフレームワークが広まるきっかけになったのはエクストリーム・プログラミング(XP)です。最初のXPの本が出版されたのが1999年です。日本語版は2000年の12月です。この本の影響*2でSmalltalkやJava以外でのテスティングフレームワークの実装がでてきました。もちろん、Ruby用の実装もでてきました。

RubyUnitのサイトを見ると、最初に公開された*3のが2001年9月2000年11月です。ちなみに、RubyUnitの作者はRubyにバンドルされているWin32OLEの作者で、日本の人です。

同じく、Lapidaryのサイトを見ると、最初のバージョンがリリースされたのが2001年3月です。ちなみに、Lapidaryの作者は後述するTest::Unitの作者で、海外の人です。

当時、walkitというテスティングフレームワークもあったようですが、インターネット上から情報を見つけることはできませんでした。

Ruby用のテスティングフレームワークがでてくると、Ruby本体にバンドルしようという話が進みます。

日本Rubyの会の高橋代表理事の2001年10月の日記によると、Lapidaryの作者がLapidaryとRubyUnitを統合したTest::Unitを作って、それをRuby本体にバンドルすることになったそうです。ただし、Ruby 1.6の間はRuby本体にバンドルされませんでした。

ここまでのまとめです。

  • RubyUnitが作られた
  • しばらくしたらLapidaryも作られた
  • RubyUnitとLapidaryを統合してTest::Unitになった

Ruby 1.8の時代

2003年8月にリリースされたRuby 1.8.0にTest::Unitがバンドルされました。なお、既存のRubyUnitユーザーのことも配慮して、Test::UnitはRubyUnit互換のAPIを提供していました。これがrubyunitです。ちなみに、rubyunitという表記はるりま以外では見たことはありません。

Ruby 1.8にはTest::Unitがバンドルされていました。requireするときはrequire "test/unit"と書くことからtest/unitとも呼ばれるようになりました。

ポイント:Ruby 1.8の頃にtest/unitと言ったらTest::Unitのこと。

さて、Ruby 1.8にバンドルされたTest::Unitですが、2003年の間はそこそこ活発に開発が続いていました。しかし、それ以降はほとんど改良はありませんでした。この状況はRuby 1.9.1がリリースされる2009年1月まで変わりませんでした。

そんなTest::Unitがのんびり暮らしていた2005年にRSpecの最初のバージョンがリリースされました。RSpecの開発は活発で便利な機能が追加されていきます。RSpecの記法がよいと思う人と記法に抵抗がない人は、便利なのでTest::UnitではなくRSpecを使うようになっていきました。

Test::Unitの開発が停滞していることに不満を感じている人が2人いました。後のminitestの作者と、後のtest-unitの開発者です。

minitestの作者はTest::Unitのメンテナーになりました。メンテナーになったminitestの作者は、Test::Unitは複雑すぎて自分はメンテナンスできないと主張しました。

ここまでのまとめです。

  • Test::UnitはRuby 1.8.0にバンドルされた(2003年8月)
  • Test::UnitはRubyUnit互換API(rubyunit)を提供していた
  • RubyにバンドルされてからTest::Unitの開発は停滞していた
  • RSpecが現れた
  • Test::Unitのメンテナーが、後のminitestの作者に変わった
  • 後のminitestの作者は複雑すぎてTest::Unitをメンテナスできないと主張した

Ruby 1.9の時代

2007年12月にリリースされたRuby 1.9.0で、Test::Unitが提供しているRubyUnit互換APIは削除されました。

2009年1月にリリースされたRuby 1.9.1ではTest::Unit自体もRuby本体から外れました。何があったのでしょうか。

後のminitestの作者は複雑すぎてTest::Unitをメンテナンスできないため、もっと小さなテスティングフレームワークにしなければいけないと考えました。その考えのもと作ったのがminiunitです。miniunitはRuby 1.9.0に入りそうになりましたが、後のtest-unitの開発者が反対したためRuby 1.9.0には入らずRuby 1.9.1でRuby本体に入りました。この間にminiunitからminitestに名前が変わっています。

minitestがRuby本体にバンドルされたタイミングでTest::UnitはRuby本体から外れました。Ruby本体から外れたTest::Unitはtest-unitというgemになります。test-unitというgemはこれをベースに今でも改良を続けています。

Test::UnitはRuby本体から外れましたが、Test::Unit互換APIは残りました。なぜならRuby本体のテストはTest::UnitのAPIで書かれていたからです。テスティングフレームワークが変わったからといって既存のテストを書き直さないといけないのは受け入れられなかったのです。

このminitestの上に実装されたTest::Unit互換APIをtest/unitと呼んでいます。

ポイント:Ruby 1.9の頃にtest/unitと言ったらminitestの上に実装されたTest::Unit互換APIのこと。Test::Unitのことではない。

ここまでのまとめです。

  • 後のminitestの作者はメンテナンスできる小さなテスティングフレームワークとしてminiunitを作り始めた
  • miniunitはminitestに名前を変えた
  • Test::UnitはRuby 1.9.0までバンドルされていた(2007年12月)
  • Test::UnitはRuby 1.9.1で外れた(2009年1月)
  • Ruby 1.9.1でminitestがバンドルされた
  • Ruby 1.9.1でminitestの上にTest::Unit互換APIを実装した(test/unit)
  • Test::Unitはtest-unit gemとして開発を継続している

Ruby 2.0時代

2013年2月にRuby 2.0.0がリリースされました。テスティングフレームワークについて特筆すべきことはありません。minitestもtest-unitもRSpecもどれも停滞することなく開発が続いています。

Ruby 2.1時代

2013年12月にRuby 2.1.0がリリースされました。2014年11月現在の最新の安定版です。

test/unit(minitestの上に実装したTest::Unit互換API)に陰りが見えてきました。minitestがminitest 5.0.0で後方互換性のないAPIの変更を導入したのです。

この非互換の変更をうけてtest/unitはメンテナンスできなくなりました。Ruby開発チームとしては既存のテストを動かすためにtest/unitは必要です。しかし、最新のminitestでは動きません。つまり、Ruby本体に最新版のminitestをバンドルできなくなったということです。

この状況に対応するため、Ruby本体のテストはminitest 4とその上に実装されたtest/unitで動かすことにしました。ただし、このminitest 4とtest/unitはRubyユーザー向けのものではなく、Ruby開発チームだけが使うものとしました。minitestとtest/unitはRubyのソースの中ではlib/minitest/とlib/test/unit*に置かれていましたが、それをtest/lib/以下に移動しました。

移動前:

lib/minitest/*.rb
lib/test/unit.rb
lib/test/unit/**/*.rb

移動後:

test/lib/minitest/*.rb
test/lib/test/unit.rb
test/lib/test/unit/**/*.rb

これで、最新版のminitestをバンドルしてもRuby本体のテストはそのまま動き続けるようになりました。

ポイント:Ruby開発チームの中でtest/unitというとtest/lib/以下にあるtest/unitのことを指す。

ここまでのまとめです。

  • minitestはバージョン5.0.0で後方互換性のないAPI変更を入れた
  • test/unitはminitest 5.0.0をサポートできなくなった
  • Ruby本体はminitest 4.7.5とtest/unitをtest/lib/以下に移動してRuby本体のテストではそれら古いバージョンを使うことにした
    • test/lib/以下にコピーしたminitestとtest/unitをRuby開発チームがメンテナンスしていく
    • Ruby開発チームがtest/unitと言ったらtest/lib/以下にあるtest/unitのこと

Ruby 2.2時代

Ruby本体のテスト用のminitestとtest/unitをtest/lib/以下に動かすことにより最新のminitestをバンドルできるようになりました。

しかし、このままRuby 2.2.0をリリースするとTest::Unit互換APIがなくなってしまいます。さらに、minitestはAPIの互換性がなくなっているので既存のテストはそのままでは動きません。つまり、既存のRubyが提供しているminitestまたはtest/unitを使っているユーザーはRuby 2.2.0にアップグレードするとテストが動かなくなるということです。

それだとあんまりだということで、Test::Unit互換APIを提供するためにRuby 2.2.0にはminitestだけでなくtest-unit(gemとなって開発が続いていたTest::Unit)がバンドルされることになりました。

なお、Ruby開発チームにはRSpecをバンドルするという選択肢はありませんでした。

ポイント:Rubyユーザーがtest/unitというとtest-unitのことを指すことになるかもしれない。

ここまでのまとめです。

  • Ruby 2.2.0には最新のminitestがバンドルされる予定
  • Ruby 2.2.0には最新のtest-unitもバンドルされる予定
    • Rubyユーザーがtest/unitと言ったらtest-unitのことになるかもしれない

まとめ

Ruby 2.2.0にどうしてtest-unitが再バンドルされるのかを、Rubyのテスティングフレームワークの歴史をたどりながら説明しました。10年以上前のサイトが今でもアクセスできると昔のことを確認するときにとても便利ですね。

Ruby 1.9.1でTest::UnitがRuby本体から外れた後、Test::Unitはtest-unit gemとして開発が続いてきました。Ruby 2.2.0で再バンドルされるのを機にtest-unitも触ってみてはいかがでしょうか?Test::Unitの頃しか知らない人はかなり便利になっていることに驚くでしょう。

最近のtest-unit関連情報:

*1 たぶん。

*2 たぶん。

*3 たぶん。

タグ: Ruby
2014-11-06

Groonga 4.0.7の実験的機能であるカラム値の圧縮とRroongaからそれを使うには

はじめに

オープンソースのカラムストア機能付き全文検索エンジンとしてGroongaがあります。 この記事を書いている時点の最新版であるGroonga 4.0.7では、カラムの値を圧縮して保存することができるようになりました。

今回は、そのカラム圧縮がどれほど有効なのか、また、Groongaの機能をRubyから利用するためのライブラリであるRroongaから使うやりかたを紹介します。

カラム圧縮機能が嬉しいケースとは

Groonga 4.0.7ではzlibもしくは、lz4による圧縮を実験的にサポートしています。 どんなケースだと嬉しいのでしょうか。

  • カラムのデータがそれなりに大きい

数バイトや数10バイト程度では、圧縮する方がサイズが大きくなる可能性があります。また、圧縮・展開のオーバーヘッドを無視できない可能性があります。 しかし、カラムに格納しているデータがそれなりに大きいのであればディスク容量を節約できます。

  • 主な用途が全文検索のみである

圧縮したカラムでもソートやドリルダウンできますが、データコピーが発生するので非圧縮の場合よりも遅くなります。 そのため、Wikipediaの本文データなど、ソートやドリルダウンの対象としないようなデータを格納しているカラムを圧縮するのが効果的です。

Groongaでカラム圧縮機能を試してみる

では、実際に試してみましょう。 サンプルデータとしては、Wikipediaのデータを使ってみます。

ストリーム指向の処理モデルを採用した分散全文検索エンジンであるDroongaのベンチマーク手順を利用するとデータの準備が簡単なので今回はそれを使います。

wikipedia-searchはDroongaを使ってWikipediaを検索するためのサンプルアプリケーションです。

サンプルデータを用意する

あらかじめ、Groongaをインストールしてあるものとします。

次に、wikipedia-search ベンチマーク取得手順にあるようにwikipedia-searchのリポジトリデータを取得して、データの準備をしておきます。

% git clone https://github.com/droonga/wikipedia-search.git
% cd wikipedia-search
% bundle install
% MAX_N_RECORDS=1000000 rake data:convert:groonga:ja

rake data:convert:groonga:jaを実行すると、Wikipediaの最新のアーカイブデータ(jawiki-latest-pages-articles.xml.bz2)をダウンロードしはじめます。1.88GiBあるのでダウンロードが完了するまでしばらく待ちます。 ダウンロード後にGroongaにデータを投入するためのデータファイル(data/groonga/ja-pages.grn)が生成されます。

デフォルトだと5000件の.grnファイルを生成しますが、データ件数をMAX_N_RECORDSで指定して、100万件のデータを生成します。

非圧縮のGroongaのデータベースを構築する

次のようにして、従来のカラム圧縮を利用しない非圧縮のデータベースを構築します。 スキーマ定義は、config/groonga以下にschema.grnがあります。インデックスの定義も同様にindexes.grnがあるのでそれを使います。

% groonga -n testdb-normal/db quit
% cat config/groonga/schema.grn | groonga testdb-normal/db
% cat config/groonga/indexes.grn | groonga testdb-normal/db
% cat data/groonga/ja-pages.grn | groonga testdb-normal/db
カラム圧縮(zlib)のGroongaのデータベースを構築する

今度はカラム圧縮(zlib)のデータベースを構築します。 カラム圧縮(zlib)を適用するには、schema.grnを一行修正します。

column_create Pages text COLUMN_SCALAR|COMPRESS_ZLIB Text

あとは、非圧縮の場合と同じようにしてデータベースを構築します。

% groonga -n testdb-zlib/db quit
% cat config/groonga/schema-zlib.grn | groonga testdb-zlib/db
% cat config/groonga/indexes.grn | groonga testdb-zlib/db
% cat data/groonga/ja-pages.grn | groonga testdb-zlib/db
カラム圧縮(lz4)のGroongaのデータベースを構築する

今度はカラム圧縮(lz4)のデータベースを構築します。 カラム圧縮(lz4)を適用するには、schema.grnを一行修正します。

column_create Pages text COLUMN_SCALAR|COMPRESS_LZ4 Text

あとは、非圧縮の場合と同じようにしてデータベースを構築します。

% groonga -n testdb-lz4/db quit
% cat config/groonga/schema-lz4.grn | groonga testdb-lz4/db
% cat config/groonga/indexes.grn | groonga testdb-lz4/db
% cat data/groonga/ja-pages.grn | groonga testdb-lz4/db
データベースのサイズを比較してみる

ここまでで、非圧縮、カラム圧縮(zlib)、カラム圧縮(lz4)それぞれでデータベースを作成しました。それぞれのデータベースが占めるディスク容量(GiB)をグラフにすると以下のようになりました。

画像の説明

カラム圧縮(zlib)が約2.3GiB、カラム圧縮(lz4)が約2.5GiB、非圧縮で約3GiBという結果です。 ここではベンチマークについては割愛しますが、まずはカラム圧縮(lz4)を試してみることをおすすめします。もしもっとディスク容量を節約したいならカラム圧縮(zlib)を使ってみてください。

Rroongaでカラム圧縮機能を試してみる

カラム圧縮の有用性がわかったところで、今度はそれをRroongaでもつかってみることにしましょう。 RroongaでGroonga 4.0.7のカラム圧縮機能を利用するにはRroonga 4.0.6が必要です。 Rroongaはgemとして提供されているので、以下のようにして簡単にインストールすることができます。

% gem install rroonga

Rroongaで検索するだけなら、先ほどのデータベースをそのまま使えます。では、Rroongaを使って同じようなスキーマ定義を実現するにはどのようにすればよいのでしょうか。 Groongaの場合と見比べてみましょう。

schema.grnの内容は次のとおりでした。

table_create Categories TABLE_HASH_KEY ShortText
table_create Pages TABLE_HASH_KEY UInt64
column_create Pages title COLUMN_SCALAR ShortText
column_create Pages text COLUMN_SCALAR|COMPRESS_LZ4 Text
column_create Pages categories COLUMN_VECTOR Categories

これをRroongaでは次のようにして定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
require "groonga"

Groonga::Schema.define do |schema|
  schema.create_table("Categories", :type => :hash) do |table|
  end
  schema.create_table("Pages",
                      :type => :hash,
                      :key_type => "UInt64") do |table|
    table.short_text("title")
    table.text("text", :compress => :lz4)
    table.reference("categories", "Categories", :type => :vector)
  end
end

注目するポイントは次の箇所です。

1
table.text("text", :compress => :lz4)
  • table.textはtext型のカラムを定義することを意味します。
  • "text"はカラム名が"text"であることを意味します。
  • :compressで圧縮方法を指定します。この場合はLZ4を使うことを意味します。zlibなら:zlibを指定します。デフォルトは非圧縮です。

カラム圧縮を利用するのとは直接関係ないのですが、インデックスについてはどうでしょうか。 indexes.grnの内容は次のとおりでした。

column_create Categories pages_categories COLUMN_INDEX Pages categories

table_create Terms TABLE_PAT_KEY ShortText \
  --default_tokenizer TokenBigram \
  --normalizer NormalizerAuto
column_create Terms pages COLUMN_INDEX|WITH_SECTION|WITH_POSITION \
  Pages title,text

これをRroongaでは次のようにして定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require "groonga"
Groonga::Schema.define do |schema|
  schema.change_table("Categories") do |table|
    table.index("Pages.categories")
  end

  schema.create_table("Terms",
                      :type => :patricia_trie,
                      :default_tokenizer => :bigram,
                      :normalizer => "NormalizerAuto") do |table|
    table.index("Pages", "title", "text",
                :with_section => true,
                :with_position => true)
  end
end

Rubyらしい書き方で定義できることがわかりますね。

まとめ

今回は、Groonga 4.0.7から導入されたカラム圧縮機能とGroongaをRubyから利用するライブラリであるRroongaでのスキーマ定義について紹介しました。

来週末の11月29日(いい肉の日)には、全文検索エンジンGroongaを囲む夕べ5というGroongaとその関連プロダクトに関するイベントがあります。 Groongaと関連プロダクトやツールの最新動向を知るのにおススメです。

また、このイベントの休憩時間には検索エンジン自作入門の著者である山田さん、末永さんによるサイン会も開催します。 ぜひ、来週末の全文検索エンジンGroongaを囲む夕べ5に参加してみませんか?

2014-11-18

Ubuntuでdebパッケージのお手軽クリーンルーム(chroot)ビルド環境を構築するには

はじめに

debパッケージを用意してDebianの公式リポジトリからインストールできるようにするために必要な作業については以前いくつか記事を書きました。

ただし、そこではパッケージのビルドそのものについては触れていませんでした。 普段Ubuntuを使っていて、sid(unstable)でのビルド環境をどうしようかというのはいくつか選択肢があります。定番のchroot環境を用意するとか、LXCのコンテナを使うとか、Vagrantを使うとか、はたまたdockerのイメージを利用するなどさまざまです。

今回はそのうちの1つとして、Ubuntuでdebパッケージのお手軽クリーンルーム(chroot)ビルド環境を構築するのに便利なcowbuilderを紹介します。

Cowbuilderとは

必要なパッケージが一式揃ったクリーンな環境でdebパッケージをビルドするためのツールです。 同様の目的のものとしてはpbuilderが有名ですが、後発だけあって

  • pbuilderとは違ってベースイメージの展開がないので速い

というのが特徴です。

設定ファイルを書く

cowbuilderはpbuilderのラッパーなので、設定ファイルは.pbuilderrcと共通です。 パッケージに同梱されているpbuilderrc(/usr/share/doc/pbuilder/examples/pbuilderrc)がありますが、そちらは複数のベースイメージをとりあつかうのには都合がよくない*1ので、PbuilderTricksのドキュメントの"How to build for different distributions"のセクションから設定*2をコピーして$HOME/.pbuilderrcとして保存します。

ただし次の2つについては不足しているので追加します。

  • BASEPATH
  • HOOKDIR

BASEPATHはベースイメージを作成するパスを指定するのに必要です。

BASEPATH="/var/cache/pbuilder/$NAME-base.cow/"

この設定を追加すると、sid(unstable)の場合だとunstable-amd64-base.cowというようにベースイメージが作成されます。

HOOKDIRは後述するフックを置くためのパスを指定するのに必要です。

HOOKDIR="/var/cache/pbuilder/hooks"
フックスクリプトを配置する

パッケージをビルドするだけでなく、作成したパッケージに問題がないか同時に確認しておきたいというのは当然ですね。それを実現するにはフックスクリプトを使います。

先程のexamplesディレクトリにlintianを実行するためのフックスクリプトがあるので、それをHOOKDIRにコピーしましょう。

% sudo cp /usr/share/doc/pbuilder/examples/B90lintian /var/cache/pbuilder/hooks

examplesディレクトリにはほかにもいくつかフックスクリプトがあるので興味があれば覗いてみるとよいでしょう。

ベースイメージを作成する

さて、設定ファイルが用意できたので、実際にsid(unstable)のベースイメージを作成してみましょう。 ベースイメージを作成するのには次のコマンドを実行します。

% sudo DIST=sid cowbuilder --create --debootstrapopts --keyring=/usr/share/keyrings/debian-archive-keyring.gpg

パッケージのダウンロードとインストールがはじまるので、完了するまでしばらく待ちましょう。

Ubuntu上でDebian wheezy/jessie/sidのベースイメージを作成する際にエラーがでるようならkeyringをインストールし忘れていないか確認してください。 keyringパッケージは次のコマンドを実行することでインストールできます。

% sudo apt-get install debian-archive-keyring
ベースイメージを更新する

ベースイメージを作ったばかりのときは良いのですが、sid(unstable)は日々更新されています。 ベースイメージも最新の状態に追従するには次のコマンドを実行します。

% sudo DIST=sid cowbuilder --update
パッケージをビルドする

ベースイメージができたので、実際にパッケージをビルドしてみましょう。

sid(unstable)向けにGroongaのパッケージをcowbuilderでビルドするには次のコマンドを実行します。

% sudo DIST=sid cowbuilder --build groonga_4.0.7-1.dsc --basepath /var/cache/pbuilder/unstable-amd64-base.cow

これで、クリーンルームでのパッケージのビルドとlintianによるチェックが実行されます。 ビルドされたパッケージは/var/cache/pbuilder/unstable-amd64/result以下に保存されます。

まとめ

今回はUbuntuでdebパッケージのお手軽クリーンルーム(chroot)ビルド環境を構築するのに便利なcowbuilderの紹介をしました。 パッケージをビルドしたあとのインストールやアップグレードについては、また次の機会に紹介します。

*1 例えばsidだけじゃなくてwheezyとかtrustyとかでもパッケージをビルドしたいとかには向いていない。

*2 wheezy,jessie,sid以外にも、hardy,lucid,natty,oneiric,precise,quantal,raring,saucy,trusty,utopicまでサポートしているらしい。すべて試したわけではないので実際にどうかはわからない。

2014-11-21

«前月 最新記事 翌月»
タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|