Jelajahi Sumber

fix typos remove extra test section

bradford.condon 6 tahun lalu
induk
melakukan
12580d9121
1 mengubah file dengan 78 tambahan dan 156 penghapusan
  1. 78 156
      docs/dev_guide/custom_data_loader.rst

+ 78 - 156
docs/dev_guide/custom_data_loader.rst

@@ -1,15 +1,13 @@
 Creating Custom Data Loaders
 ==============================
 
+.. note::
 
-.. warning::
-
-  This documentation is currently under development.  There is a corresponding walkthrough on youtube that will be posted soon.
-
+  This guide is also available as a `video on youtube. <https://www.youtube.com/watch?v=5y8rDtDQEg0>`_
 
 The ``TripalImporter`` class can be extended to create your own data loader.  This class provides many conveniences to simplify loader construction. For example, it simplifies and unifies input form development, automatically handles files upload by the user, provides job submission, logging and progress updates. Using the TripalImporter to create your loader also makes it easy to share your loader with other Tripal users!
 
-To document how to create a new importer, we will describe use of the ``TripalImporter`` class within the context of a new simple importer called the ``ExampleImpoter``. This importer will read in a comma-separated file containing genomic features and their properties.  The loader will split each line into feature and property values, and then insert each property into the ``featureprop`` table of Chado using a controlled vocabulary term (supplied by the user) as the ``type_id`` for the property. 
+To document how to create a new importer, we will describe use of the ``TripalImporter`` class within the context of a new simple importer called the ``ExampleImpoter``. This importer will read in a comma-separated file containing genomic features and their properties ( a fictional "Test Format" file).  The loader will split each line into feature and property values, and then insert each property into the ``featureprop`` table of Chado using a controlled vocabulary term (supplied by the user) as the ``type_id`` for the property.
 
 .. note::
   Prior to starting your data loader you should plan how the data will be imported into Chado. Chado is a flexible database schema and it may be challenging at times to decide in to which tables data should be placed.  It is recommended to reach out to the Chado community to solicit advice. Doing so will allow you to share your loader will other Tripal users more easily!
@@ -27,11 +25,11 @@ To define a new class that extends ``TripalImporter``, you should create a new c
   class ExampleImporter extends TripalImporter {
   }
 
-There is no need to include the importer via a ``require_once`` statement in your module file. Placing it in the ``/includes/TripalImporter/`` directory of your module is all you need for Tripal to find it. Tripal will automatically place a link for your importer at ``admin -> Tripal -> Data Loaders``.  
+There is no need to include the importer via a ``require_once`` statement in your module file. Placing it in the ``/includes/TripalImporter/`` directory of your module is all you need for Tripal to find it. Tripal will automatically place a link for your importer at ``admin -> Tripal -> Data Loaders``.
 
 .. note::
 
-  If after creation of your importer file, Tripal does not show a link for it in the Data Loaders page, check that you have named your class file correctly and it is in the path described above. Sometimes a clear cache is neccessary (``drush cc all``).
+  If after creation of your importer file, Tripal does not show a link for it in the Data Loaders page, check that you have named your class file correctly and it is in the path described above. Sometimes a clear cache is necessary (``drush cc all``).
 
 
 Static Variables
@@ -40,7 +38,7 @@ The next step in creation of your importer is setting the static member variable
 
 .. note::
 
-  For the sake of simplicity in this document, many of the default settings are not changed, and threfore, not all are included.
+  For the sake of simplicity in this document, many of the default settings are not changed, and therefore, not all are included.
 
 Our ``ExampleImporter`` class now appears as follows:
 
