单一责任/单位可测性与实用性的平衡

时间:2017-03-11 14:25:30

标签: unit-testing oop solid-principles

我仍然对单元测试感到困惑。假设我有一些微不足道的东西:

class x {
    zzz someMethod(some input...) {
        BufferedImage image = getter.getImageFromFile(...);

        // determine resize mode:
        int width = image.getWidth();
        int height = image.getHeight();

        Scalr.Mode resizeMode = (width > height) ? Scalr.Mode.FIT_TO_WIDTH : Scalr.Mode.FIT_TO_HEIGHT;

        return ScalrWrapper.resize(image, resizeMode);
    }
}

遵循规则, Scalr.Mode resizeMode = 应该是一个单独的类,以便更好地进行上述方法的单元可测试性,如下所示:

class xxx {
    mode getResizeMode(int width, int height)
    {
       return (width > height) ? Scalr.Mode.FIT_TO_WIDTH : Scalr.Mode.FIT_TO_HEIGHT;
    }
}

class x {
   zzz someMethod(some input...) {
        BufferedImage image = getter.getImageFromFile(...);

        // determine resize mode:
        int width = image.getWidth();
        int height = image.getHeight();

        Scalr.Mode resizeMode = xxx.getResizeMode(width, height);

        return ScalrWrapper.resize(image, resizeMode);
    }
}

但它看起来像是一种矫枉过正......我不确定哪一种更好但我想这种方式更好。假设我走这条路,这样做会更好吗?

class xxx {
    mode getResizeMode(Image image)
    {
        return (image.getWidth() > image.getHeight()) ? Scalr.Mode.FIT_TO_WIDTH : Scalr.Mode.FIT_TO_HEIGHT;
    }
}

class x {
   void someMethod(some input...) {
        BufferedImage image = getter.getImageFromFile(...);

        // determine resize mode:
        Scalr.Mode resizeMode = xxx.getResizeMode(image);

        return ScalrWrapper.resize(image, resizeMode);
    }
}

根据我的理解,正确的方法是getResizeMode接受整数,因为它与属性为width和height的数据类型分离。但是,就我个人而言,使用getResizeMode(BufferedImage)实际上证明了更好地创建单独的类,因为从main方法中删除了一些更多的工作。因为我不打算在我的应用程序中使用getResizeMode来处理除BufferedImage之外的任何类型的数据,所以没有可重用性的问题。此外,如果我认为由于YAGNI原则而不需要它,我不认为我应该只是为了重用性而使用getResizeMode(int,int)。 所以我的问题是:根据OOD在真实世界中,getResizeMode(BufferedImage)是一个好方法吗?我理解它的教科书好OOD,但后来我一直认为100%的教科书OOD在现实世界中是不切实际的。因此,当我试图学习OOD时,我只想知道我应该遵循哪条路径。 ...或者我应该把所有内容都放在一个方法中,就像在第一个代码片段中一样?

3 个答案:

答案 0 :(得分:2)

