Test Content Generator

Header image for Test Content Generator

Quickly generate a test site full of random users, posts, comments, tags and images.

6 minute reading time of 1227 words (inc code) Code available at codeberg.org and the last commit was Plugin available at WordPress


Developing any WP plugin or theme often means needing to test it against as much of a “real site” as your development environment will allow, and if you’re adding that test data by hand, it gets pretty tedious pretty quickly.

So, many years after it would have been really useful, I’ve finally got around to writing a plugin to automate that process.

You can quickly add test users, populate the Media Library with example images, add an additional custom post type and custom taxonomies, generate as many test “Lorem Ipsum” posts as you want, and then add comments to all those posts - and each of those “Lorem Ipsum” posts can get randomly tagged and categorised, and can also be assigned one of the test images as its Featured Image, as well as a variety of HTML content in addition to the usual paragraphs of lipsum.


The main plugin file - test-content-generator.php - is where the common plugin init stuff gets handled (eg. language and activation hooks, checking if you’re in the admin or if you’re using WP-CLI etc) and because there were an increasing number of things that I wanted to generate, it then loads up a different file depending on what you’re doing.

$this->tabs = [
  'users'      => __('Users', 'TestContentGenerator'),
  'custom'     => __('Custom', 'TestContentGenerator'),
  'taxonomies' => __('Taxonomies', 'TestContentGenerator'),
  'images'     => __('Images', 'TestContentGenerator'),
  'posts'      => __('Posts', 'TestContentGenerator'),
  'comments'   => __('Comments', 'TestContentGenerator'),

That array of key => name pairs represents the tabs available in the admin UI, and the key gets used as the corresponding filename holding the class - ie. if you’re on the Posts tab, it will include the tab_posts.php file, and create an instance of the TCG_Posts class which will then register the admin settings WordPress needs.


If I wasn’t careful, splitting the functionality up into multiple tab_thing.php files would make my life harder in the long run; I’d inevitably start doing the fifth type of test content slightly different from the first type and that’s never a good idea when it’ll be many months til the next time I look at the code…

In order to try to keep some consistency, each of the TCG_Thing classes implement the AbstractTCG generic class to enforce certain standards.

Whenever a TCG_Thing class is instantiated, the abstraction will use their class name as an identifier (basically the prefix that gets used in the WP options) and set a flag to show if they’re called via a WP-CLI command. Then, it will call three functions that need to be defined in the child class:

  • one to set some sensible defaults for that particular test content,
  • one to make the various settings and UI elements of the admin form, and
  • one to handle the particular sanitisation requirements of that class.

In addition to containing the common utility functions each child class uses - handling log messages, parsing input into an array, making a dropdown admin menu etc - the abstract class holds the run() function, without which nothing gets made.

This run() function ensures that each type of test content gets its generator called in the same way, and that the differences between being called from the WP-CLI command or via the admin page are ironed out before that child function needs to care.

public function run(array|null $input = [], bool $save = false): array {
  // detect multiple sanitising passes, for an old bug: https://core.trac.wordpress.org/ticket/21989
  static $pass_count = 0; $pass_count++; if ($pass_count > 1) return [];
  // re-sanitise our form/cli input
  // save it if desired
  if (! $this->cli or $save) update_option($this->ident, $this->options);
  // make a progress bar if cli
  $progress = ($this->cli and isset($this->options['amount'])) ? \WP_CLI\Utils\make_progress_bar(
    sprintf(__('%s: running', 'TestContentGenerator'), $this->ident), $this->options['amount'], 100
  ) : null;
  // pass it to the generator in the child class
  $this->create($this->options, $progress);
  // and return it to WP
  return $this->options;

WP CLI Integration

This is a fairly straightforward process, which only really differs (for me at least) in that long function comments aren’t optional - it’s the way WP-CLI automatically populates your CLI help text, and you want that.

Write a class as normal, registering it at the end of the file with WP_CLI::add_command('<SHORTCODE>', '<CLASS_NAME>'); and all your public methods are instantly available on the command line as wp <SHORTCODE> <METHOD>. Your PHPDoc style comments are there under wp help <SHORTCODE> and wp help <SHORTCODE> <METHOD>.

In this particular case, wp-cli-commands.php is almost all help text - the individual commands are merely wrappers that instantiate the right class (as above) and pass their arguments over to it:

public function posts(array $args, array $named) {
  self::run('posts', $named);

private static function run(string $classname, array $named) {
  $instance = self::load($classname);
  $named = wp_parse_args($named, $instance->options);
  $instance->run($named, (isset($named['save']) and $named['save'] == 1));

private static function load(string $classname): object|false {
  $filename = __DIR__.'/tab_'.$classname.'.php';
  if (! file_exists($filename)) return false;
  require_once $filename;
  $class = 'TCG_'.ucfirst($classname);
  return (new $class());

And at the end of this, I can now recreate a fresh test site with a simple script. Meaning I’ve got no excuse for not simply checking that my old plugins still work with WP version 123…

Reply via email