@@ -51,36 +49,36 @@ Our ``ExampleImporter`` class now appears as follows:
    * @see TripalImporter
    */
    class ExampleImporter extends TripalImporter {
-   
+
     /**
      * The name of this loader.  This name will be presented to the site
      * user.
      */
     public static $name = 'Example TST File Importer';
-  
+
     /**
      * The machine name for this loader. This name will be used to construct
      * the URL for the loader.
      */
     public static $machine_name = 'tripal_tst_loader';
-  
+
     /**
      * A brief description for this loader.  This description will be
      * presented to the site user.
      */
     public static $description = 'Loads TST files';
-  
+
     /**
      * An array containing the extensions of allowed file types.
      */
     public static $file_types = ['txt', 'tst', 'csv'];
-  
+
     /**
      * Provides information to the user about the file upload.  Typically this
      * may include a description of the file types allowed.
      */
     public static $upload_description = 'TST is a fictional format.  Its a 2-column, CSV file.  The columns should be of the form featurename, and text';
-    
+
     /**
      * Indicates the methods that the file uploader will support.
      */
@@ -107,12 +105,12 @@ Now that we've given our importer a name and description, it will show up at ``/
 Form Components
 -----------------
 
-By default, the ``TripalImporter`` class will provide the necessary upload widgets to allow a user to upload files for import.  The static variables we set in the previous step dictate how that uploader appears to the user.  However, for this example, our importer needs additional information from the user before data can be loaded.  We need to provide additional form widgets.  
+By default, the ``TripalImporter`` class will provide the necessary upload widgets to allow a user to upload files for import.  The static variables we set in the previous step dictate how that uploader appears to the user.  However, for this example, our importer needs additional information from the user before data can be loaded.  We need to provide additional form widgets.
 
-Typically, to create forms, Drupal provides form hooks: ``form``, ``form_validate``, ``form_submit``. The **TripalImporter** wraps these for us as class functions named ``form``, ``formValidate`` and ``formSubmit``.  We can override these class functions to provide additional widgets to the form.  
+Typically, to create forms, Drupal provides form hooks: ``form``, ``form_validate``, ``form_submit``. The **TripalImporter** wraps these for us as class functions named ``form``, ``formValidate`` and ``formSubmit``.  We can override these class functions to provide additional widgets to the form.
 
 .. note::
-  
+
   Typically we only need to implement the ``form`` and ``formValidate`` functions. The ``formSubmit`` does not need to be modified.
 
 .. note::
@@ -122,7 +120,7 @@ Typically, to create forms, Drupal provides form hooks: ``form``, ``form_validat
 
 The form function
 ^^^^^^^^^^^^^^^^^
-To provide custom widgets for our importer we need to implement the ``form`` function.  However, let's review the current form provided by the TripalImporter for us already.  Using the static variables settings specified above the form automatically provides a **File Upload** field set, and an **Analysis** selector.  The **File Upload** area lets users choose to upload a file, provide a **Server path** to a file already on the web server or a **Remote path** for files located via a downloadable link on the web.   The **Analysis** selector is important because it allows the user to specify an analysis that describes how the data file was created. 
+To provide custom widgets for our importer we need to implement the ``form`` function.  However, let's review the current form provided by the TripalImporter for us already.  Using the static variables settings specified above the form automatically provides a **File Upload** field set, and an **Analysis** selector.  The **File Upload** area lets users choose to upload a file, provide a **Server path** to a file already on the web server or a **Remote path** for files located via a downloadable link on the web.  The **Analysis** selector is important because it allows the user to specify an analysis that describes how the data file was created.
 
 .. image:: ./custom_data_loader.1.oob_file_interface.png
 
@@ -130,30 +128,29 @@ To provide custom widgets for our importer we need to implement the ``form`` fun
 
 For our example TST file importer these upload options are sufficient.  However, for our data import we want the user provide a CV term.  We want our importer to read the file, split it into feature and values, and insert properties into the ``featureprop`` table of Chado using the the CV term as the ``type_id`` for the table.
 
-To add a widget that allows the user to provide a CV term, we must implement the ``form`` function and include code using Drupal's Form API that will add the widget.  
+To add a widget that allows the user to provide a CV term, we must implement the ``form`` function and include code using Drupal's Form API that will add the widget.
 
 .. code-block:: php
   :name: ExampleImporter::form
 
 
   public function form($form, &$form_state) {
-    
 
-    // For our example loader let's assume that there is a small list of 
-    // vocbaulry terms that are appropriate as proprties for the genomics
-    // fatures. Therfore, we will provide an array of sequence ontology terms 
+
+    // For our example loader let's assume that there is a small list of
+    // vocabulary terms that are appropriate as properties for the genomics
+    // features. Therefore, we will provide an array of sequence ontology terms
     // the user can select from.
     $terms = [
-      ['id' => 'SO:0000235'], 
-      ['id' => 'SO:0000238'], 
+      ['id' => 'SO:0000235'],
+      ['id' => 'SO:0000238'],
       ['id' => 'SO:0000248']
     ];
 
     // Construct the options for the select drop down.
     $options = [];
-    $options[0] = '--please select an option--';
     // Iterate through the terms array and get the term id and name using
-    // appropriate Tripal API functions.  
+    // appropriate Tripal API functions.
     foreach ($terms as $term){
       $term_object = chado_get_cvterm($term);
       $id = $term_object->cvterm_id;
@@ -166,7 +163,8 @@ To add a widget that allows the user to provide a CV term, we must implement the
       '#description' => 'Please pick a CVterm.  The loaded TST file will associate the values with this term as a feature property.',
       '#type' => 'select',
       '#default_value' => '0',
-      '#options' => $options
+      '#options' => $options,
+      '#empty_option' => '--please select an option--'
     ];
 
     // The form function must always return our form array.
@@ -194,7 +192,7 @@ The ``formValidate`` function is responsible for verifying that the user supplie
 .. code-block:: php
 
   public function formValidate($form, &$form_state) {
-  
+
     // Always call the TripalImporter (i.e. parent) formValidate as it provides
     // some important feature needed to make the form work properly.
     parent::formValidate($form, $form_state);
@@ -209,12 +207,16 @@ The ``formValidate`` function is responsible for verifying that the user supplie
 
 The implementation above looks for the ``pick_cvterm`` element of the ``$form_state`` and ensures the user selected something.  This is a simple example. An implementation for a more complex loader with a variety of widgets will require more validation checks.
 
+.. note::
+
+
+
 When an importer form is submitted and passes all validation checks, a job is automatically added to the **Tripal Job** system. The ``TripalImporter`` parent class does this for us! The **Tripal Job** system is meant to allow long-running jobs to execute behind-the-scenes on a regular time schedule.  As jobs are added they are executed in order.  Therefore, if a user submits a job using the importer's form then the **Tripal Job** system will automatically run the job the next time it is scheduled to run or it can be launched manually by the site administrator.
 
 
 Importer Execution
 ------------------
-The ``form`` and ``formValidate`` functions allow our Importer to receive an input file and additional values needed for import of the data.  To execute loading a file the ``TripalImporter`` provides several additional overridable functions:  ``run``, ``preRun`` and ``postRun``.  When the importer is executed, the ``preRun`` function is called first. It allows the importer to perform setup prior to full execution.  The ``run`` function is where the full exeuction occurs and the ``postRun`` function is used to perform "cleanup" prior to completion. For our ``ExampleImporter`` class we only need to implement the ``run`` function.  We have no need to perform any setup or cleanup outside of the typical run.
+The ``form`` and ``formValidate`` functions allow our Importer to receive an input file and additional values needed for import of the data.  To execute loading a file the ``TripalImporter`` provides several additional overridable functions:  ``run``, ``preRun`` and ``postRun``.  When the importer is executed, the ``preRun`` function is called first. It allows the importer to perform setup prior to full execution.  The ``run`` function is where the full execution occurs and the ``postRun`` function is used to perform "cleanup" prior to completion. For our ``ExampleImporter`` class we only need to implement the ``run`` function.  We have no need to perform any setup or cleanup outside of the typical run.
 
 The run function
 ^^^^^^^^^^^^^^^^
@@ -229,10 +231,10 @@ The ``run`` function is called automatically when Tripal runs the importer. For
     public function run() {
 
       // All values provided by the user in the Importer's form widgets are
-      // made available to us here by the Class' arguments member variable.  
+      // made available to us here by the Class' arguments member variable.
       $arguments = $this->arguments['run_args'];
-      
-      // The path to the uploaded file is always made avilable using the
+
+      // The path to the uploaded file is always made available using the
       // 'files' argument. The importer can support multiple files, therefore
       // this is an array of files, where each has a 'file_path' key specifying
       // where the file is located on the server.
@@ -241,8 +243,8 @@ The ``run`` function is called automatically when Tripal runs the importer. For
       // The analysis that the data being imported is associated with is always
       // provided as an argument.
       $analysis_id = $arguments['analysis_id'];
-      
-      // Any of the widgets on our form are also avilable as an argument.
+
+      // Any of the widgets on our form are also available as an argument.
       $cvterm_id = $arguments['pick_cvterm'];
 
       // Now that we have our file path, analysis_id and CV term we can load
@@ -253,11 +255,11 @@ The ``run`` function is called automatically when Tripal runs the importer. For
 
 .. note::
 
-  We do not need to validate in the ``run`` function that all of the necessary values in the arguments array are valid.  Remember, this was done by the ``formValidate`` function when the user submitted the form.  Threfore, we can trust that all of the necessary values we need for the import are correct.  That is of course provided our ``formValidate`` function sufficiently checks the user input.
+  We do not need to validate in the ``run`` function that all of the necessary values in the arguments array are valid.  Remember, this was done by the ``formValidate`` function when the user submitted the form.  Therefore, we can trust that all of the necessary values we need for the import are correct.  That is of course provided our ``formValidate`` function sufficiently checks the user input.
 
 Importing the File
 ^^^^^^^^^^^^^^^^^^
-To keep the ``run`` function small, we will implement a new function named ``loadMyFile`` that will perfrom parsing and import of the file into Chado. As seen in the code above, the ``loadMyFile`` function is called in the ``run`` function. 
+To keep the ``run`` function small, we will implement a new function named ``loadMyFile`` that will perform parsing and import of the file into Chado. As seen in the code above, the ``loadMyFile`` function is called in the ``run`` function.
 
 Initially, lets get a feel for how the importer will work.  Lets just print out the values provided to our importer:
 
@@ -268,14 +270,14 @@ Initially, lets get a feel for how the importer will work.  Lets just print out
     var_dump(["this is running!", $analysis_id, $file_path, $cvterm]);
   }
 
-To test our importer navigate to ``admin > Tripal > Data Importers`` and click the link for our TFT importer. Fill out the form and press submit.  If there are no validation errors, we'll receive notice that our job was submitted and given a command to execute the job manually. For example: 
+To test our importer navigate to ``admin > Tripal > Data Importers`` and click the link for our TFT importer. Fill out the form and press submit.  If there are no validation errors, we'll receive notice that our job was submitted and given a command to execute the job manually. For example:
 
 ..
 
   drush trp-run-jobs --username=admin --root=/var/www/html
 
 
-If we execute our importer we should see the following output: 
+If we execute our importer we should see the following output:
 
 
 .. code-block:: bash
@@ -313,24 +315,24 @@ To import data into Chado we will use the Tripal API. After splitting each line
   public function loadMyFile($analysis_id, $file_path, $cvterm_id){
 
     // We want to provide a progress report to the end-user so that they:
-    // 1) Recognize that the loader is not hung if running a large file, but is 
-    //    executing  
-    // 2) Provides some idicatation for how long the file will take to load.
+    // 1) Recognize that the loader is not hung if running a large file, but is
+    //    executing
+    // 2) Provides some indicatation for how long the file will take to load.
     //
     // Here we'll get the size of the file and tell the TripalImporter how
     // many "items" we have to process (in this case bytes of the file).
     $filesize = filesize($file_path);
     $this->setTotalItems($filesize);
     $this->setItemsHandled(0);
-    
+
     // Loop through each line of file.  We use the fgets function so as not
     // to load the entire file into memory but rather to iterate over each
-    // line seprately.
+    // line separately.
     $bytes_read = 0;
     while ($line = fgets($file_path)) {
-    
-      // Caluculate how many bytes we have read from the file and let the 
-      // importer know how many have been processed so it can provide a 
+
+      // Calculate how many bytes we have read from the file and let the
+      // importer know how many have been processed so it can provide a
       // progress indicator.
       $bytes_read += drupal_strlen($line);
       $this->setItemsHandled($bytes_read);
@@ -350,39 +352,39 @@ To import data into Chado we will use the Tripal API. After splitting each line
          continue;
       }
 
-      // Using the name of te feature from the file, see if we can find a
+      // Using the name of the feature from the file, see if we can find a
       // record in the feature table of Chado that matches.  Note: in reality
       // the feature table of Chado has a unique contraint on the uniquename,
       // organism_id and type_id columns of the feature table.  So, to ensure
       // we find a single record ideally we should include the organism_id and
-      // type_id in our filter and that would require more widgets on our form!  
-      // For sipmlicity, we will just search on the uniquename and hope we
+      // type_id in our filter and that would require more widgets on our form!
+      // For simplicity, we will just search on the uniquename and hope we
       // find unique features.
       $match = ['uniquename' => $feature_name];
       $results = chado_select_record('feature', ['feature_id'], $match);
-      
-      // The chado_select_record funtion always returns an array of matches. If
+
+      // The chado_select_record function always returns an array of matches. If
       // we found no matches then this feature doesn't exist and we'll skip
       // this line of the file.  But, log this issue so the user knows about it.
       if (count($results) == 0) {
-        $this->logMessage('The feature, !feature, does not exist in the database', 
-          ['!feature' => $feature_name], TRIPAL_WARNING); 
+        $this->logMessage('The feature, !feature, does not exist in the database',
+          ['!feature' => $feature_name], TRIPAL_WARNING);
         continue;
       }
-      
+
       // If we failed to find a unique feature then we should warn the user
       // but keep on going.
       if (count($results) == 0) {
         $this->logMessage('The feature, !feature, exists multiple times. ' .
-          'Cannot add a property', ['!feature' => $feature_name], TRIPAL_WARNING); 
+          'Cannot add a property', ['!feature' => $feature_name], TRIPAL_WARNING);
         continue;
       }
-      
-      // If we've made it this far then we have a feature and we can do the 
+
+      // If we've made it this far then we have a feature and we can do the
       // insert.
       $feature = $results[0];
       $record = [
-        'table' => 'feature', 
+        'table' => 'feature',
         'id' => $feature->feature_id
       ];
       $property = [
@@ -391,7 +393,7 @@ To import data into Chado we will use the Tripal API. After splitting each line
       ];
       $options = ['update_if_present' => TRUE];
       chado_insert_property($record, $property, $options);
-    } 
+    }
   }
 
 Logging and Progress
