2017-08-04 8 views
1

私はテスト可能なコードを書く際に読んでいましたが、現在私のロギングフレームワークAPIをリファクタリングして実践しています。私の主な関心事は、1)ビジネスコードからの呼び出しが容易であること、2)簡単な疑似コードなので、実際のロギングを呼び出さずにビジネスコードをテストできること、そしてロギングされたかどうかを主張できることですそのテスト。正しい方法でJavaでモック可能なロギングAPIを構築する

私はかなりテスト可能と感じるところに達しましたが、私はまだ改善できると感じています。私と一緒に抱きしめてください。これは私がこれまで持っていたものです。

/* 
* The public API, which has a mockable internal factory responsible for creating log implementations. 
*/ 
public final class LoggerManager { 
    private static LoggerFactory internalFactory; 
    private LoggerManager() {} 

    public static SecurityLogger getSecurityLogger() { 
     return getLoggerFactory().getSecurityLogger(); 
    } 
    public static SystemErrorLogger getSystemErrorLogger() { 
     return getLoggerFactory().getSystemErrorLogger(); 
    } 
    private static LoggerFactory getLoggerFactory() { 
     if (internalFactory == null) 
      internalFactory = new LoggerFactoryImpl(); 
     return internalFactory; 
    } 
    public static void setLoggerFactory(LoggerFactory aLoggerFactory) { 
     internalFactory = aLoggerFactory; 
    } 
} 
/* 
* Factory interface with methods for getting all types of loggers. 
*/ 
public interface LoggerFactory { 
    public SecurityLogger getSecurityLogger(); 
    public SystemErrorLogger getSystemErrorLogger(); 
    // ... 10 additional log types 
} 
public final class LoggerFactoryImpl implements LoggerFactory { 
    private final SecurityLogger securityLogger = new SecurityLoggerImpl(); 
    private final SystemErrorLogger systemErrorLogger = new SystemErrorLoggerImpl(); 

    public SecurityLogger getSecurityLogger() { 
     return securityLogger; 
    } 
    public SystemErrorLogger getSystemErrorLogger() { 
     return systemErrorLogger; 
    } 
} 

APIは、ビジネスコードでこのように呼ばれている:

LoggerManager.getSystemErrorLogger().log("My really serious error"); 

私はその後、TestLoggerFactoryを使用してユニットテストでこれをあざけるだろう、単純にすべてのロギングコールとなりますを追跡し、テスト・ロガーを作成しますそれは例えばassertNoSystemErrorLogs()を実行することが可能に:

LoggerManager.setLoggerFactory(new TestLoggerFactory()); 

さて、これは正常に動作しますが、私は何かが欠けてるかのように私はまだ落ち、それはより多くのテストが優しい行うことができます。たとえば、静的なsetLoggerFactoryを使用することによって、すべてのテストでロガーファクトリを設定します。つまり、あるテストが実際に別のテストに影響を与えることができます。ですから私の大きな疑問は、この種の簡単に模倣できるAPIを作成する標準的な方法は何ですか?ある種の依存性注入?

疑問点は、アクセスしやすく、使いやすいAPIを作成することと関連があります。私の例がロギングフレームワークAPIであるという事実は、その要点の横にあります。

+0

JUnitテストを行っている場合は、私自身の模擬クラスを実装する代わりに、Mockitoのような模擬フレームワークを使用することを検討します。 Java EE環境では、静的ファクトリを使用する代わりに、修飾子/プロデューサでCDIを使用して異なるロガーを注入することもできます。 – Dainesch

+0

そして私は最初の答えに同意します。すべてのプログラマーはロギングフレームワークを自分自身で作成したいと思っていますが、既に存在するホイールを再発明する時間を費やしています。そして、あなたの車輪のバージョンは独自のもので、洗練されていなくて、テストされたものはそれほど多くありません。もちろん、それは少ない機能を持つでしょう。 – GhostCat

