【问题标题】:Should a function that simply passes an argument to another function do type checking on that argument?一个简单地将参数传递给另一个函数的函数是否应该对该参数进行类型检查?
【发布时间】:2015-01-31 09:05:15
【问题描述】:

给定同一类中的以下两个(简单/随机)PHP方法:

/*
 * @param $a An int to play with
 * @param $b An int to play with
 * @param $c An int to play with
 *
 * @throws InvalidArgumentException when either $a, $b, or $c are not an int
 *
 * @return A new int
 */
public function1($a, $b, $c) {
    if(!is_int($a)) throw new InvalidArgumentException('$a must be an int');
    if(!is_int($b)) throw new InvalidArgumentException('$b must be an int');

    $x = $a * $b;

    $y = $this->function2($c);

    return $x - $y;
}

/*
 * @param $c An int to play with
 *
 * @throws InvalidArgumentException when $c is not an int
 *
 * @return A new int
 */
private function2($c) {
    if(!is_int($c)) throw new InvalidArgumentException('$c must be an int');

   return $c + 1;
}

两部分问题:

  • function1() 是否也应该检查 $c 的参数类型?
  • 在测试时,比如使用 PHPUnit,是否足以测试 function2 的错误参数,还是我还应该编写第二个测试来测试将错误的 $c 传递给 function1?

function2()有可能被function1()以外的其他函数调用。

一方面,我认为函数应该检查赋予它的所有内容。另一方面,我觉得这可能会导致大量重复且(虽然没有这些特定功能)成本高昂的代码。

【问题讨论】:

  • 我认为没有一个正确答案。我喜欢史蒂夫麦康奈尔建议的方法 - 检查公共方法中的所有参数,而内部实现应该假设所有数据都是有效的。关于测试,IMO 第二种方式会过度测试,所以不需要。
  • @RomaKliuchko 我也喜欢这样,但是这样做会阻止我为私有方法编写完整的单元测试,不是吗?我觉得我应该能够完全测试 method1 而无需调用 method2 来测试 method1 的参数。
  • 如果 function2() 可以被其他方法调用,那么将 $c 参数移动到类属性并为其创建 setter 方法可能是有意义的。使用这种方法,您只需测试一次 setC($c) 方法即可避免代码重复。
  • 我同意。我也不认为有正确的答案。但是,因为评论清楚地指出 C 是一个 int 并且它会在 A、B 或 C 上引发异常。我认为这是你不应该破坏的信任。当然,异常仍然会被抛出并冒泡,但对我来说,代码越可读和易理解越好。我想我可以争辩说,你可能想以同样的方式对另一个函数进行单元测试。可能更容易看到所有内容都已涵盖,并且更容易测试单个方法,而无需弄清楚要调用哪个其他方法。
  • 您在function2 中的代码引用$a,而不是$c 我假设这是错字,但我没有更新它,因为我真的不喜欢更新其他人代码示例...

标签: php unit-testing testing exception-handling phpunit


【解决方案1】:

function1 是否检查参数c 并不重要,这在很大程度上是一种风格选择。有些人喜欢在函数开始时执行 ALL 检查,因为这意味着函数可以尽快中止,而不会发生任何不必要的处理。如果在调用function2 之前进行了重要处理,那么将有更多的理由进行检查,但就目前而言,重要的是在实际使用参数之前对其进行检查。

就您的第二个问题而言,是的,您应该测试将错误参数传递给function1。就个人而言,我认为您不应该测试将错误参数传递给function2。事实上,从单元测试的角度来看,你甚至不应该知道function2 的存在。

我不是 PHP 程序员,所以如果这太离谱了,请随意投反对票,但根据我使用过的其他语言,类的 公共方法 决定公共接口因此该类的 可测试 api。换句话说,如果我是您班级的客户,我可以调用您班级的任何公共方法,包括function1。当客户端调用公共方法时,可以测试某些期望(输入/输出/执行的处理),但是客户端不应该知道或关心这些期望是否在一种方法中或通过使用多种方法全部满足/强制执行方法。因此,从客户的角度来看,您的代码可能是这样编写的:

/*
 * @param $a An int to play with
 * @param $b An int to play with
 * @param $c An int to play with
 *
 * @throws InvalidArgumentException when either $a, $b, or $c are not an int
 *
 * @return A new int
 */
public function1($a, $b, $c) {
    if(!is_int($a)) throw new InvalidArgumentException('$a must be an int');
    if(!is_int($b)) throw new InvalidArgumentException('$b must be an int');
    if(!is_int($c)) throw new InvalidArgumentException('$c must be an int');

    $x = $a * $b;

    $y = $c + 1;

    return $x - $y;
}

如果您最初是这样编写代码并为此行为编写测试的,那么您可以通过添加 function2 与其他方法共享功能来重构您的代码,因为您知道公共接口测试是安全的确保课程仍然按预期对客户执行。这就是您不时听到的“自信重构”一词的来源。

如果您开始测试所有私有方法,那么您最终会将测试与实现(而不是行为)或类紧密耦合。这使得您在不破坏测试的情况下重构代码变得更加困难,并且您可能会发现测试更多的是开销而不是好处。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-25
    • 2020-10-03
    • 2014-01-06
    • 2019-10-09
    • 2017-08-07
    • 1970-01-01
    相关资源
    最近更新 更多