@@ -401,19 +403,19 @@ During execution of our importer it is often useful to inform the user of progre
 The logMessage function
 ^^^^^^^^^^^^^^^^^^^^^^^
 The ``logMessage`` function is meant to allow the importer to provide status messages to the user while the importer is running.  The function takes three arguments:
- 
+
 1) a message string.
 2) an array of substitution values.
-3) a message status.  
+3) a message status.
 
 The message string contains the message for the user.  You will notice that no variables are included in the string but rather tokens are used as placeholders for variables.  This is a security feature provided by Drupal.  Consider these lines from the code above:
 
 .. code-block:: php
 
-  $this->logMessage('The feature, !feature, does not exist in the database', 
-    ['!feature' => $feature_name], TRIPAL_WARNING); 
-    
-Notice that ``!feature`` is used in the message string as a placeholder for the feature name. The mapping of ``!feature`` to the actualy feature name is providedin the array provided as the second argument.  The third argument supports several message types including ``TRIPAL_NOTICE``, ``TRIPAL_WARNING`` and ``TRIPAL_ERROR``.  The message status indicates a severity level for the message.  By default if no message type is provided the message is of type ``TRIPAL_NOTICE``.
+  $this->logMessage('The feature, !feature, does not exist in the database',
+    ['!feature' => $feature_name], TRIPAL_WARNING);
+
+Notice that ``!feature`` is used in the message string as a placeholder for the feature name. The mapping of ``!feature`` to the actually feature name is provided in the array provided as the second argument.  The third argument supports several message types including ``TRIPAL_NOTICE``, ``TRIPAL_WARNING`` and ``TRIPAL_ERROR``.  The message status indicates a severity level for the message.  By default if no message type is provided the message is of type ``TRIPAL_NOTICE``.
 
 Any time the ``logMessage`` function is used the message is stored in the job log, and a site admin can review these logs by clicking on the job in the ``admin > Tripal > Tripal Jobs`` page.
 
