Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/318.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/visual-studio-2012/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 面向对象的分解与单元测试困境_Java_Unit Testing_Oop - Fatal编程技术网

Java 面向对象的分解与单元测试困境

Java 面向对象的分解与单元测试困境,java,unit-testing,oop,Java,Unit Testing,Oop,假设我有这样的代码: class BookAnalysis { final List<ChapterAnalysis> chapterAnalysisList; } class ChapterAnalysis { final double averageLettersPerWord; final int stylisticMark; final int wordCount; // ... 20 more fields } interface B

假设我有这样的代码:

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, ...);
   }
 }
这很好,只是现在我有了
HumorEvaluator
,我需要这个:

class HumorEvaluatorInput {
  final int letterCount;
  final int stylisticMark;
  // ... 5 more things it might need
}
虽然在许多情况下,这可能只是通过列出参数来实现,但一个大问题是返回参数。即使我必须返回两个int,我也必须创建一个单独的bean,其中包含这两个int、constructor、equals/hashCode和getter

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行代码-哎呀

所以这一切都很好,但它:

  • 不可重复使用。其中绝大多数将仅用于使代码可测试。请考虑以下分析器:
    BookAnalyzer
    CarAnalyzer
    GrainAnalyzer
    TootAnalyzer
    。他们毫无共同之处
  • 在1个类中设置20个类,除了允许测试外,不会产生太多结果
  • 你可以争辩说,无论它被划分成类还是方法,从使部分小到足以被理解和操作的角度来看,区别并没有那么大
  • 另一方面,如果我在考虑可测试性的情况下进行适当的OOP,那么会增加大量的干扰和间接性。比较:
    • 管理10个文件=10个分析器*1个文件和20个专用方法
    • 800个文件=10个分析器*(20个接口、20个实现、20个输入和20个输出bean)
    • 400个文件,如果我们删除输入/输出bean并采用其他方法(例如每个analyzer hack使用一个大I/O bean) 请注意,数百个文件将非常短,大部分是样板文件-可能大多数逻辑都在10行以下(=第一种情况下的私有方法)
  • 这里面有很大的开销。如果我调用一个私有方法一百万次,那么创建额外的输入和输出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行代码-仍然是yikes!当你考虑一个例子的时候并不多。当你有20个不同的分析器(
BookAnalyzer
CarAnalyzer
,…)和20个不同的子分析器(对于上面的书:
ComplexSytlisticaAppraiser
HumoralEvaluator
和所有其他分析器类似,显然是不同的类别),代码增加了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
如上所述,我现在必须创建20个接口、20个极短的子类和20个输入/输出bean,而不是每个分析器1个类,并且没有一个类会被重用。在分析书籍和汽车的过程中,很少使用相同的方法和步骤

再说一次——我对做上述工作很满意,但我只是觉得除了允许测试之外,没有任何好处。这就像开车去隔壁邻居的派对。你能做到和其他来参加聚会的人一样吗?当然你应该这样做吗?呃

因此:

  • 仅仅为了遵循OOP并启用测试,将10个文件中的500行转换为800个文件中的5000行(可能不是完全正确的数字,但你明白了这一点)真的更好吗
  • 如果没有,其他人如何做到这一点,并且仍然站在不违反OOP/测试“规则”的一边(例如,通过使用反射来测试不应该首先测试的私有方法)
  • 如果是的,其他人都是这样做的,好吧。事实上,还有一个子问题——你如何找到你需要的东西,并在如此嘈杂的环境中遵循应用程序的流程

您是对的,测试私有方法通常被认为是不好的做法。然而,理解为什么会这样很重要,因为只有这样你才能判断它是否适用于你的案件

反对测试私有方法的主要理由是,测试代码的维护工作量预计会增加:如果软件设计得当,私有元素比公共元素更容易被更改。因此,如果测试只使用公共API,那么保持测试代码构建和正常工作的工作量就会减少。(他们最好只使用关于测试代码的黑盒智慧,但这是另一回事……)

看看你的例子,我认为方法
countlets
可能是私有的。然而,这个名称给我的印象是,这个方法可能会在代码中实现一个易于理解且稳定的概念。如果是这样的话,一些替代的设计选项将是将这个方法分解成它自己的类——它将不再是私有的

然而,这个想法并不意味着建议您考虑这个函数(您可以这样做,但这不是我的观点)。关键是要明确,这一切归结为一个问题,即某段代码的稳定性如何


这种对稳定性的期望必须与测试的努力相权衡:首先,测试私有元素可能更容易(这节省了您的努力),但从长远来看,这可能会让您付出代价。也许长期成本永远不会超过短期收益。您必须对此作出判断。

您是对的,测试私有方法通常被认为不是一种好的做法。然而,重要的是要理解为什么会这样,因为只有这样你才能判断我
BookAnalyzer
  ChapterSubAnalyzer
  HumorSubAnalyzer
  ... // 25 more
CarAnalyzer
  EngineSubAnalyzer
  DrivertrainSubAnalyzer
  ... // 15 more
GrainAnalyzer
  LiquidContentSubAnalyzer
  FiberContentSubAnalyzer
  ... // 20 more
class BookAnalyserImpl implements BookAnalyser
    // Pass in analyser factory and book parser
    // to constructor so mocked version can
    // be used for testing
    public BookAnalyserImpl(TextAnalyserFactory textAnalyserFactory,
                            BookParser bookParser) {
        if(null != textAnalyserFactory) {
           mTextAnalyserFactory = textAnalyserFactory;
        } else {
           mTextAnalyserFactory = new AnalyserFactoryImpl();
        }
        // Same for bookParser
    }
    BookAnalysis analyse(String bookText) {
        BookAnalysis bookAnalysis = new BookAnalysis();
        ChapterAnalyser chapterAnalyser = mTextAnalyserFactory.GetChapterAnalyser();

        foreach(chapterText in mBookParser.splitIntoChapters(bookText)) {
            bookAnalysis.AddChapterAnalysis(chapterAnalyser.analyse(chapterText));
        }
    }
}

class TextAnalyserFactoryImpl implements TextAnalyserFactory {
    ChapterAnalyser GetChapterAnalyser() {...}
}

class ChapterAnalyserImpl implements ChapterAnalyser {
     ChapterAnalysis analyse(String chapterText) { ... }
}
class BookAnalyser {
    ChapterAnalysis analyseChapter(String text)  { ... }
    PageAnalysis analysePage(String text) {...}
    // ...
}
HumorEvaluation humorEvaluation = evaluateHumor(letterCount, stylisticMark);
class GenericTextAnalyserImpl implements GenericTextAnalyser {
    int countLetters(String text);
    int countWords(String text);
    int complexSytlisticAppraising(int letterCount, int wordCount);
    // ...
}