Swift の Mirror API を触って、動作を XCTest で確認した

今日やってた勉強内容。

github.com

動かし方

$ git clone https://github.com/sinsoku/study.git
$ cd study/swift
$ bin/run swift build -C PlayGround
$ bin/run swift test -C PlayGround

ソースコード

// PlayGround/Sources/PlayGround/SimpleClass.swift
class BaseClass {
  let number: Int = 0
  let string: String? = nil
}

class SimpleClass: BaseClass {
}

テストコード

// PlayGround/Tests/PlayGround/SimpleClassTests.swift
import XCTest
@testable import PlayGround

class SimpleClassTests: XCTestCase {
    func testSimpleClassBehavior() {
        let obj = SimpleClass()
        // refs: https://stackoverflow.com/questions/25725033/swift-too-smart-checking-an-objects-type-when-testing-with-xctest
        XCTAssertTrue((obj as Any) is SimpleClass)
        XCTAssertTrue((obj as Any) is BaseClass)
    }

    // refs: https://github.com/apple/swift/blob/swift-DEVELOPMENT-SNAPSHOT-2016-06-06-a/stdlib/public/core/Mirror.swift#L137
    func testReflectionBehavior() {
        let playGround = Mirror(reflecting: PlayGround())
        XCTAssertEqual(playGround.description, "Mirror for PlayGround")
        XCTAssertEqual(playGround.displayStyle, Mirror.DisplayStyle.struct)
        XCTAssertTrue(playGround.subjectType == PlayGround.self)

        let base = Mirror(reflecting: BaseClass())
        XCTAssertEqual(base.description, "Mirror for BaseClass")
        XCTAssertEqual(base.displayStyle, Mirror.DisplayStyle.class)
        XCTAssertTrue(base.subjectType == BaseClass.self)

        let simple = Mirror(reflecting: SimpleClass())
        XCTAssertEqual(simple.description, "Mirror for SimpleClass")
        XCTAssertEqual(simple.displayStyle, Mirror.DisplayStyle.class)
        XCTAssertTrue(simple.subjectType == SimpleClass.self)

        let parent = simple.superclassMirror
        XCTAssertEqual(parent?.description, "Mirror for BaseClass")
        XCTAssertEqual(parent?.displayStyle, Mirror.DisplayStyle.class)
        XCTAssertTrue(parent?.subjectType == BaseClass.self)

        let defNumber = base.children.filter { $0.label == "number" }[0]
        XCTAssertEqual((defNumber.value as! Int), 0)
        let defString = base.children.filter { $0.label == "string" }[0]
        // FIXME: the assertion is failed. (XCTAssertNil failed: "nil")
        // XCTAssertNil(defString.value)
        XCTAssertEqual(String(defString.value), "nil")
    }

    static var allTests : [(String, (SimpleClassTests) -> () throws -> Void)] {
        return [
            ("testSimpleClassBehavior", testSimpleClassBehavior),
            ("testReflectionBehavior", testReflectionBehavior),
        ]
    }
}

FIXME について

value の型が Optional<String> にはなっているっぽいのに、なぜか nil にはならずにテストが通らなかった。 結局、よく分からないまま・・・。

XCode でも調べてみた

class Foo {
    let number: Int? = nil
}

let m = Mirror(reflecting: Foo())
let defNumber = m.children.filter { $0.label == "number" }[0]
defNumber.value == nil
// Playground execution failed: MyPlayground.playground:1:17: error: value of type 'Any' (aka 'protocol<>') can never be nil, comparison isn't allowed
// defNumber.value == nil
//~~~~~~~~~~~~~~~ ^

エラーでるけど、結局 value の返す値が何なのかが不明・・・。

参考にしたページ

XCode を使わず Swift で TDD しながら FizzBuzz を書いてみた

この前作った Swift の環境 で勉強として FizzBuzz を書いてみた。

github.com

パッケージの作り方