@@ -423,17 +425,17 @@ Any time the ``logMessage`` function is used the message is stored in the job lo
 
 The setTotalItems and setItemsHandled functions
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The ``TripalImporter`` class is capable of providing progress updates to the end-user while the importer job is running. This is useful as it gives the end-user a sense for how long the job will take. As shown in the sample code above for the ``loadMyFile`` function, The first step is to tell the ``TripalImporter`` how many items need processing.  An **item** is an arbitray term indicating some measure of countable "units" that will be processed by our importer.  
+The ``TripalImporter`` class is capable of providing progress updates to the end-user while the importer job is running. This is useful as it gives the end-user a sense for how long the job will take. As shown in the sample code above for the ``loadMyFile`` function, The first step is to tell the ``TripalImporter`` how many items need processing.  An **item** is an arbitrary term indicating some measure of countable "units" that will be processed by our importer.
 
-In the code above we consider a byte as an item, and when all bytes from a file are read we are done loading that file.  Threfore the ``setTotalItems`` function is used to tell the importer how many bytes we need to process.  As we read each line, we count the number of bytes read and provide that number to the ``setItemsHandled`` function.  The ``TripalImporter`` class will automatically calcaulte progress and print a message to the end-user indicating the percent complete, and some additional details such as the total amount of memory consumed during the loading.
+In the code above we consider a byte as an item, and when all bytes from a file are read we are done loading that file.  Therefore the ``setTotalItems`` function is used to tell the importer how many bytes we need to process.  As we read each line, we count the number of bytes read and provide that number to the ``setItemsHandled`` function.  The ``TripalImporter`` class will automatically calculate progress and print a message to the end-user indicating the percent complete, and some additional details such as the total amount of memory consumed during the loading.
 
 .. note::
