web-dev-qa-db-ja.com

静的ユーティリティクラスをシングルトンに変換する

私が働いている会社では、たくさんの「ユーティリティ」クラスがあり、それぞれにたくさんのコード(数千行)があり、それらはすべて静的です。そして、1つの静的メソッドが別の静的メソッドを呼び出します。ここでの問題は、他のクラスをテストしたい場合、(静的であるため)オーバーライドできないため、テストされた関数がこれらの静的ユーティリティクラスを内部で使用するときに多くの苦痛を伴うことです。テストにはmockitoを使用しています。 Powermockはバイトコードを破壊することがあり、mockitoよりもはるかに遅いため、オプションではありません。

私たちが決めたのは、それらをすべてシングルトンに段階的に移動することです。テストするときに、必要なシングルトンインスタンスとテストメソッドをモックできます(または、自動テストでカバーする必要があるため、いくつかのユーティリティメソッドもテストできます)。小さなクラスの場合も簡単にできますが、この例では6006行クラス、1261行クラス、2231行クラスなどがあります。それらをすべて手動でシングルトンに変換するのは大変なことです。そのため、自動的に行う方法はありますか?または、少なくともいくつかのヘルパー。

テスト不可能なコードの例:

static Object methodA()
{
      *very heavy code here*
}

static void methodB()
{
      *some code*
      xx = methodA();
      *here is code I've changed and want to test*
      *some code*
}

MethodBで何かを変更し、自分の変更をテストしたいとします。テストとは関係がないので、このmethodAを呼び出さないでください。しかし、そのような場合、mockitoでmethodAが実行することを変更することはできません。ただし、me​​thodA内では、静的ではないためにモック化できる同じシングルトンメソッドへの呼び出しがある場合は可能です。

だからあなたがそれをこのようなものに変換すると

public class A {
    private A(){}

    public Object methodAInner()
    {
        *heavy code here*
    }
    public void methodBInner()
    {
        *some code*
        xx = methodA();
        *here is code I've changed and want to test*
            *some code*
    }


    private static A getInstance()
    {
        return SpringUtils.getBean(A.class); // here can be anything that supports testing
    }

    static Object methodA()
    {
        return getInstance().methodAInner();
    }

    static void methodB()
    {
        getInstance().methodBInner();
    }
}

これらのSpringUtilsが実際のmethodBでモックされたオブジェクトを返すようにして、既存のコードにほとんど変更を加えず(git blameに便利)、静的修飾子とchngeメソッド名を削除するだけです。レガシーで最も重要なもの-このユーティリティクラスを使用するコードを変更できないようにします。

3
maxpovver

私は皆、火と戦うのが好きですが、悪いコード(たくさんの静的メソッド)を他の悪いコード(たくさんのシングルトン)に変えることは、本当に時間を費やすのに良い考えのようには思えません。

あなたが書いたものから、これはすべてレガシーコードのコンテキストですが、それらの静的メソッドが存在するソースへの変更アクセス権があります(おそらくすべてですが、残りは大きすぎて触れられません)。それでは、最終的に行きたい場所を見てみましょう。

  • どうしてそんな仕事をしたいの?現在のコードはテスト不可能であり、新しいコードもテストするのが困難になるため
  • なぜテストできないのですか?すべての静的メソッドのため
  • なぜこれらの静的メソッドがあるのですか?どこでも使用されており、再帰的な呼び出しがあり(互いに依存している)、したがって、取り除くのが難しいため
  • なぜ彼らはお互いに依存しているのですか?ユーティリティクラスは、単一の責任ではなく、多くのことを行う傾向があるため

次の質問とあなたへの提案は、次のようになります。

あなたは責任を特定するのに苦労するかもしれませんが、おそらく最初にこの問題を認識させた具体的なユースケースをすでに持っているので、それは良いスタートになるはずです。

個別の単一の責任とテスト可能なクラスが利用可能になった後も、それを古いクラス内で使用して、そのクラスの一部を置き換えることができます。新しいクラスで他の静的メソッドのコードが必要だったので、まだ重複している可能性がありますが、それはまだ他から呼び出されていますか?次に、それは次の作業領域へのポインタです。2つの場所が共有している共通点を特定し、両方から抽出します。