+0

@Dainesch確かに、単体テストでは新しいTestLoggerFactory()の代わりにMockito.mock(LoggerFactory.class)を使うことができましたが、実際にはAPIの設計は変わりません。 – yzfr1

答えて

1

CDIによるロガー注入を使用してサンプルを提供するようにコメントに尋ねられました。多くの可能な実装がありますが、異なる修飾子がどのように使用されるかを示すために、私はこのサンプルを選択しました。ここでは、ロガーが共通のLoggerインターフェースを共有し、異なる修飾子が使用されると仮定しています。

ただし、10種類以上のログタイプがある場合は、どのロガーを注入するかを決定するためにenumプロパティを使用する修飾子を使用します。

まずあなたが注入したいどの実装マークする(セキュリティ、SystemErrorを、....)あなたの修飾子を定義します。

@Qualifier 
@Retention(RUNTIME) 
@Target({TYPE, METHOD, FIELD, PARAMETER}) 
public @interface Security { 
} 

その後、あなたはロガーの実装を作成する方法を定義する必要があります。それは工場なのです。実装は、注入点と修飾子に与えられた値に依存する。ここでは、たとえば、ロガーに注入されたクラスのクラス名を渡すだけです。 2つのメソッドは、異なる実装を作成するために使用されます。これは、メソッドに修飾子を適用することで実現できます。

これで、使用したいロガーを注入することができます(CDI設定ファイルの豆を参照してください)。XML)

@Stateless 
public class SampleService { 

    @Inject 
    @Security 
    private Logger securityLogger; 

    public void doSomething() { 
     securityLogger.log("I did something!"); 
    } 

} 

ユニットテストは、あなたはまだあなたが他のすべての注入されたオブジェクトをモックと同じようにロガーを模擬する必要があります場合は、何もCDIを使用して変化しません。統合テストでは、ロガーの作成方法や方法を変更できます。

個人的なものではありませんが、10種類以上の異なるロガーを使用する理由はなく、ほとんどのログフレームワークや模擬フレームワークで提供されているメカニズムを使用しません。多分、あなたには良い理由があります。あなたのすべての選択肢を知ることは常に助けになります。


編集:ユニットテストするために、我々はMockitoを使用して、次のテストケースを作成SampleServiceをJUnitのサンプル

を追加します。

@RunWith(MockitoJUnitRunner.class) 
public class SampleServiceTest { 

    @InjectMocks 
    private SampleService sample; 

    @Mock 
    private Logger securityLogger; 

    @Test 
    public void testDoSomething() { 
     doThrow(new RuntimeException("Fail")).when(securityLogger).log(anyString()); 

     sample.doSomething(); // will fail 
    } 

} 

テストはロガーような方法でセットアップアップは、あります.logが呼び出され、例外がスローされます。

+0

いいね。だから、どうやって単体テストでロガーを嘲笑するつもりですか? 複数のロガーの理由として、異なるデータでいっぱいになっているカスタムログタイプが数多くあります。私が言ったように、私は、テスト可能でモックになるデザインの作成という概念にもっと興味があります。 – yzfr1

+0

回答の編集にテストケースを追加しました – Dainesch

1

最も大きな改善点は、現実的な標準実装の無関係なAPI slf4jを使用することです。

+0

私はボンネットの下でそれを使用していないことをどうやって知っていますか?問題は、ビジネスコードからアクセス可能な使いやすいAPIを作成することです。これは、簡単なことですが、ロギングフレームワークなのか、それとも別の点です。 – yzfr1

0

また、あなたのクラスはスレッドセーフではありません LoggerManagerは複数のインスタンスを作成することができます。これはシングルトンにしようとしています。 に注意を払うgetLoggerFactory()

+0

はい、私は、質問を不必要に大きくしないように簡単に模擬的でテスト可能なコードを書くことに関する質問に厳密に関連していないすべてのコードを削除しました。 – yzfr1