26、PHP 测试套件与伪造测试数据生成指南

PHP 测试套件与伪造测试数据生成指南

1. 编写测试套件

在处理包含大量类和文件的应用程序时,手动运行 phpunit 并指定测试类和 PHP 文件名会变得十分繁琐。不过, PHPUnit 项目具备使用单一命令运行多个测试的内置功能,这种一组测试的集合被称为测试套件。

1.1 操作步骤
  • 步骤 1:将所有测试文件移动到一个文件夹中
mkdir tests
cp *Test.php tests
  • 步骤 2:调整包含或引用外部文件的命令,以适应新的文件位置
<?php
use PHPUnit\Framework\TestCase;
require_once __DIR__ . '/../chap_13_unit_test_simple.php';
class SimpleTest extends TestCase
{
  // etc.
  • 步骤 3:以目录路径作为参数运行 phpunit ,它将自动运行该文件夹中的所有测试
phpunit tests
  • 步骤 4:使用 --bootstrap 选项指定在运行测试之前执行的文件,常用于启动自动加载
phpunit --boostrap tests_with_autoload/bootstrap.php tests
  • 步骤 5:示例 bootstrap.php 文件,用于实现自动加载
<?php
require __DIR__ . '/../../Application/Autoload/Loader.php';
Application\Autoload\Loader::init([__DIR__]);
  • 步骤 6:使用 XML 配置文件定义一组或多组测试
<phpunit>
  <testsuites>
    <testsuite name="simple">
      <file>SimpleTest.php</file>
      <file>SimpleDbTest.php</file>
      <file>SimpleClassTest.php</file>
    </testsuite>
  </testsuites>
</phpunit>
  • 步骤 7:另一个示例,基于目录运行测试并指定引导文件
<phpunit bootstrap="bootstrap.php">
  <testsuites>
    <testsuite name="visitor">
      <directory>Simple</directory>
    </testsuite>
  </testsuites>
</phpunit>
1.2 工作原理

首先确保之前定义的所有测试都已完成。然后创建一个 tests 文件夹,并将所有 *Test.php 文件移动或复制到该文件夹中。接着,按照步骤 2 调整 require_once 语句中的路径。

为了演示 PHPUnit 如何运行文件夹中的所有测试,从包含本章源代码的目录中运行以下命令:

phpunit tests

以下是创建包含自动加载和命名空间的单元测试的详细步骤:
1. 创建一个新的 tests_with_autoload 目录。
2. 在该文件夹中定义一个 bootstrap.php 文件,内容如步骤 5 所示。
3. 在 tests_with_autoload 中创建两个目录: Demo Simple
4. 从本章源代码目录中复制相关文件到 tests_with_autoload/Demo/Demo.php ,并在开头的 <?php 标签后添加 namespace Demo;
5. 将 SimpleTest.php 文件复制到 tests_with_autoload/Simple/ClassTest.php ,并修改前几行代码如下:

<?php
namespace Simple;
use Demo\Demo;
use PHPUnit\Framework\TestCase;
class ClassTest extends TestCase
{
  protected $demo;
  public function setup()
  {
    $this->demo = new Demo();
  }
  // etc.
  1. 创建一个 tests_with_autoload/phpunit.xml 文件,内容如下:
<phpunit bootstrap="bootstrap.php">
  <testsuites>
    <testsuite name="visitor">
      <directory>Simple</directory>
    </testsuite>
  </testsuites>
</phpunit>
  1. 切换到包含本章代码的目录,运行以下命令:
phpunit -c tests_with_autoload/phpunit.xml
2. 生成伪造测试数据

在测试和调试过程中,融入真实的测试数据是很重要的一部分。特别是在测试数据库访问和生成基准测试时,往往需要大量的测试数据。一种实现方法是从网站抓取数据,然后将这些数据以真实且随机的组合方式插入到数据库中。

2.1 操作步骤
  • 步骤 1:确定测试应用程序所需的数据,并考虑网站的受众范围
  • 步骤 2:将数据从源转换为可用的数字格式,首选是一系列数据库表,另一个选择是 CSV 文件
  • 步骤 3:可以分阶段转换数据,例如从网页上提取国家代码和国家名称列表到文本文件中
  • 步骤 4:由于列表较短,可以直接复制粘贴到文本文件中
  • 步骤 5:在文本文件中搜索 " " 并替换为 "\n"
  • 步骤 6:将文本文件导入电子表格,然后导出为 CSV 文件,再将其导入数据库,例如使用 phpMyAdmin
  • 步骤 7:创建目标表 prospects 的 SQL 语句
CREATE TABLE 'prospects' (
  'id' int(11) NOT NULL AUTO_INCREMENT,
  'first_name' varchar(128) NOT NULL,
  'last_name' varchar(128) NOT NULL,
  'address' varchar(256) DEFAULT NULL,
  'city' varchar(64) DEFAULT NULL,
  'state_province' varchar(32) DEFAULT NULL,
  'postal_code' char(16) NOT NULL,
  'phone' varchar(16) NOT NULL,
  'country' char(2) NOT NULL,
  'email' varchar(250) NOT NULL,
  'status' char(8) DEFAULT NULL,
  'budget' decimal(10,2) DEFAULT NULL,
  'last_updated' datetime DEFAULT NULL,
  PRIMARY KEY ('id'),
  UNIQUE KEY 'UNIQ_35730C06E7927C74' ('email')
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 步骤 8:创建一个能够生成伪造数据的类 FakeData
namespace Application\Test;
use PDO;
use Exception;
use DateTime;
use DateInterval;
use PDOException;
use SplFileObject;
use InvalidArgumentsException;
use Application\Database\Connection;
class FakeData
{
  // data generation methods here
}
  • 步骤 9:定义常量和属性
const MAX_LOOKUPS     = 10;
const SOURCE_FILE     = 'file';
const SOURCE_TABLE    = 'table';
const SOURCE_METHOD   = 'method';
const SOURCE_CALLBACK = 'callback';
const FILE_TYPE_CSV   = 'csv';
const FILE_TYPE_TXT   = 'txt';
const ERROR_DB        = 'ERROR: unable to read source table';
const ERROR_FILE      = 'ERROR: file not found';
const ERROR_COUNT     = 'ERROR: unable to ascertain count or ID  
                        column missing';
const ERROR_UPLOAD    = 'ERROR: unable to upload file';
const ERROR_LOOKUP    = 'ERROR: unable to find any IDs in the  
                        source table';
protected $connection;
protected $mapping;
protected $files;
protected $tables;
  • 步骤 10:定义用于生成随机字母、街道名称和电子邮件地址的属性
protected $alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
protected $street1 = ['Amber','Blue','Bright','Broad','Burning',
  'Cinder','Clear','Dewy','Dusty','Easy']; // etc. 
protected $street2 = ['Anchor','Apple','Autumn','Barn','Beacon',
  'Bear','Berry','Blossom','Bluff','Cider','Cloud']; // etc.
protected $street3 = ['Acres','Arbor','Avenue','Bank','Bend',
  'Canyon','Circle','Street'];
protected $email1 = ['northern','southern','eastern','western',
  'fast','midland','central'];
protected $email2 = ['telecom','telco','net','connect'];
protected $email3 = ['com','net'];
  • 步骤 11:在构造函数中接受数据库连接对象和映射数组
public function __construct(Connection $conn, array $mapping)
{
  $this->connection = $conn;
  $this->mapping = $mapping;
}
  • 步骤 12:生成街道名称的方法
public function getAddress($entry)
{
  return random_int(1,999)
   . ' ' . $this->street1[array_rand($this->street1)]
   . ' ' . $this->street2[array_rand($this->street2)]
   . ' ' . $this->street3[array_rand($this->street3)];
}
  • 步骤 13:生成英国邮政编码的方法
public function getPostalCode($entry, $pattern = 1)
{
  return $this->alpha[random_int(0,25)]
   . $this->alpha[random_int(0,25)]
   . random_int(1, 99)
   . ' '
   . random_int(1, 9)
   . $this->alpha[random_int(0,25)]
   . $this->alpha[random_int(0,25)];
}
  • 步骤 14:生成伪造电子邮件的方法
public function getEmail($entry, $params = NULL)
{
  $first = $entry[$params[0]] ?? $this->alpha[random_int(0,25)];
  $last  = $entry[$params[1]] ?? $this->alpha[random_int(0,25)];
  return $first[0] . '.' . $last
   . '@'
   . $this->email1[array_rand($this->email1)]
   . $this->email2[array_rand($this->email2)]
   . '.'
   . $this->email3[array_rand($this->email3)];
}
  • 步骤 15:生成日期的方法
public function getDate($entry, $params)
{
  list($fromDate, $maxDays) = $params;
  $date = new DateTime($fromDate);
  $date->sub(new DateInterval('P' . random_int(0, $maxDays) . 'D'));
  return $date->format('Y-m-d H:i:s');
}
  • 步骤 16:从文件中获取数据的方法
public function getEntryFromFile($name, $type)
{
  if (empty($this->files[$name])) {
      $this->pullFileData($name, $type);
  }
  return $this->files[$name][
  random_int(0, count($this->files[$name]))];
}
  • 步骤 17:从文件中提取数据到数组的方法
public function pullFileData($name, $type)
{
  if (!file_exists($name)) {
      throw new Exception(self::ERROR_FILE);
  }
  $fileObj = new SplFileObject($name, 'r');
  if ($type == self::FILE_TYPE_CSV) {
      while ($data = $fileObj->fgetcsv()) {
        $this->files[$name][] = trim($data);
      }
  } else {
      while ($data = $fileObj->fgets()) {
        $this->files[$name][] = trim($data);
      }
  }
}
  • 步骤 18:从数据库表中获取随机数据的方法
public function getEntryFromTable($tableName, $idColumn, $mapping)
{
  $entry = array();
  try {
      if (empty($this->tables[$tableName])) {
        $sql  = 'SELECT ' . $idColumn . ' FROM ' . $tableName 
          . ' ORDER BY ' . $idColumn . ' ASC LIMIT 1';
        $stmt = $this->connection->pdo->query($sql);
        $this->tables[$tableName]['first'] = 
          $stmt->fetchColumn();
        $sql  = 'SELECT ' . $idColumn . ' FROM ' . $tableName 
          . ' ORDER BY ' . $idColumn . ' DESC LIMIT 1';
        $stmt = $this->connection->pdo->query($sql);
        $this->tables[$tableName]['last'] = 
          $stmt->fetchColumn();
    }
    $result = FALSE;
    $count = self::MAX_LOOKUPS;
    $sql  = 'SELECT * FROM ' . $tableName 
      . ' WHERE ' . $idColumn . ' = ?';
    $stmt = $this->connection->pdo->prepare($sql);
    do {
      $id = random_int($this->tables[$tableName]['first'], 
        $this->tables[$tableName]['last']);
      $stmt->execute([$id]);
      $result = $stmt->fetch(PDO::FETCH_ASSOC);
    } while ($count-- && !$result);
    if (!$result) {
        error_log(__METHOD__ . ':' . self::ERROR_LOOKUP);
        throw new Exception(self::ERROR_LOOKUP);
    }
    foreach ($mapping as $key => $value) {
      $entry[$value] = $result[$key] ?? NULL;
    }
    return $entry;
  } catch (PDOException $e) {
    error_log(__METHOD__ . ':' . $e->getMessage());
    throw new Exception(self::ERROR_DB);
  }
}
  • 步骤 19:生成单个伪造数据数组的方法
public function getRandomEntry()
{
  $entry = array();
  foreach ($this->mapping as $key => $value) {
    if (isset($value['source'])) {
      switch ($value['source']) {
        case self::SOURCE_FILE :
            $entry[$key] = $this->getEntryFromFile(
            $value['name'], $value['type']);
          break;
        case self::SOURCE_CALLBACK :
            $entry[$key] = $value['name']();
          break;
        case self::SOURCE_TABLE :
            $result = $this->getEntryFromTable(
            $value['name'],$value['idCol'],$value['mapping']);
            $entry = array_merge($entry, $result);
          break;
        case self::SOURCE_METHOD :
        default :
          if (!empty($value['params'])) {
              $entry[$key] = $this->{$value['name']}(
                $entry, $value['params']);
          } else {
              $entry[$key] = $this->{$value['name']}($entry);
          }
      }
    }
  }
  return $entry;
}
  • 步骤 20:生成多行伪造数据的方法
public function generateData(
$howMany, $destTableName = NULL, $truncateDestTable = FALSE)
{
  try {
      if ($destTableName) {
        $sql = 'INSERT INTO ' . $destTableName
          . ' (' . implode(',', array_keys($this->mapping)) 
          . ') '. ' VALUES ' . ' (:' 
          . implode(',:', array_keys($this->mapping)) . ')';
        $stmt = $this->connection->pdo->prepare($sql);
        if ($truncateDestTable) {
          $sql = 'DELETE FROM ' . $destTableName;
          $this->connection->pdo->query($sql);
        }
      }
  } catch (PDOException $e) {
      error_log(__METHOD__ . ':' . $e->getMessage());
      throw new Exception(self::ERROR_COUNT);
  }
  for ($x = 0; $x < $howMany; $x++) {
    $entry = $this->getRandomEntry();
    if ($insert) {
      try {
          $stmt->execute($entry);
      } catch (PDOException $e) {
          error_log(__METHOD__ . ':' . $e->getMessage());
          throw new Exception(self::ERROR_DB);
      }
    }
    yield $entry;
  }
}
2.2 工作原理

首先确保有用于随机数据生成的数据。假设目标表是 prospects ,其 SQL 定义如步骤 7 所示。

作为名称的数据来源,可以创建包含名字和姓氏的文本文件,例如 first_names.txt surnames.txt 。对于城市、州或省、邮政编码和国家,可以从 http://www.geonames.org/ 等来源下载数据,并上传到 world_city_data 表中。对于其他字段,如地址、电子邮件、状态等,可以使用 FakeData 类中的方法或定义回调函数。

接下来,定义 Application\Test\FakeData 类,并添加步骤 8 到 29 中讨论的内容。完成后,创建一个名为 chap_13_fake_data.php 的调用程序,设置自动加载并使用相应的类。同时,定义与数据库配置路径和名称文件匹配的常量:

<?php
define('DB_CONFIG_FILE', __DIR__ . '/../config/db.config.php');
define('FIRST_NAME_FILE', __DIR__ . '/../data/files/first_names.txt');
define('LAST_NAME_FILE', __DIR__ . '/../data/files/surnames.txt');
require __DIR__ . '/../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/..');
use Application\Test\FakeData;
use Application\Database\Connection;
$mapping = [
  'first_name'   => ['source' => FakeData::SOURCE_FILE,
  'name'         => FIRST_NAME_FILE,
  'type'         => FakeData::FILE_TYPE_TXT],
  'last_name'    => ['source' => FakeData::SOURCE_FILE,
  'name'         => LAST_NAME_FILE,
  'type'         => FakeData::FILE_TYPE_TXT],
  'address'      => ['source' => FakeData::SOURCE_METHOD,
  'name'         => 'getAddress'],
  'email'        => ['source' => FakeData::SOURCE_METHOD,
  'name'         => 'getEmail',
  'params'       => ['first_name','last_name']],
  'last_updated' => ['source' => FakeData::SOURCE_METHOD,
  'name'         => 'getDate',
  'params'       => [date('Y-m-d'), 365*5]],
  'phone'        => ['source' => FakeData::SOURCE_CALLBACK,
  'name'         => function () {
                    return sprintf('%3d-%3d-%4d', random_int(101,999),
                    random_int(101,999), random_int(0,9999)); }],
  'status'       => ['source' => FakeData::SOURCE_CALLBACK,
  'name'         => function () { $status = ['BEG','INT','ADV']; 
                    return $status[rand(0,2)]; }],
  'budget'       => ['source' => FakeData::SOURCE_CALLBACK,
                     'name' => function() { return random_int(0, 99999) 
                     + (random_int(0, 99) * .01); }],
  'city' => ['source' => FakeData::SOURCE_TABLE,
  'name' => 'world_city_data',
  'idCol' => 'id',
  'mapping' => [
  'city' => 'city', 
  'state_province' => 'state_province',
  'postal_code_prefix' => 'postal_code', 
  'iso2' => 'country']
  ],
  'state_province'=> [],
  'postal_code'  => [],
  'country'    => [],
];
$destTableName = 'prospects';
$conn = new Connection(include DB_CONFIG_FILE);
$fake = new FakeData($conn, $mapping);
foreach ($fake->generateData(10) as $row) {
  echo implode(':', $row) . PHP_EOL;
}
3. 测试数据生成的数据源网站
数据类型 URL 备注
名字 http://nameberry.com/
http://www.babynamewizard.com/international-names-lists-popular-names-from-around-the-world
-
原始名字列表 http://deron.meranda.us/data/census-dist-female-first.txt
http://deron.meranda.us/data/census-dist-male-first.txt
http://www.avss.ucsb.edu/NameFema.HTM
http://www.avss.ucsb.edu/namemal.htm
分别为美国女性和男性名字
姓氏 http://names.mongabay.com/data/1000.html
http://surname.sofeminine.co.uk/w/surnames/most-common-surnames-in-great-britain.html
https://gist.github.com/subodhghulaxe/8148971
http://www.dutchgenealogy.nl/tng/surnames-all.php
http://www.worldvitalrecords.com/browsesurnames.aspx?l=A
分别为美国、英国、荷兰等姓氏
城市 http://www.travelgis.com/default.asp?framesrc=/cities/ 世界城市
4. 流程图
graph TD;
    A[确定测试数据需求] --> B[准备数据来源];
    B --> C[创建目标表];
    C --> D[定义FakeData类];
    D --> E[配置映射数组];
    E --> F[生成伪造数据];

通过以上步骤和方法,你可以方便地编写测试套件和生成伪造测试数据,从而更好地进行应用程序的测试和调试。在实际应用中,根据具体需求灵活调整数据来源和生成方法,以满足不同的测试场景。同时,注意代码的优化和错误处理,确保测试过程的稳定性和可靠性。

PHP 测试套件与伪造测试数据生成指南

5. 测试套件与伪造数据生成的应用场景分析

在实际的 PHP 项目开发中,测试套件和伪造测试数据生成有着广泛的应用场景。下面我们通过几个具体的场景来深入了解它们的重要性。

5.1 新功能开发测试

当开发新的功能模块时,为了确保其正确性和稳定性,需要进行全面的测试。通过编写测试套件,可以快速地对新功能进行单元测试。例如,在开发一个用户注册功能时,可以编写一系列的测试用例,包括正常注册、重复注册、注册信息不完整等情况。同时,使用伪造测试数据生成工具,可以为这些测试用例提供各种不同类型的测试数据,如不同格式的电子邮件地址、不同长度的密码等,从而更全面地验证新功能的健壮性。

5.2 数据库迁移测试

在进行数据库迁移时,需要确保数据的完整性和一致性。可以使用测试套件来验证迁移前后的数据是否正确。例如,在将数据从一个旧的数据库表迁移到新的表结构时,可以编写测试用例来检查新表中的数据是否与旧表一致。而伪造测试数据生成则可以帮助模拟大量的真实数据,用于测试数据库迁移的性能和稳定性。

5.3 性能测试

在进行性能测试时,需要模拟大量的用户请求和数据。测试套件可以用于编写性能测试用例,而伪造测试数据生成则可以提供大量的测试数据,以模拟真实的用户场景。例如,在测试一个电商网站的商品搜索功能时,可以使用伪造测试数据生成工具生成大量的商品数据,然后使用测试套件来模拟不同数量的用户同时进行搜索操作,从而评估系统的性能。

6. 常见问题及解决方案

在使用测试套件和伪造测试数据生成的过程中,可能会遇到一些常见的问题。下面我们对这些问题进行分析,并提供相应的解决方案。

6.1 测试套件运行失败
  • 问题描述 :在运行测试套件时,可能会出现部分或全部测试用例失败的情况。
  • 可能原因
    • 测试代码存在逻辑错误。
    • 被测试的代码发生了变更,导致测试用例不再适用。
    • 测试环境与开发环境不一致,例如数据库配置不同。
  • 解决方案
    • 仔细检查测试代码,确保逻辑正确。
    • 及时更新测试用例,以适应被测试代码的变更。
    • 确保测试环境与开发环境一致,检查数据库配置、依赖库等。
6.2 伪造数据生成异常
  • 问题描述 :在生成伪造测试数据时,可能会出现数据格式错误、数据重复等问题。
  • 可能原因
    • 数据生成方法的逻辑存在问题。
    • 数据源文件或数据库表的格式不符合要求。
  • 解决方案
    • 检查数据生成方法的代码,确保逻辑正确。
    • 验证数据源文件或数据库表的格式,进行必要的调整。
7. 总结与展望

通过本文的介绍,我们了解了如何编写 PHP 测试套件和生成伪造测试数据。测试套件可以帮助我们提高代码的质量和稳定性,而伪造测试数据生成则可以为测试提供丰富的测试数据,使测试更加全面和真实。

在未来的 PHP 开发中,测试和调试将变得越来越重要。随着项目规模的不断增大和复杂度的提高,自动化测试和数据模拟将成为必不可少的工具。同时,我们也可以进一步探索如何优化测试套件和伪造数据生成的性能,提高测试效率。例如,可以使用并行测试技术来加快测试套件的运行速度,使用更智能的算法来生成更真实的伪造数据。

8. 参考流程图
graph LR;
    A[新功能开发] --> B[编写测试套件];
    B --> C[生成伪造测试数据];
    C --> D[执行测试用例];
    D --> E{测试结果};
    E -- 失败 --> F[检查代码与数据];
    F --> B;
    E -- 成功 --> G[功能上线];
    H[数据库迁移] --> B;
    I[性能测试] --> B;
9. 总结表格
技术点 作用 操作步骤
测试套件 提高代码质量和稳定性,快速验证功能正确性 1. 创建测试文件夹并移动测试文件;2. 调整文件路径;3. 运行 phpunit 命令;4. 使用 --bootstrap 选项;5. 配置 XML 文件
伪造测试数据生成 提供丰富测试数据,模拟真实场景 1. 确定数据需求;2. 准备数据来源;3. 创建目标表;4. 定义 FakeData 类;5. 配置映射数组;6. 生成伪造数据

通过合理运用测试套件和伪造测试数据生成技术,我们可以更加高效地开发和维护 PHP 项目,确保项目的质量和稳定性。在实际应用中,要根据具体需求灵活调整和优化这些技术,以适应不同的项目场景。

基于径向基函数神经网络RBFNN的自适应滑模控制学习(Matlab代码实现)内容概要:本文介绍了基于径向基函数神经网络(RBFNN)的自适应滑模控制方法,并提供了相应的Matlab代码实现。该方法结合了RBF神经网络的非线性逼近能力和滑模控制的强鲁棒性,用于解决复杂系统的控制问题,尤其适用于存在不确定性和外部干扰的动态系统。文中详细阐述了控制算法的设计思路、RBFNN的结构权重更新机制、滑模面的构建以及自适应律的推导过程,并通过Matlab仿真验证了所提方法的有效性和稳定性。此外,文档还列举了大量相关的科研方向和技术应用,涵盖智能优化算法、机器学习、电力系统、路径规划等多个领域,展示了该技术的广泛应用前景。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的研究生、科研人员及工程技术人员,特别是从事智能控制、非线性系统控制及相关领域的研究人员; 使用场景及目标:①学习和掌握RBF神经网络滑模控制相结合的自适应控制策略设计方法;②应用于电机控制、机器人轨迹跟踪、电力电子系统等存在模型不确定性或外界扰动的实际控制系统中,提升控制精度鲁棒性; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,深入理解算法实现细节,同时可参考文中提及的相关技术方向拓展研究思路,注重理论分析仿真验证相结合。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值