說我有如下代碼:OOP分解和單元測試困境
class BookAnalysis {
final List<ChapterAnalysis> chapterAnalysisList;
}
class ChapterAnalysis {
final double averageLettersPerWord;
final int stylisticMark;
final int wordCount;
// ... 20 more fields
}
interface BookAnalysisMaker {
BookAnalysis make(String text);
}
class BookAnalysisMakerImpl implements BookAnalysisMaker {
public BookAnalysis make(String text) {
String[] chaptersArr = splitIntoChapters(text);
List<ChapterAnalysis> chapterAnalysisList = new ArrayList<>();
for(String chapterStr: chaptersArr) {
ChapterAnalysis chapter = processChapter(chapterStr);
chapterAnalysisList.add(chapter);
}
BookAnalysis book = new BookAnalysis(chapters);
}
private ChapterAnalysis processChapter(String chapterStr) {
// Prepare
int letterCount = countLetters(chapterStr);
int wordCount = countWords(chapterStr);
// ... and 20 more
// Calculate
double averageLettersPerWord = letterCount/wordCount;
int stylisticMark = complexSytlisticAppraising(letterCount, wordCount);
HumorEvaluation humorEvaluation = evaluateHumor(letterCount, stylisticMark);
// ... and 20 more
// Return
return new ChapterAnalysis(averageLettersPerWord, stylisticMark, wordCount, ...);
}
}
在我的具體情況,我有嵌套的一個多水平(認爲BookAnalysis - > ChapterAnalysis - > SectionAnalysis)和幾個ChapterAnalysis上的類(認爲每章都包含PageAnalysis)和SectionAnalysis(認爲FootnotesAnalysis等)級別。我對如何構建這個問題感到困惑。的問題是,在processChapter
方法:
- 兩種製劑和計算步驟需要時間/資源的不可忽略量的
- 計算步驟取決於多個準備步驟
一些憂慮:
- 上面這個類,考慮到ChapterAnalysis中有20個字段會比較長
- 測試整個測試需要一個非常複雜的準備方法,可以測試大量的代碼。要確認例如
countLetters
按預期工作,我不得不不必要的複製,幾乎整個輸入只是爲了測試兩種不同的情況下,countLetters
行爲不同
解決方案包含的複雜性,並允許testabilty:
- 拆分成
processChapter
私人方法,但不能/不應該測試它們 - 拆分成多個類,但然後我需要大量的輔助數據類(對於計算階段中的每種方法)或一個大廚房水槽(保存所有數據準備階段)
- 使輔助方法包私有。雖然這解決了測試問題,但我可以測試它們,但「我不應該」部分仍然適用
任何提示,特別是來自類似現實世界的經驗?
編輯:更新命名,並根據當前答案添加了一些說明。
我分裂成類的主要問題是它不是線性/單層。例如,以上countLetters
產生complexSytlisticAppraising
所需的結果。假設爲這兩種方法(LetterCounter
和ComplexSytlisticAppraiser
)分別制定類別是有意義的。現在,我必須做出獨立豆的ComplexSytlisticAppraiser.appraise
輸入,即是這樣的:
class ComplexSytlisticAppraiserInput {
final int letterCount;
final int wordCount;
// ... 10 more things it might need
}
這很好,但現在我有HumorEvaluator
爲此,我需要這樣的:
class HumorEvaluatorInput {
final int letterCount;
final int stylisticMark;
// ... 5 more things it might need
}
雖然這可能在許多情況下僅僅通過列出參數來完成,一個大問題是返回參數。即使當我必須返回兩個整數時,我也必須創建一個具有這兩個整數,構造函數,equals/hashCode和getters的獨立bean。
class HumorEvaluatorOutput {
final int letterCount;
final int stylisticMark;
public HumorEvaluatorOutput(int letterCount, int stylisticMark) {
this.letterCount = letterCount;
this.stylisticMark = stylisticMark;
}
public int getLetterCount() {
return this.letterCount;
}
public int getStylisticMark() {
return this.stylisticMark;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("HumorEvaluatorOutput [letterCount=");
sb.append(letterCount);
sb.append(", stylisticMark=");
sb.append(stylisticMark);
sb.append("]");
return sb.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + letterCount;
result = prime * result + stylisticMark;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
HumorEvaluatorOutput other = (HumorEvaluatorOutput) obj;
if (letterCount != other.letterCount)
return false;
if (stylisticMark != other.stylisticMark)
return false;
return true;
}
}
這是2對53行代碼 - yikes!
所以這一切是好的,但它:
- 是不可重用。其中絕大多數僅用於使代碼可測試。想想分析儀,例如:
BookAnalyzer
,CarAnalyzer
,GrainAnalyzer
,ToothAnalyzer
。他們分享絕對沒有共同 - 使20班列的1不會產生太大除允許測試
- 你可以爭辯說,不管是分成類或方法,作出足以零件小點的differene被理解和操縱的並不是那麼大
- 另一方面,如果我想要在考慮可測試性的情況下進行適當的面向對象操作,將會出現大量的噪音和間接性。比較:
- 管理10個文件= 10個分析儀* 1個文件與20種私有方法
- 800文件= 10個分析器*(20個接口,20個implementions,20個輸入和20種輸出豆)
- 400個文件,如果我們刪除輸入/輸出bean並轉到其他路徑(例如每個分析器破解一個大I/O bean) 請注意,數百個文件將非常短,主要是樣板文件 - 可能大多數邏輯將不到10行==第一種情況下的私有方法)
- 有很大的開銷在這。如果我打電話給一個私有方法1m次,創建額外的輸入和輸出bean將會加起來......
也許做正確的事情就是做正確的事情。只是想看看是否有其他一些我可以追求的選擇,我錯過了。或者我的邏輯純粹是壞的?
編輯:根據評論的額外更新。我們可以HumorEvaluatorOutput
較短,而不是一個巨大的問題:
class HumorEvaluatorOutput {
final HumorCategoryEnum humorCategory;
final int humorousWordsCount;
public HumorEvaluatorOutput(HumorCategoryEnum humorCategory, int humorousWordsCount) {
this.humorCategory = humorCategory;
this.humorousWordsCount = humorousWordsCount;
}
public HumorCategoryEnum getHumorCategory() {
return this.humorCategory;
}
public int getHumorousWordsCount() {
return this.humorousWordsCount;
}
}
這是2比17行代碼 - 仍然讓人驚訝!當你考慮一個例子時並不多。當您有20個不同的分析儀(BookAnalyzer
,CarAnalyzer
,...)和20個不同的子分析儀時(對於上述書:ComplexSytlisticAppraiser
和HumorEvaluator
以及其他所有分析儀類似,顯然差別很大),代碼增加8倍向上。
至於BookAnalyzer
VS CarAnalyzer
和Book
VS Chapter
子儀 - 其實,我需要比較BookAnalyzer
VS CarAnalyzer
,因爲這就是我都會有。我肯定會重用所有章節的章節子分析器。但是,我不會將其用於任何其他分析儀。即我也會有這樣的:
BookAnalyzer
ChapterSubAnalyzer
HumorSubAnalyzer
... // 25 more
CarAnalyzer
EngineSubAnalyzer
DrivertrainSubAnalyzer
... // 15 more
GrainAnalyzer
LiquidContentSubAnalyzer
FiberContentSubAnalyzer
... // 20 more
通過上面去,而不是每分析儀1級,我現在要創建20個接口,20極短的子類有20個輸入/輸出豆類和沒有人會永遠重新使用。分析書籍和汽車很少在流程的任何地方使用相同的方法和步驟。
再次 - 我很好,做了上述,但我只是沒有看到任何好處,除了允許測試。這就像駕駛Toyota Thundra到你的隔壁鄰居的派對。你能做到與所有其他參加派對的人一樣嗎?當然。你應該這樣做嗎? Ehhh ...
所以:
- 難道真的更好地使在10個文件500線到800個文件5000行(可能不是完全正確的數字,但你明白了吧)剛剛跟隨OOP並啓用測試?
- 如果沒有,其他人是如何做的並且仍然保持在不破壞OOP /測試「規則」的方面(例如,通過使用反射來測試不應該首先測試的私有方法)?
- 如果是,其他人都這樣做,那很好。其實,那麼一個子問題 - 你如何設法找到你需要的東西,並在所有噪音中跟蹤應用程序的流程?
有時可以用assert對私有方法進行測試,並將複雜但效率較高的算法與簡單但很慢的實現進行比較,或者檢查通常只是假定存在但未驗證的輸入/輸出的不變量。它並不能解決所有情況,但可以減輕編寫單元測試的需要。 – the8472
@ the8472在這種特殊情況下,情況並非如此。我總體上同意,並且我總體上使用相同的原則,這非常有用。我在帖子中增加了更多信息以突出關注點/評論,現在可能會更清楚一些:) –
@levantpied關於更新的一些初步想法。 'letterCount'確實屬於'HumorEvaluatorOutput',它似乎屬於其他地方,除非這是一個幽默因素?你爲參數類建議使用'hashCode','equals'和'toString'方法,你真的需要這些嗎,你是否將一個HumourEvaluatorOutput與另一個HumourEvaluatorOutput相比較?比較書和汽車分析儀,比較書和章。能夠分析一本書的一個章節是否有意義?它看起來可能是這樣,然後這可以在書中多個章節重複使用。 – forsvarir