我不认为调整大小模式计算会影响可测试性。 至于单一责任: "一个班级应该只有一个改变的理由" (https://en.wikipedia.org/wiki/Single_responsibility_principle)。

您认为调整大小模式计算会发生变化吗? 如果没有,那么只需要在需要此模式的类中。 这不会为该课程添加任何改变的理由。

如果计算可能会改变(和/或可能有多个版本) 然后将其移至一个单独的类(使其成为一种策略)

答案 1 :(得分:0)

实现单一责任原则(SRP)不是每次都要创建新类,而是提取方法。此外,SRP取决于具体情况。

模块应该与SRP有关 一个班级应该关注SRP SRP应该关注一种方法。

Bob叔叔发来的消息是:Extract till you Drop
除了他说:

  

也许你认为这样做太过分了。我曾经也这么认为。但是经过40多年的编程后,我开始得出这样的结论:这种提取水平并没有太过分。

在决定创建新课程时,请牢记指标高凝聚力。内聚是模块元素在一起的程度。如果所有方法都在一个特定的上下文和同一组变量中工作,则它们属于一个类。

回到你的案子。我会提取所有方法并将它们放在课堂上。这一课也很容易测试。

答案 2 :(得分:0)

  

派对有点晚了,但这是我的2c。

在我看来,x类并不是出于其他原因而坚持SRP。

目前负责

  • 从文件中获取图像(getter.getImageFromFile)
  • 调整图片大小

TL; DR

TL; DR就是这两种方法都很好,而且事实上两者都坚持 - 不同程度的粘性 - 给SRP。但是,如果你想非常紧密地遵守SRP(这往往会产生非常可测试的代码),你可以先将它分成三个类:

Orchestrator的

class imageResizeService
{
    ImageGetter _getter;
    ImageResizer _resizer;

    zzz ResizeImage(imageName)
    {
         image=_getter.GetImage(imageName);
         resizedImage=_resizer.ResizeImage(image);
         return resizedImage;
    }
}
  • 本课程负有单一责任;即,给定一个图像名称, 根据某些条件返回它的大小调整版本。
  • 为此,它编排了两个依赖项。但它只有一个改变的理由,即用于获取和调整图像大小的过程 一般,已经改变了。
  • 您可以通过模拟getterresizer并测试按顺序调用它们来轻松地对此进行单元测试,使用{{1}给出的数据调用resizer最终返回值等于getter返回的值,依此类推(即" White Box"测试)

ImageGetter

resizer
  • 同样,我们有一个责任(从磁盘加载图像,然后返回)。
  • 改变这个类的唯一原因是如果加载图像的机制要改变 - 例如您是从数据库而不是磁盘加载。
  • 这里有一个有趣的说明,这个类已经成熟,可以进一步推广 - 例如,能够使用class ImageGetter { BufferedImage GetImage(imageName) { image=io.LoadFromDisk(imageName) or explode; return image; } } BufferedImageBuilder抽象来构建它,这可能有多个磁盘,数据库实现, Http等等,但现在是RawImageDataGetter和另一天的对话:)

关于可测试性的注意事项

就单元测试而言,你可能遇到一个小问题,即你不能完全"单元测试"它 - 除非您的框架作为文件系统的模拟。在这种情况下,您可以进一步抽象原始数据的加载(按照前一段),或者接受它,然后只对已知的好文件执行集成测试。这两种方法都是完全有效的,你不必担心你选择哪种方式 - 对你来说更容易。

ImageResizer

YAGNI
  • 此课程只有一个作业,可以调整图像大小。
  • class ImageResizer { zzz ResizeImage(image) { int width = image.getWidth(); int height = image.getHeight(); Scalr.Mode resizeMode = getResizeMode(width, height); return ScalrWrapper.resize(image, resizeMode); } private mode getResizemode(width, height) { return (width > height) ? Scalr.Mode.FIT_TO_WIDTH : Scalr.Mode.FIT_TO_HEIGHT; } } 方法 - 当前只是保持代码清洁的私有方法 - 应该是一个单独的责任的问题必须在该操作是否以某种方式独立于图像调整大小。
  • 即使不是,但SRP仍然被跟踪,因为它是单一责任的一部分"调整图像大小"。
  • 测试方面,这也很容易测试,因为它甚至没有跨越任何边界(你可以创建并提供唯一的依赖 - 图像 - 在测试运行时)你可能会赢得甚至需要嘲笑。

我个人我会把它提取到一个单独的类中,这样我就可以单独验证给定宽度大于高度的时候,我返回了getResizeMode和副-versa;这也意味着我可以坚持Open Closed Principle,从而可以引入新的缩放模式而无需修改Scalr.Mode.FIT_TO_WIDTH类。

但确实

这里的答案必须取决于它;例如,如果你有一个简单的方法来验证,给定宽度为100和高度为99,那么调整大小的图像确实缩放到"适合宽度"那你真的不需要。

话虽如此,我怀疑如果你 将其提取到一个单独的方法,你会更容易测试它。

  

请记住,如果您正在使用具有良好重构工具的体面IDE,那么这应该不会超过几次击键,所以不要担心开销。