あなたが描く純粋なサイズのために、これはゆっくりと長く実行されるプロセスになるので、そのクラスから外れるすべての小さな責任は勝利と見なされなければなりません。古い静的メソッドが新しいクラスに依存するようにし、非推奨にし、呼び出しサイトが表示されるたびに根絶します。これらのことは、最終的に最後の投資を行い、チームに1〜2日かけて穴をあけて、残っている古い問題のあるコードをすべて取り除くことができるまで、時間がかかります。注意する限り、新しいコードや変更されたコードにこれらの古いメソッドを使用しないようにすれば、その目標に近づくことになります。旅の成功をお祈りします。

補遺-これらのユーティリティクラスは異なるブランチ間で異なるという追加情報に基づいており、異なるチームはこれらのそれぞれで動作します:つまり、実行中のターゲットを撃とうとしています。コードベースのサイズを考えると、タスク自体は安定したターゲットには十分難しいですが、最初にその動きを止めるまで、その道を進むことはお勧めできません。他のチームと話し合って、チームに参加してください。彼らが何らかの形や形で反対する場合、すでに難しい仕事はほとんど不可能になります。うまくいけば、それらはすべてあなたの痛みを共有し(または少なくともコードベースのその部分を気にしない)、それらをすべてマージすることによってこれらのクラスの統一された状態に戻ることができます。

はい、これらすべてのブランチをマージすることをお勧めします。ユーティリティクラス。それはそれ自体が地獄であることを知っています。あなたには3つの選択肢があります。それを一般的な問題にする(そして解決策を共有する!)か、自分自身を完全に切断するか、そのままにするかです。

切断とは、支店のチームが完全に別の製品ラインとして定義されることを意味します。あなたは他の人とのマージをやめ、代わりにあなた自身のものを転がします。うまくいかないかもしれませんし、たとえそれがうまくいくとしても、これが数年後に会社の全員を襲うように戻ってくるのを見てきました。

とにかく、自分のチーム(コードベース)内でローカルに解決できない問題はすべて、残念ながら即座に会社の政治問題になります。チームのコードで厄介なクラスを取り除くためにチームメイトとの戦いを選ぶのは簡単ですが、他のチーム/利害関係者/マネージャーなどを巻き込んで戦争に本当に行きたいかどうかについてもう一度考える価値があります。

5
Frank

ユーティリティクラスのメソッドをモックする必要があることは非常にまれです。非常に重要なOOP=原則は、状態はオブジェクトによって表されることであり、オブジェクトを操作しない静的メソッドはステートレスであることを指示します。

functionalityをモックする必要はありません。階乗の実装に依存するコードをテストしている場合、階乗関数をモックする必要はありません。それが単純な実装をしているからではなく、あなたが非常に多くのお金を払い、それをあざけることからほとんど利益を得ないからです。得る唯一のことは、factorialが壊れている場合でもそのコードをテストできることです。これは、コードに導入する必要があるすべての混乱と、このモックを可能にするために壊す必要があるすべてのプログラミング原則に値するものではありません。 。 factorial(6)が_720_を返すことを確認する必要がある場合、(構文は正確でない場合があります):

_Factorial factorial = Mockito.mock(Factorial.class);
test.when(factorial.factorial(6)).thenReturn(720);
_

Factorialのユニットテストに追加した方がいいでしょう:

_Assert.assertEquals(Factorial.factorial(6), 720);
_

これで、factorial(6)が_720_を返すことを確認でき、モックする必要はありません。

したがって、機能をモックする必要はありません。モックしたいのはresourcesです。テストでWebサービスを実際に呼び出さないように、Webサービスをモックします。データベースのモックを作成して、テストによってデータベース内の実際のデータが変更されないようにします。テストをより複雑にする(階乗のモックのように)のではなく、これらをモックすることで、ユニットテストの内部からWebサービスまたはデータベースをデプロイする必要がなくなるため、テストがより簡単になります。彼らはトラブルに値する。

したがって、リソースをモックするだけでよく、リソースは自然にオブジェクトに変換されます。静的メソッドを使用してWebサービスの関数を呼び出すのではなく、Webサービスを表す(URLのようなデータを保持する)オブジェクトがあります。リソースの性質上、ユニットテストを作成するつもりがない場合でも、リソースを渡す必要があります。つまり、リソースをモックするためにコードを混乱させる必要はありません。

したがって、これらのユーティリティメソッドがリソースに対するアクションを表さない場合は、それらをモックする必要はありません。そしてもしそうなら-そもそもユーティリティメソッドであってはならないはずです!

2
Idan Arye