-  
+
   All importers are different and the "item" need not be the number of bytes in the file.  However, if you want to provide progress reports you must identify an "item" and the total number of items there are for processing.
-          
+
 Testing Importers
 ------------------
-Unit Testing is a critically important component of any software project. You should always strie to write tests for your software.  Tripal provides unit testing using the ``phpunit`` testing framework. The Tripal Test Suite provides a strategy for adding tests for your new Importer.  It will automatically set up and bootstrap Drupal and Tripal for your testing environment, as well as provide database transactions for your tests, and factories to quickly generate data.  We will use the Tripal Test Suite to provide unit testing for our ``ExampelImporter``.
+Unit Testing is a critically important component of any software project. You should always strive to write tests for your software.  Tripal provides unit testing using the ``phpunit`` testing framework. The Tripal Test Suite provides a strategy for adding tests for your new Importer.  It will automatically set up and bootstrap Drupal and Tripal for your testing environment, as well as provide database transactions for your tests, and factories to quickly generate data.  We will use the Tripal Test Suite to provide unit testing for our ``ExampelImporter``.
 
 .. note::
   Before continuing, please install and configure Tripal Test Suite.
@@ -454,12 +456,12 @@ When developing tests, consider including a small example file as this is good p
 
 Loading the Importer
 ^^^^^^^^^^^^^^^^^^^^
