Language Generator
Background
Qualified gives developers the ability to run code in powerful testing environments designed to make them feel comfortable and productive immediately. Every language that Qualified supports comes with an industry-standard language-specific testing framework to run in-depth tests with.
The advantage of using native testing environments is that developers are empowered to read the provided tests, write their own test cases and debug their solutions much like they do in an on-the-job development workflow.
The standard alternative to providing native, language-specific test suites is to validate candidate solutions by comparing standard output using a single language-agnostic validator script. This approach lends itself to speedy challenge development but removes the benefits of candidate interaction with tests.
The drawback of using native language-specific testing environments is that a test suite must be translated into each language by hand. This can be a time-consuming task and motivates Qualified's Language Generator tool. By automating generation of challenge solution stubs and test suites, it's possible to rapidly develop challenges that use native testing suites.
What is the Language Generator?
The Language Generator is a tool that allows teams to design challenges at a high-level by specifying a function header and a test suite in a language-agnostic YAML configuration file. This configuration file is then used to instantly generate language-specific boilerplate code and test suites in every language that the generator supports.
Let's walk through a simple example to see how to use the generator.
Limitations of the Language Generator
The language generator is great for classic code challenges that rely on primitive data types such as strings, integers and booleans. The generator also supports 1d and 2d dynamic list array types. A full list of types is available in supported types.
Challenges that involve structs, classes, hashes, mixed type lists, variable arguments and other nested or complex data structures are not supported and must be created and translated by hand.
The language generator tool does not support project challenges and cannot generate random test cases.
Although the language generator supports over a dozen languages, not all languages supported by Qualified are supported by the generator. Try out the language generator in the app to see an up-to-date list of supported languages.
Example
Let's design a toy challenge using JavaScript as a generic point of reference to help visualize how the generator works (you don't need to write JS to use the generator).
Our toy challenge might consist of a function called sayHello
which takes in a name
and returns "Hello {name}!"
.
The setup code for the candidate might be:
function sayHello(name) {
// TODO write your solution here
}
The test suite might look something like this:
let assert = require("chai").assert;
describe('with a non empty string', function() {
it('should say hello specifically', function() {
assert.equal(sayHello("Qualified"), "Hello, Qualified!");
});
});
In the suite above, we're checking to see if the candidate's function sayHello
provides the greeting string we expect.
Here's the YAML configuration for this example challenge:
entry_point: say_hello
return_type: String
parameters:
- name: name
type: String
test_cases:
- describe: with a non empty string
its:
- it: should say hello specifically
assertions:
- input_arguments:
- value: Qualified
expected_output:
value: Hello, Qualified!
- describe: with an empty string
its:
- it: should say hello generically
assertions:
- input_arguments:
- value:
expected_output:
value: Hello there!
example_test_cases:
- it: should say hello
assertions:
- input_arguments:
- value: Qualified
expected_output:
value: Hello, Qualified!
Let's break it down. Our entry_point
is say_hello
.
entry_point: say_hello
Since this is translated into multiple languages, it's shown snake-cased here, but it will translate into the appropriate casing for each language. In JavaScript, it would become sayHello
automatically.
You'll notice we designated a return_type
of String
:
return_type: String
This must be specified. Check out Supported Types for all the available types return_type
could be.
Next is a parameters
entry:
parameters:
- name: name
type: String
This is a list of parameters that the entry point function will be called with. The entry point is set up automatically for the candidate so they understand what kind of parameters to expect and where to expect we'll be sending them. In the example above, we specify that sayHello
has a single name
parameter with type string.
Finally, we have test_cases
and example_test_cases
. example_test_cases
are provided to the candidate to view, modify and run during the challenge. The test_cases
suite is hidden from the candidate but can be designed to show debugging information to the candidate on submission in order to lead them to the correct solution.
Let's take a look at the test_cases
.
test_cases:
- describe: with a non empty string
its:
- it: should say hello specifically
assertions:
- input_arguments:
- value: Qualified
expected_output:
value: Hello, Qualified!
- describe: with an empty string
its:
- it: should say hello generically
assertions:
- input_arguments:
- value:
expected_output:
value: Hello there!
The configuration allows the challenge designer to create assertions inside of it
blocks which are nested inside describe
blocks. Each assertion can have unlimited input arguments, each with a particular type and value. Given these inputs, we can specify what the expected output of the candidate's solution (entry_point
) should be.
In JavaScript this configuration would translate to:
let assert = require("chai").assert;
describe('with a non empty string', function() {
it('should say hello specifically', function() {
assert.equal(sayHello("Qualified"), "Hello, Qualified!");
});
});
describe('with an empty string', function() {
it('should say hello generically', function() {
assert.equal(sayHello(""), "Hello there!");
});
});
This walkthrough example has shown the basic usage of the language generator.
Sections below show how to work with different types and describe the YAML configuration in depth.
Working with Types
Language Generator types are case-insensitive. The currently supported types are string
, boolean
, integer
and array
. The array
type must also specify what the element's type is using diamond brackets: Array<Integer>
or Array<Boolean>
for statically-typed languages.
An example of a configuration with arrays might look something like this:
entry_point: splitter
return_type: Array<String>
parameters:
- name: word
type: String
test_cases:
- it: split_the_word
assertions:
- input_arguments:
- value: pizza
expected_output:
values: [p, i, z, z, a]
In this case we're inputting a String
with the value "pizza" and expecting that the splitter
function will split the string into an array of five String
values ['p','i','z','z','a']
.
Supported Types
Here is a list of types supported by the language generator:
String
Boolean
Integer
Array<String>
Array<Boolean>
Array<Integer>
Array<Array<String>>
Array<Array<Boolean>>
Array<Array<Integer>>
Syntax Definition
On the top level of the configuration there are four required key mappings and one optional key.
entry_point
(required) - The name of the function or method that will be tested. Your candidate will be coding this function.return_type
(required) - The type of object that will be returned from the entry point method.parameters
(required) - A list of parameters that will passed as arguments to the entry point method.test_cases
(required) - A set of test cases that will be used to test the entry point method. Each case has inputs and expected outputs. These test cases are hidden from the candidate and will determine their final score. By default, the candidate can see the output from these test cases.example_test_cases
(recommended, but optional) - Similar totest_cases
except that example tests are viewable and modifiable by the candidate. They are not used in the calculation of their final score.
Parameters
parameters
is a list specified in the YAML configuration describing the parameters to be passed to the solution method under test. For example, a function that accepts two parameters might be represented by:
parameters:
- name: list
type: Array<Array<Integer>>
- name: x
type: Integer
Where name
is the name of the parameter specified in the entry point method for the candidate's setup code and the generated solution. The type
can be any one of the supported types specified above.
For example, the above configuration would generate a C# parameter list like SomeFunc(List<List<int>> list, int x)
.
Test Cases
Test Cases contains a list of describe
and it
blocks that will be translated into test cases specific to each language's test framework.
Describe Block
The describe
block contains a grouping of related it
blocks. describe
blocks help organize the code and the eventual output shown to the candidate. describe
will contain one list, its
, which contains it
blocks corresponding to the describe
.
describe
is a string label that describes the purpose of its constituent it
blocks.
A describe block in the YAML might look something like this:
test_cases:
- describe: with a non empty string
its:
- it: should say hello specifically
assertions:
- input_arguments:
- value: Qualified
expected_output:
value: Hello, Qualified!
It Block
The it
block contains a list of assertions. It is generally recommended to stick to one assertion per it
block for organizational purposes. Keeping each it
block simple provides clear feedback to the candidate should they fail an assertion.
Assertions
The assertions
list contains a list of input_arguments
and the expected_output
.
The input_arguments
will map to the parameters
specified at the main level of the configuration. The order in which the input argument is specified will match up to the order in which the parameter is listed. A type
can be specified, but is not necessary because it must adhere to the type
specified by the parameters
list.
The expected_output
will map to the return_type
specified at the main level of the configuration. It can only return a single value.
Values
The input_arguments
and expected_output
will use the keyword value
to specify inputs and outputs.
The keyword value
can contain any of the supported types. A string can always be specified by using apostrophes, so a number can be turned into a string ('52'
as opposed to 52
). Arrays can be listed out in the array syntax [1,2,3]
or with -
in the YAML list format.
Advanced Test Cases
describe
and it
blocks can define Advanced Test Cases. Use hidden
, weight
and tag
to add advanced testing functionality to your it blocks, or optionally add them to an entire describe block.
test_cases:
- describe: with a non empty string
its:
- it: should say hello specifically
hidden: true
tag: Basic
weight: 1.5
assertions:
- input_arguments:
- value: Qualified
expected_output:
value: Hello, Qualified!