2017-08-04 46 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這樣的模擬框架,而不是實現你自己的mockable類。 同樣在Java EE環境中,您可以使用CDI與限定符/生產者來注入不同的記錄器,而不是使用靜態工廠。 – Dainesch

+0

我同意第一個答案 - 雖然每個程序員都喜歡*自己創建一個日誌框架 - 但您花時間重新創建一個已經存在的輪子。而你的車輪版本將是專有的,不太複雜,而且測試要少得多。當然,它的功能會更少。 – GhostCat

+0

@Dainesch當然,我可以在單元測試中使用Mockito.mock(LoggerFactory.class)而不是新的TestLoggerFactory(),但這並沒有真正改變API的設計,對吧? – yzfr1

回答

1

我被問到在評論中提供了一個使用記錄器通過CDI進行注射的樣本。有很多可能的實現,但爲了展示如何使用不同的限定符,我選擇了這個示例,我假設記錄器共享通用的Logger接口並使用不同的限定符。

但是,如果提供10種以上可能的日誌類型,我會選擇一個使用enum屬性的限定符來決定注入哪個記錄器。

首先定義預選賽(安全,關於SystemError,....)來標記你將要注入哪個實現:

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

然後,你必須定義如何創建記錄器的實現。這是一個工廠,有點。實現可以取決於注入點和賦值給限定符的值。在這裏,例如,我只是將記錄器傳遞到注入類的類名。 這兩種方法用於創建不同的實現。這可以通過將限定符應用於方法來實現。

@Singleton 
public class LoggerProducer { 

    // here perhaps a cache or environment related flags, ... 

    @Security 
    @Produces 
    public Logger getSecurityLogger(InjectionPoint ip) { 
     String key = getKeyFromIp(ip); 
     return new SecurityLoggerImpl(key); 
    } 

    @SystemError 
    @Produces 
    public Logger getSystemErrorLogger(InjectionPoint ip) { 
     String key = getKeyFromIp(ip); 
     return new SystemErrorLoggerImpl(key); 
    } 

    private String getKeyFromIp(InjectionPoint ip) { 
     return ip.getMember().getDeclaringClass().getCanonicalName(); 
    } 

} 

現在您可以在想要使用它的地方注入想要的記錄器(請參閱CDI配置文件bean。XML)

@Stateless 
public class SampleService { 

    @Inject 
    @Security 
    private Logger securityLogger; 

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

} 

當單元測試你仍然有嘲笑記錄,你會嘲笑隔日注射對象,沒有使用CDI變化。在進行集成測試時,您可以更改生成記錄的方式。

不要把它看作個人,但我不明白爲什麼要使用10多種不同的記錄器,而不是使用大多數日誌框架和/或模擬框架提供的機制。也許你有很好的理由,好吧,它總是有助於瞭解你的所有選擇。


編輯:添加的JUnit樣品

進行單元測試,我們創建一個使用的Mockito下面的測試情況下,SampleService:

@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,這些API易於模擬,它是一個日誌記錄框架或別的東西。 – yzfr1

0

此外,您的課程不是線程安全的 LoggerManager可以創建多個LoggerFactoryImpl實例,我想你正試圖使它成爲單身人士。 注意getLoggerFactory()

+0

是的,我知道,我刪除了所有與編寫容易可嘲弄和可測試代碼的問題並不嚴格相關的代碼,以避免不必要地提出問題。 – yzfr1

相關問題