-Testing your loader requires a few setup steps.  First, TripalImporters are not explicitly loaded in your module (note that we never use ``include_once()`` or ``require_once`` in the ``.module`` file).  Normally Tripal finds the importer automatically, but for unit testing we must include it to our test class explicitly.  Second, we must initialize an instance of our importer class. Aftewards we can perform any tests to ensure our loader executed properly.  The following function provides an example for setup of the loader for testing:
+Testing your loader requires a few setup steps.  First, TripalImporters are not explicitly loaded in your module (note that we never use ``include_once()`` or ``require_once`` in the ``.module`` file).  Normally Tripal finds the importer automatically, but for unit testing we must include it to our test class explicitly.  Second, we must initialize an instance of our importer class. Afterwards we can perform any tests to ensure our loader executed properly.  The following function provides an example for setup of the loader for testing:
 
 .. code-block:: php
 
   private function run_loader(){
-  
+
     // Load our importer into scope.
     module_load_include('inc', 'tripal_example_importer', 'includes/TripalImporter/ExampleImporter');
 
@@ -469,12 +471,12 @@ Testing your loader requires a few setup steps.  First, TripalImporters are not
       'cvterm' => $some_cvterm_id
     ];
     $file = ['file_local' => __DIR__ . '/../data/exampleFile.txt'];
-    
+
     // Create a new instance of our importer.
     $importer = new \ExampleImporter();
     $importer->create($run_args, $file);
