Gambas Unit Testing

gb.test is a Gambas component for unittesting and test-driven-development (TDD). With this component you can create tests for your Gambas program, you can develop software in a test-driven matter (write test first, program functionality afterwards) and you are able to ensure that on refactoring or when you enhance your software the desired results of your code stays the same. Therefore gb.test is helpful to ensure better stability and higher reliability of Gambas programs.

You can write the tests in special test classes (class files with the ending '.test').

gb.test runs the tests and the results are output as a TAP string together with a summary. You can start the testing inside the Gambas IDE and watch the progress of the testing as well as the result in the Unit Tests tab. The summary at the end shows "PASSED" or "FAILED" so you can easily see if your tests have been successful or not. But they also can be displayed, analyzed or viewed with any Tap consumer.

You can also run the tests of your project on the command line and so gb.test provides the tools to run tests automatically.

How gb.test works

gb.test is there to test a project from the inside. It can access all procedures and variables that are public within a project.

By default, gb.test executes all tests that exist in a project. The tests are grouped into test modules. test modules are classes with the extension '.test'. They contain public methods (we call them 'test methods'). The names of the test methods must not begin with '_'.

gb.test organizes the order in which the test methods are called in test modules in alphabetical order. This results in this sequence of execution:

TestModuleA.TestMethodA
TestModuleA.TestMethodB
...
TestModuleB.TestMethodA
TestModuleB.TestMethodB

The test methods themselves contain the actual tests, which are performed with methods of the class 'Assert'. The class 'Assert' provides various assertions, which are checked in the gb.test process.

For example, the assertion 'Assert.Equals(Got as String, Expected as String)' can be used to test whether the content of the variable 'Got' is identical to the content of the variable 'Expected'.

If yes, gb.test outputs 'ok', if no, gb.test outputs 'not ok' to Stdout.

So basically gb.test works through all assertions, in all test methods in all test modules of a project, checks the assertions and outputs the result to stdout. In addition, gb.test also runs a statistic and at the end it outputs a summary that shows if and which errors were found.

If you don't want gb.test to perform all tests it finds in a project, you can limit its work with so-called test suites to single or groups of tests. A test-suite is basically nothing more than a string containing the names of the desired tests that gb.test should execute.

Example

There is an example in this simple Gambas project.

Test module

In test driven development it is common practice to first write the test that the future program will later fulfill. Therefore we start by creating a test module.

This is a class, named like any other Gambas class with the ending ".test", for example "THelloWorld.test". This class contains one or more tests, these are public subs whose name must not contain an underscore:

' Gambas test module file
''' Test module THelloWorld

Public Sub AHelloWorld()

  Test.Note("I am the first test, because my name starts with A.")
  Assert.Equals(Hello.World(), "Hello World", "HW strings should be equal")

End

With the methods provided by the Assert class you are able to test the output of any public state (variable, constant or function) in your project.

Module (Function) to test:

Now that we have written the test, it is time to develop the program that will fulfill the test. So we create a function "World" in a module "Hello" in our project:

' Gambas module file

''' Module is named "Hello"

'' Returns "World"
Public Function World() As String
   
  Return "Hello World"
    
End

Running the test

If you are working with the Gambas IDE you can hit F4 to let your tests run. The IDE will show you the "Unit tests" tab with this content:

1..1

Test THello:AHelloWorld
  # I am the first test, because my name starts with A.
  ok 1 - HW strings should be equal
  1..1
ok 1 - THello:AHelloWorld

# Ran: 'THello.AHelloWorld'
#
# PASSED

If a failure occurs it will report FAILED instead of PASSED and will show you the place of the failure. If you want to debug the code you can set a breakpoint anywhere in your test or production code, hit <F4> again and start debugging.

Test modules and test methods can be named the same way as any Gambas module or method except that a test method may not be named _Setup(), _Teardown(), _SetupEach() or _TeardownEach() and it's name must not contain an underscore.

Testsuites

If you hit Shift-F4 the Gambas IDE lets you create so called "test suites". This is convenient if you have a lot of tests but need to run just one or a couple of them by chance. You can create a lot of test suites and use them to test different aspects of your project separately.

Testsuites are stored in the file .test in the project's path.

Test your project on the console

You also can test your project on the console. It has to be compiled first. The command gbx3 -T "*" /path/to/my/project executes the unittests and prints the result to standard output. You can call single tests or an arrangement of tests in the same way, as these are stored in the file .test after you created a test suite.

You can even let run a testsuite by name by adding a preceeding "@" to it's name if you let it run with gbx3:

gbx3 -T "@my first testsuite" /path/to/my/project

If your project relies on the existence of environment variables you can define these on the command line:

MyEnvVar=1 gbx3 -T "@my first testsuite" /path/to/my/project

Environment variables defined inside the Project properties dialog of the Gambas IDE are not taken into account.

Test fixture

Sometimes it is neccessary to create a "fixture", a special environment for a test or a couple of tests, and to destroy this environment after the test is done. For example a database connection should be established at the beginning of all tests, some tables for testing should be created and deleted for every single test method and the database connection should be closed at the end. This can be done with _Setup... and _Teardown... functions inside the test module.

Sub _Setup() and Sub _Teardown()

You can create methods with these names to create an environment for all test methods inside a test module, in the beginning _Setup() is invoked and after all test methods inside the test module are done you can destroy the environment with _Teardown().

Sub _SetupEach() and Sub _TeardownEach()

You can create methods with these names to create an environment for each test method before it is invoked and to destroy it afterwards. If you have five test methods inside your test module these functions will be invoked five times, _SetupEach() before each test method, _TeardownEach() after each test method. Got it?

Plan tests

It would be pretty stupid if your test system, for unknown reasons, swallowed single tests and never showed you that they did not run. To protect yourself from this, there is a way to specify a plan that defines the number of assertions that must run inside a testmethod. And it goes like this:

' Gambas test file
''' Test module THelloWorld

Public Sub AHelloWorld()

  '' Defines the count of assertions in this test method
  Test.Plan(1)

  Assert.Equals(Hello.World(), "Hello World", "HW strings should be equal")

End

If the tests have not fulfilled the plan, the system will display "FAILED" and tell you the reason.