$ mkdir FizzBuzz
$ cd FizzBuzz
$ swift package init

コード

// Sources/app/FizzBuzz.swift
class FizzBuzz {
    let num : Int

    init(num : Int) {
        self.num = num
    }

    func toString() -> String {
        switch self.num {
            case let n where n % 15 == 0:
                return "FizzBuzz"
            case let n where n % 3 == 0:
                return "Fizz"
            case let n where n % 5 == 0:
                return "Buzz"
            default:
                return String(self.num)
        }
    }
}
// Tests/app/FizzBuzzTests.swift
import XCTest
@testable import app

class FizzBuzzTests: XCTestCase {
    func testThree() {
      let fizzBuzz = FizzBuzz(num: 3)
      XCTAssertEqual(fizzBuzz.toString(), "Fizz")
    }

    func testFive() {
      let fizzBuzz = FizzBuzz(num: 5)
      XCTAssertEqual(fizzBuzz.toString(), "Buzz")
    }

    func testFifteen() {
      let fizzBuzz = FizzBuzz(num: 15)
      XCTAssertEqual(fizzBuzz.toString(), "FizzBuzz")
    }

    func testOther() {
      let fizzBuzz = FizzBuzz(num: 1)
      XCTAssertEqual(fizzBuzz.toString(), "1")
    }

    static var allTests : [(String, (FizzBuzzTests) -> () throws -> Void)] {
        return [
            ("testThree", testThree),
            ("testFive", testFive),
            ("testFifteen", testFifteen),
            ("testOther", testOther)
        ]
    }
}

テストの実行方法

$ swift build
$ swift test

Travis CI で npm package の自動リリースに挑戦したら失敗したので、 package を deprecate した話

Travis CI で npm package を自動リリースできるっぽいので、設定してみた。

そして、失敗したので deprecate した話。

参考ページ

npm Releasing
https://docs.travis-ci.com/user/deployment/npm

やったこと

clairvoyance の npm publish を自動化してみようと、上記のページを参考に設定してみた。

# .travis.yml
deploy:
  provider: npm
  email: sinsoku.listy@gmail.com
  on:
    tags: true

npm の api token は npm login を実行すると、 ~/.npmrc に追記されるので、それを travis コマンドで設定する。

$ travis encrypt YOUR_API_KEY --add deploy.api_key

あとは git tag でタグを作成し、 git push origin v0.4.1 でプッシュすると自動的に publish された。

リリース失敗

新しいバージョンである v0.4.1 が自動的に publish されたが、パッケージが壊れていた。

原因は gulp build した後のファイルがパッケージに含まれていないため。

修正&再リリース

このあたり を読むと、 .npmignore がない場合、デフォルトでは .gitignore を参照するっぽい。というわけで、 .npmignore を用意した。

coverage/
maps/
node_modules/
src/
test/

そして、新たに v0.4.2 のタグを作ってプッシュしたところ、動くようになった。

npm-deprecate

公開してしまった v0.4.1deprecate にしておいた。

$ npm deprecate clairvoyance@0.4.1 "v0.4.1 does not work, please upgrade to v0.4.2."

ちゃんとインストールするときに WARNING が出るようになってた。

$ npm i clairvoyance@0.4.1         
npm WARN deprecated clairvoyance@0.4.1: v0.4.1 does not work, please upgrade to v0.4.2.

Swift の勉強する環境を docker を使って構築する

docker で勉強する環境を作ってみた。

github.com

準備

リポジトリと README を見てもらえば分かるけど、下記のような bin/runスクリプトを用意する。

#!/bin/sh

if [ -n "$*" ]
  then docker run -v $(pwd):/app -w /app swiftdocker/swift $*
else
  docker run --privileged -it swiftdocker/swift swift
fi

使い方

REPL で試したい場合は bin/run を実行する。

$ bin/run
Welcome to Swift version 3.0-dev (LLVM 8fcf602916, Clang cf0a734990, Swift 000d413a62). Type :help for assistance.
  1>

終了するときは :exit を入力する。
*.swift を実行したいときは bin/run swift sample/hello.swift のように実行する。

$ bin/run swift sample/hello.swift
Hello, World!

Mac で docker-machine の IP を localdocker にしておくと捗る

Mac で docker を使うと、docker-machine の IP にアクセスするときが少し面倒です。
これを /etc/hosts を使って localdocker でアクセスできるようにしておくと便利って備忘録。

準備

まず、 /etc/hosts にこんな感じで書いておく。

127.0.0.1 localdocker

次に、下記のようなスクリプトを用意しておく。

  • up_localdocker
#!/bin/sh

eval $(docker-machine env default)
sudo sed -i '' "s/.*localdocker$/$(docker-machine ip default) localdocker/" /etc/hosts

使い方

docker-machine の再起動の後などにスクリプトを実行すれば環境変数/etc/hosts が更新される。

$ . up_localdocker

. up_localdocker と実行すれば IP が更新されます。

babel + remap-istanbul を使って、ES2015 のコードのカバレッジを計測する

表参道.rb #12【一周年】 で話したネタ。

全然アップデートできていない clairvoyance をちゃんと更新しようと、やる気を高める為に ES2015 で書けるように直した。

資料

やったこと

gulp + babel の環境構築

これは Web 上に資料がいくらでも転がっているので、楽。

clairvoyance
├ lib/
└ test/
  • gulp + babel の構築後
clairvoyance
  ├ lib/   # gulp build で src/lib から生成
  ├ test/  # gulp build で src/test から生成
  └ src/   # ES2015 のソースコード
    ├ lib/
    └ test/

こんな感じになるように環境作った。

ESLint (airbnb) の導入

ES2015 初心者なので、 ESLint を導入して、 airbnb の style に準拠して書くように設定した。
これも苦労せず、 web 上に資料がいくらでも転がっているので、普通に環境作れた。

あとは、 ESLint に指摘された箇所を 頑張って直す だけです。

カバレッジ計測

今まで使っていた istanbul だとカバレッジが計測できなくなったので、 remap-istanbul を使って直した。
これが一番苦労した。

# gulpfile.babel.js
gulp.task('build', ['clean'], () =>
  gulp.src('src/lib/**/*.js')
    .pipe(sourcemaps.init())
    .pipe(babel())
    .pipe(sourcemaps.write('../maps', {
        includeContent: false, sourceRoot: '../src/lib'
      }))
    .pipe(gulp.dest('lib'))
);

gulp.task('remap-istanbul', ['test'], () =>
  gulp.src('coverage/coverage-final.json')
    .pipe(remapIstanbul({
      basePath: 'maps/',
      reports: {
        json: 'coverage/coverage.json',
        html: 'coverage/lcov-report',
        lcovonly: 'coverage/lcov.info',
      },
    }))
);

こんな感じで設定したら動いた。 Current Directory がよく分からんけど、色んなパターンで設定を試したところ、これで動いた。

備考

ブログ書いていたときに見つけたけど、 babel 向けのカバレッジツールもあるみたい。

github.com

codecov とか使えるか検証してないけど、もしかしたら remap-istanbul を使うより楽かも。

最後に

あまり参考になりそうにないブログだけど、 babel + remap-istanbul を使う人の参考に何かなれば幸いです。
詳しく設定内容などを知りたい人は GitHubリポジトリを見てください。

github.com

Ruby でメソッドがどこで定義されているか調べる方法

byebug で辿っても良いけど、これはこれで便利なのでメモ。

method(:byebug).source_location
#=> ["/Users/sinsoku/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/byebug-8.2.5/lib/byebug/attacher.rb", 29]

参考

class Method (Ruby 2.3.0)
http://docs.ruby-lang.org/ja/2.3.0/class/Method.html