-     
-    // Before we run our loader we must let the TripalImporter prepare the 
+
+    // Before we run our loader we must let the TripalImporter prepare the
     // files for us.
     $importer->prepareFiles();
     $importer->run();
@@ -483,83 +485,3 @@ Testing your loader requires a few setup steps.  First, TripalImporters are not
 .. note::
 
   We highly recommend you make use of database transactions in your tests, especially when running loaders.  Simply add ``use DBTransaction;`` at the start of your test class.  Please see the `Tripal Test Suite documentation for more information <https://tripaltestsuite.readthedocs.io/en/latest/>`_.
-
-
-Test Data
-^^^^^^^^^
-You will note that our test has a few requirements.  It needs the features (test_gene_1 and test_gene_2) and the analysis.  You could load this data separately, but then the test will fail for new developers until they also create the features and analysis.
-
-Instead, you can use **Chado Factories** to quickly and easily provide unique features, analyses, or whatever else you may need for your test to run.  This data is created for each test, and, if wrapped in a DBTransaction, is removed when the test finishes.
-
-.. note::
-  To learn more about Chado Factories, please see the `Tripal Test Suite documentation <https://tripaltestsuite.readthedocs.io/en/latest/>`_.
-
-
-We could use factories for ``$some_cvterm_id`` as well, but because our form actually forces the user to choose from predefined cvterms, let's pick one of those instead.
-
-
-
-Using factories, our test might look something like this now.
-
-  .. code-block:: php
-
-    private function run_loader(){
-      $some_analysis_id = factory('chado.analysis')->create()->analysis_id;
-      factory('chado.feature')->create(['uniquename' => 'test_gene_1', 'name' => 'test_gene_1']);
-      factory('chado.feature')->create(['uniquename' => 'test_gene_2', 'name' => 'test_gene_2']);
-
-      $some_cvterm_id = chado_get_cvterm(['id' => 'SO:0000235']);
-
-      $run_args =
-        'analysis_id' => $some_analysis_id,
-        'cvterm' => $some_cvterm_id
-      ];
-      $file = ['file_local' => __DIR__ . '/../data/exampleFile.txt'];
-
-      module_load_include('inc', 'tripal_example_importer', 'includes/TripalImporter/ExampleImporter');
-       $importer = new \ExampleImporter();
-       $importer->create($run_args, $file);
-       $importer->prepareFiles();
-       $importer->run();
-    }
-
-
-Writing the Test
-^^^^^^^^^^^^^^^^
-
-Below is an example test.  Note that it runs the importer, then uses the values in the ``run_loader`` method to retrieve the property that loader inserted.
-
-.. code-block:: php
-
-  /**
-   * Test that the loader runs and adds a property.
-   * The test file will associate "blue" with test_gene_1
-   *
-   * @group test_ExampleImporter
-   * @group chado
-   *
-   */
-  public function test_ExampleImporter_adds_test_props(){
-
-    $this->run_loader();
-
-    $type = chado_get_cvterm(['id' => 'SO:0000235'])->cvterm_id;
-
-    $query = db_select('chado.featureprop', 'fp');
-    $query->join('chado.feature', 'f', 'f.feature_id = fp.feature_id');
-    $query->condition('f.uniquename', 'test_gene_1');
-    $query->condition('fp.type_id', $type);
-    $query->fields('fp', ['value']);
-    $result = $query->execute()->fetchField();
-    $this->assertEquals('blue', $result);
-
-
-  }
-
-Note that the test name begins with ``test_``.  This tells Tripal Test Suite that this function is a test and should be run as such.  The test itself runs the loader, then queries the db to retrieve the ``featureprop`` record that should have been created in the first line of the example file.  It then uses an **assertion** to check that the value retrieved is, in fact, the same value in the test file.  PHPUnit has many assertions: please `read the documentation <https://phpunit.de/manual/>`_ for more information on the many assertion types available.
-
-Note also that we use the ``@group`` tag: this lets us run specific subsets of tests.
-
-To run the test from the command line, we can ``phpunit --group test_exampleImporter`` to **just run tests associated with this importer!**  This is very helpful if you have many tests.
-
-Once the test is passing, we can refactor the importer as much as we'd like.  So long as the test passes, we have confidence that our refactoring has not broken the code.