ベータコンピューティングの活動や技術、開発のこだわりなどを紹介するブログです。



Xamarinの共有プロジェクトでも単体テストが書きたい!

弊社Android担当のd-ariakeです。

現在、Xamarinの改修案件をさせてもらっています。
そのプロジェクトで単体テストを書こうと思ったのですが、少しハマってしまいましたので、それを共有したいと思います。

このプロジェクトはXamarinネイティブの 共有プロジェクト でした。
共有プロジェクトは単なるソースコードの置き場で、AndroidとiOSのそれぞれのプロジェクトでそのソースファイルを参照するといった仕組みです。

この案件には既存のコードがあり、そのソースではがっつりそのままXamarin固有のコードが多用されていました。
ですので、そのまま .NET CorexUnit プロジェクトで共有プロジェクトの参照を追加してしまうと、Xamarin固有のコードを含むソースファイルでエラーになってしまいます。

↓ こんな感じに、Xamarin.AndroidプロジェクトとXamarin.iOSプロジェクトから見たときはビルドが通りますが、単体テストプロジェクトから見たときにエラーになります。

f:id:betacomputing3:20201124135257p:plain f:id:betacomputing3:20201124135309p:plain

単体テストでテストしたい部分というのはXamarinに依存しない計算ロジックや判定ロジックだったりします。
そのため、 共有プロジェクトをまるごと参照に追加するのではなく、ソースファイル単位での追加をすること で解決することができました。

↓ これです。

f:id:betacomputing3:20201124135316p:plain

既存のファイルの追加で、Xamarinに依存していないソースコードのみを単体テストプロジェクトに追加します。
もし、テストをしたい箇所でXamarin固有の機能 (Preferencesなど) を使用している場合、適切にリファクタリングをしてあげましょう。

固有の機能を必要とする箇所を IHogeHogeProviderIHugaHugaRepository などといったインタフェースで切り、テストをしたいビジネスロジックがインタフェースのみに依存するように変更します。

先ほどの画像でも Xamarin.Essentials.Preferences を使っている箇所がありましたが、 IHogePrefereces というインタフェースと HogePreferences (Impl) という具象型をつくり、単体テストプロジェクトには IHogePrefereces のみを追加しました。
これでちゃんと単体テストプロジェクトでもビルドが通るはずです。

そして、もう一箇所ハマったので、そちらも共有します。
私はテストに Moq というモックライブラリを使っているのですが、何も設定せずに使うと以下のようなエラーが発生しました。

System.ArgumentException : Cannot set up 〇〇 because it is not accessible to the proxy generator used by Moq: Can not create proxy for method 〇〇 because it or its declaring type is not accessible. Make it public, or internal and mark your assembly with [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] attribute, because assembly 〇〇 is not strong-named.

怒られている内容に従い、 AssemblyInfo.cs に以下のような属性を追加します。

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

しかし、最初は Hoge.SharedHoge.Shared.Tests というプロジェクトがあったとき、Hoge.Shared の方に書いてしまい、エラーが消えてくれませんでした。
共有プロジェクトは単なるソースコード置き場で、それを取り込む側の単体テストプロジェクトでアセンブリが吐かれるので Hoge.Shared.Tests の方に書かないといけません。 (ここで少しハマってしまいました。)
単体テストプロジェクトの方に書き直すと、正常にMoqによるモックが動作してくれました。 🎉

共有プロジェクトの保守をする際には是非参考にしてみてください!