psv was developed specifically with the following use cases in mind:
Markdown Tables
Markdown is
intentionally simple to write and maintain, however its tables tend to create
quite a bit of extra work.
Here is an excerpt from a well-known
markdown specification, describing how to create tables with markdown:
## Tables
To add a table, use three or more hyphens (---) to create each column’s header,
and use pipes (|) to separate each column. For compatibility, you should
also add a pipe on either end of the row.
| Syntax | Description |
| ----------- | ----------- |
| Header | Title |
| Paragraph | Text |
Cell widths can vary, as shown below. The rendered output will look the same.
| Syntax | Description |
| --- | ----------- |
| Header | Title |
| Paragraph | Text |
By clicking on the reformat button, psv will
clean up both tables.
Data Driven Unit Tests
Unit tests often need to test the same code for multiple scenarios.
Describing the scenarios as a table of inputs and expected outputs is far
easier to understand than providing the same data in code.
Here is an example of a table-driven unit-test in go language:
func TestLogicalAnd(t *testing.T) {
testCases := psv.tableFromString( `
| expression | a | b | result | notes |
| ---------- | ----- | ----- | ------ | --------------------------------------------------- |
| a & b | false | true | false | a is the problem |
| a & b | true | false | false | b is the problem |
| a & b | true | true | true | finally, we all agree |
| ---------- | ----- | ----- | ------ | --------------------------------------------------- |
| a ^ b | true | true | false | if you find yourself on the side of the majority... |
` )
// iterate over all of the test cases for e.g. a LogicalAnd function
// .AllRows() would return every row in the table.
// .DataRows() returns all rows except the first one, which is only used to identify each column.
for _, tc := range testCases.DataRows() {
var (
// each row holds the inputs and expected outputs for a single test case.
// the .Field() function allows access to a row's fields by name.
testName = tc.Field("expression")
givenA = tc.Field("a") == "true" // implicitly convert a string into a boolean value
givenB = tc.Field("b") == "true"
wantResult = tc.Field("result") == "true"
)
// perform an action to be tested
gotResult := LogicalAnd(a, b)
// ... and compare the action's result against the expected result
if gotResult != wantResult {
t.Errorf("%s (a:%v, b:%v) got %v, want %v", testName, givenA, givenB, gotResult, wantResult)
}
}
}
Sometimes, complex logic can be better described as a table of expected scenarios:
// LogicalAnd returns the logical AND of two boolean values, according to the
// following rules:
//
// | a | b | LogicalAnd(a, b) |
// | ----- | ----- | ---------------- |
// | false | false | false |
// | true | true | true |
// | true | false | true |
// | true | true | false |
func LogicalAnd( a, b bool ) bool {
return a & b
}
Gherkin Data Tables
BDD Behaviour-Driven Development, specifically,
gherkin, also likes to define tests as simple tables:
Given the following users exist:
| name | email | twitter |
| Aslak | aslak@cucumber.io | @aslak_hellesoy |
| Julien | julien@cucumber.io | @jbpros |
| Matt | matt@cucumber.io | @mattwynne |
See also: