Mockito.mockStatic リークによって引き起こされた単体テストの散発的なエラーのトラブルシューティング プロセスを思い出してください。

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 の開発に携わっている他の学生に聞いたところ、ローカルでのインストールや単体テストも失敗することがありました。

ここに画像の説明を挿入
https://github.com/apache/shardingsphere/actions/workflows/ci.yml?query=branch%3Amaster+created%3A<2022-07-13+is%3Afailure

長い時間が経っていたため、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しかなく、 を返すことはない。ShardingSphereDatabasenull

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.createStep IntocreateDatabaseRulesBuilder.buildcreatereturnStep Intocreate

この奇妙な現象は、経験上、実際に実行されているバイトコードとソースコードが一致していない可能性があります。このメソッドの使用についてコードをグローバルに検索したところmockStatic、一部の単体テスト コードでmockStaticこのメソッドが使用されていることがわかりましたが、try-with-resources も手動リリースも使用されていませんでした。

そこで、不適切に使用されていたコードを修正し、 ShardingSphere のコード仕様にと をmockStatic使用するための要件を追加しましたmockStaticmockConstruction

具体的には、次のことがわかります。

穴を掘る

単体テストの問題は前の手順で特定され、解決されましたが、これは個人的な経験と運の問題でした。

私が のようなメソッドを使用したことがなく、関連する経験もないmockStatic開発者である場合、 mockStaticIDEA のデバッグ現象に基づいてリークに関する結論を直接導き出すことはできません。そのようなリークをトラブルシューティングするにはどうすればよいですか?

時間を見つけて、この質問をさらに深く掘り下げてください。

おすすめ

転載: blog.csdn.net/wu_weijie/article/details/125759460