优化使用不同参数测试相同方法的单元测试
Optimizing unit tests which testing same methods with different parameters
我有很多 PHP 类 这样的:
class Article
{
public function fetchAll($params) {
if ($params["client"] == "mobile") {
return [
"author" => "",
"body" => ""
];
}
return [
"author" => "",
"body" => "",
"publishedOn => """
];
}
}
如您所见,fetchAll()
方法根据 $params["client"]
返回不同的字段。
我需要检查这些字段是否存在。我有 3 个选择来实现我的目标。
(注意:所有代码都简化了,不用担心语法错误。)
方法一 : 不同client
s
的不同测试方法
class ArticleTest extends ...
{
public function testFetchAll() {
$params = [];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body","publishedOn"]);
}
public function testFetchAll2() {
$params = ["client" => "mobile"];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body"]);
}
}
缺点: 一次又一次地使用相同的代码,这是一种非常糟糕的做法。无需解释。重构真的很难。还有这个方法会导致几十个测试方法。
方法 2:对不同的 client
s
使用 for 循环
class ArticleTest extends ...
{
public function testFetchAll() {
for ($i=0; $i<2; $i++) {
$params = [];
if ($i == 1) $params["client"] = "mobile";
$data = $article->fetchAll($params);
if ($i == 0)
$this->assertEqual(array_keys($data), ["author","body","publishedOn"]);
else
$this->assertEqual(array_keys($data), ["author","body"]);
}
}
}
缺点: 我不确定在 PHP 单元测试中使用循环是否是个好主意。代码可读性也下降了。
方法 3: 不同的测试用例/测试文件用于不同的 client
s
// articleTest.php
class ArticleTest extends ...
{
public function testFetchAll() {
$params = [];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body","publishedOn"]);
}
}
// articleTest2.php
class ArticleTest2 extends ...
{
public function testFetchAll() {
$params = ["client" => "mobile"];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body"]);
}
}
缺点: 这种方法导致了几十个测试文件,并且一次又一次地使用相同的代码。重构真的很难。
性能比较
方法 1:时间:127 毫秒,内存:8.00Mb
方法 2:时间:125 毫秒,内存:8.00Mb
方法 3:时间:96 毫秒,内存:8.00Mb
我正在尝试了解哪种方法更好,有很多像这样的 类,并等待关于在这种情况下优化单元测试的讨论。
编辑:即使您的问题侧重于优化,实际上您的案例也有一个被广泛接受的最佳实践,所以这就是我在回答中所展示的。
只需使用 dataProviders。使用这些,您可以执行相同的测试,将参数传递给测试方法。参数可以是参数或 / 和预期结果的变体。
所以你可以这样做:
public function fetchAllTestData() {
return [
// case 1
[
[], // params
['author', 'body', 'publishedon'] // expectedKeys
],
// case 2
[
['client' => 'mobile'], // params
['author', 'body'] // expectedKeys
],
];
}
/**
* @dataProvider fetchAllTestData
*/
public function testFetchAll(array $params, array $expectedKeys) {
$data = $article->fetchAll($params);
$this->assertEqual($expectedKeys, array_keys($data));
}
这样,测试将针对每个 dataProvider 行独立执行。这意味着如果第一个案例失败,第二个案例将被测试,因此您将知道它是否也失败了。如果在测试用例中使用循环,每当出现故障时,其余的用例将不会被测试。
看看dataProviders documentation。
注意 编写测试时请注意断言方法参数的顺序。通常预期的数据首先出现。如果您以相反的顺序传递它们,错误消息将具有误导性,并且数组和对象比较会告诉您缺少行,而实际上这些行不应该存在。
我有很多 PHP 类 这样的:
class Article
{
public function fetchAll($params) {
if ($params["client"] == "mobile") {
return [
"author" => "",
"body" => ""
];
}
return [
"author" => "",
"body" => "",
"publishedOn => """
];
}
}
如您所见,fetchAll()
方法根据 $params["client"]
返回不同的字段。
我需要检查这些字段是否存在。我有 3 个选择来实现我的目标。
(注意:所有代码都简化了,不用担心语法错误。)
方法一 : 不同client
s
class ArticleTest extends ...
{
public function testFetchAll() {
$params = [];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body","publishedOn"]);
}
public function testFetchAll2() {
$params = ["client" => "mobile"];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body"]);
}
}
缺点: 一次又一次地使用相同的代码,这是一种非常糟糕的做法。无需解释。重构真的很难。还有这个方法会导致几十个测试方法。
方法 2:对不同的 client
s
class ArticleTest extends ...
{
public function testFetchAll() {
for ($i=0; $i<2; $i++) {
$params = [];
if ($i == 1) $params["client"] = "mobile";
$data = $article->fetchAll($params);
if ($i == 0)
$this->assertEqual(array_keys($data), ["author","body","publishedOn"]);
else
$this->assertEqual(array_keys($data), ["author","body"]);
}
}
}
缺点: 我不确定在 PHP 单元测试中使用循环是否是个好主意。代码可读性也下降了。
方法 3: 不同的测试用例/测试文件用于不同的 client
s
// articleTest.php
class ArticleTest extends ...
{
public function testFetchAll() {
$params = [];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body","publishedOn"]);
}
}
// articleTest2.php
class ArticleTest2 extends ...
{
public function testFetchAll() {
$params = ["client" => "mobile"];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body"]);
}
}
缺点: 这种方法导致了几十个测试文件,并且一次又一次地使用相同的代码。重构真的很难。
性能比较
方法 1:时间:127 毫秒,内存:8.00Mb
方法 2:时间:125 毫秒,内存:8.00Mb
方法 3:时间:96 毫秒,内存:8.00Mb
我正在尝试了解哪种方法更好,有很多像这样的 类,并等待关于在这种情况下优化单元测试的讨论。
编辑:即使您的问题侧重于优化,实际上您的案例也有一个被广泛接受的最佳实践,所以这就是我在回答中所展示的。
只需使用 dataProviders。使用这些,您可以执行相同的测试,将参数传递给测试方法。参数可以是参数或 / 和预期结果的变体。
所以你可以这样做:
public function fetchAllTestData() {
return [
// case 1
[
[], // params
['author', 'body', 'publishedon'] // expectedKeys
],
// case 2
[
['client' => 'mobile'], // params
['author', 'body'] // expectedKeys
],
];
}
/**
* @dataProvider fetchAllTestData
*/
public function testFetchAll(array $params, array $expectedKeys) {
$data = $article->fetchAll($params);
$this->assertEqual($expectedKeys, array_keys($data));
}
这样,测试将针对每个 dataProvider 行独立执行。这意味着如果第一个案例失败,第二个案例将被测试,因此您将知道它是否也失败了。如果在测试用例中使用循环,每当出现故障时,其余的用例将不会被测试。
看看dataProviders documentation。
注意 编写测试时请注意断言方法参数的顺序。通常预期的数据首先出现。如果您以相反的顺序传递它们,错误消息将具有误导性,并且数组和对象比较会告诉您缺少行,而实际上这些行不应该存在。