Java で単体テストを作成したことのある読者は Mockito に精通していると思います。Mockito とは何か、Mockito が使用される理由については、この記事では詳しく説明しません。この記事では、Apache ShardingSphere プロジェクトでの不適切な使用によって発生する偶発的な単体テスト エラー
Mockito.mockStatic
のトラブルシューティング プロセスを記録します。
記事ディレクトリ
序文
Mockito は 3.4.0 以降、静的メソッドのモックをサポートする新しいメソッドを追加しましたMockito.mockStatic
。
また、Stack Overflow の質問にも回答し、 Apache ShardingSphere の単体テスト コードでMockito.mockStatic
モック シングルトンを使用する私のケースを示しましたMockito.mockStatic
。この方法に特に慣れていない学生でも、それについて学ぶことができます:
Mockito モック シングルトンの使用方法 mockito でシングルトンをモックする
mockStatic
実装にはどのような予防策が講じられますか? Mockito の公式ドキュメントの説明を見てみましょう: 48. 静的メソッドのモック (3.4.0 以降)
インライン モック メーカーを使用すると、現在のスレッドおよびユーザー定義のスコープ内で静的メソッド呼び出しをモックできます。このようにして、Mockito は、同時かつ順次に実行されるテストが干渉しないことを保証します。静的モックが一時的なものであることを確認するには、try-with-resources 構造内でスコープを定義することをお勧めします。
一般的な意味は、mockStatic
メソッドのスコープは現在のスレッドであり、ユーザーによって定義されたスコープです。一時的にのみ有効にするためにmockStatic
、try-with-resources コード ブロックでラップすることをお勧めしますmockStatic
。
Mockito ドキュメントで提供されている例を解釈すると、次のようになります。
assertEquals("foo", Foo.method()); // 静态方法 Foo.method() 原本行为
try (MockedStatic mocked = mockStatic(Foo.class)) {
// 对 Foo 类进行 mockStatic
mocked.when(Foo::method).thenReturn("bar"); // 通过 mock 改变静态方法 Foo.method() 行为
assertEquals("bar", Foo.method()); // 进行测试断言
mocked.verify(Foo::method);
}
assertEquals("foo", Foo.method()); // 离开 mockStatic 作用域,Foo.method() 恢复原本行为
mockStatic
ここで、メソッドが try-with-resources ブロックでラップされておらず、オブジェクトが手動で閉じられていない場合にMockedStatic
何が起こるかを考えてみましょう。
ドキュメントの説明によると、 close していない場合mockStatic
、このスレッド上のモック化された静的クラスの動作は常に変更されますか?
Apache ShardingSphere の単体テストでは、Mockito.mockStatic
使用後にリリースされないために単体テストが散発的に失敗するという問題がありました。
トラブルシューティングのプロセス
Apache ShardingSphere は、マスターにマージされた PR またはコミットごとに GitHub Actions を通じて CI を実行します。これは、標準の Maven クリーン インストール プロセスであり、インストール プロセス中の単体テストの実行が含まれます。
一時期、ShardingSphere の CI が失敗することがありましたが、同じく ShardingSphere の開発に携わっている他の学生に聞いたところ、ローカルでのインストールや単体テストも失敗することがありました。
長い時間が経っていたため、GitHub Actions ログがクリーンアップされました。
プロジェクトの単体テストが安定して合格することが保証できない場合、それはテスト コードに問題があるか、運用コードに隠れた危険性があるに違いありません。
問題の再発
ShardingSphere インフラ共通モジュールの下の単体テストを見てみましょう。ShardingSphereMetaDataTest には次のようなユースケースがあります。
@Test
public void assertGetMySQLDefaultSchema() throws SQLException {
MySQLDatabaseType databaseType = new MySQLDatabaseType();
ShardingSphereDatabase actual = ShardingSphereDatabase.create("foo_db", databaseType, Collections.singletonMap("", databaseType), mock(DataSourceProvidedDatabaseConfiguration.class), new ConfigurationProperties(new Properties()), mock(InstanceContext.class));
assertNotNull(actual.getSchema("foo_db"));
}
このテスト ケースを単独で実行すると成功します。
ただし、infra-common モジュールの下のすべてのテストが実行されると、この使用例は失敗します。
このうち、最終的に呼び出される静的メソッドはおおよそ以下のようになるが、コード上ではインスタンスを正常に返すか例外をスローするかの二通りShardingSphereDatabase.create
しかなく、 を返すことはない。ShardingSphereDatabase
null
private static ShardingSphereDatabase create(final String name, final DatabaseType protocolType, final DatabaseConfiguration databaseConfig, final Collection<ShardingSphereRule> rules, final Map<String, ShardingSphereSchema> schemas) {
// 省略中间过程代码
return new ShardingSphereDatabase(name, protocolType, resourceMetaData, ruleMetaData, schemas);
}
ただし、このような単純な単体テストでは null ポインターが報告されますが、それは依然としてactual
(ShardingSphereDatabase.create
静的メソッドの戻り結果)ですnull
。
java.lang.NullPointerException: Cannot invoke "org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase.getSchema(String)" because "actual" is null
at org.apache.shardingsphere.infra.metadata.ShardingSphereMetaDataTest.assertGetMySQLDefaultSchema(ShardingSphereMetaDataTest.java:109)
コードの観点から見ると、返される可能性のない静的メソッドがnull
単体テストで返されますnull
。理解できません。
ローカル環境ではしばらくの間、特定の問題が引き続き発生する可能性があるため、デバッグを中断できます。
障害が避けられずに散発的に発生する理由は、モジュールの下で単体テストが実行される順序が一定ではないためです。他のテスト ケースを汚染する可能性のある一部のテスト コードは、実行シーケンスの比較的遅いところに偶然発生し、テスト操作は正常に成功したように見えます。
また、単体テストの実行順序に影響を受ける別の問題も解決しました。詳細については、以前の記事「ThreadLocal リークによる shardingsphere-jdbc-core 単体テストの散発的エラーのトラブルシューティングと修復」を参照してください。
デバッグコード
ブレークポイントを設定し、モジュール全体のテストを実行し、アサーションが失敗する前のコードまで実行します。
簡単な式の計算に移ります。実際、ShardingSphereDatabase.create
メソッドは を返しますnull
。
次に、メソッド内に移動して、以下を確認します。
手がかりを見つけて解決する
奇妙な現象が現れた!以下のアニメーションを見ることができます。メソッド
を入力した後、 をクリックすると、通常はメソッドのコードの最初の行でメソッドの入力を続ける必要がありますが、デバッガはメソッドに直接ジャンプし、クリックしてもメソッドの入力は続行されません。方法!ShardingSphereDatabaes.create
Step Into
create
DatabaseRulesBuilder.build
create
return
Step Into
create
この奇妙な現象は、経験上、実際に実行されているバイトコードとソースコードが一致していない可能性があります。このメソッドの使用についてコードをグローバルに検索したところmockStatic
、一部の単体テスト コードでmockStatic
このメソッドが使用されていることがわかりましたが、try-with-resources も手動リリースも使用されていませんでした。
そこで、不適切に使用されていたコードを修正し、 ShardingSphere のコード仕様にと をmockStatic
使用するための要件を追加しました。mockStatic
mockConstruction
具体的には、次のことがわかります。
- ShardingSphere 単体テストのモック静的リークを修正: 単体テストのモック静的リークを修正 #19077
- Mockito に関して ShardingSphere で使用されるコード仕様を更新します。Mockito #19083 に関する行動規範を更新します。
穴を掘る
単体テストの問題は前の手順で特定され、解決されましたが、これは個人的な経験と運の問題でした。
私が のようなメソッドを使用したことがなく、関連する経験もない
mockStatic
開発者である場合、mockStatic
IDEA のデバッグ現象に基づいてリークに関する結論を直接導き出すことはできません。そのようなリークをトラブルシューティングするにはどうすればよいですか?
時間を見つけて、この質問をさらに深く掘り下げてください。