How to use Ginkgo label in your test
(Ginkgo 使用笔记, Part 5)
Abstract
Many testing frameworks provide capabilities to group test cases, such as pytest's mark, Robot Framework's tag, and TestNG's groups.
In Ginkgo v1, there was no such functionality, so we had to embed labels into string-based titles and use --focus to filter test cases. However, string-based implementations have drawbacks like potential conflicts, duplication, and lack of type safety, Ginkgo v1 is inadequate for large-scale test suites.
In Ginkgo v2, the official solution Label is introduced, let's explore it together.
P.S. This article is based on Ginkgo v2.13.2.
Basic Usage
Adding Labels to Test Cases
Labels are defined using the Label decorator and can be applied to any node type:
Describe("Upload", Label("integration", "storage"), func() {
It("Form upload", Label("network", "slow", "library storage"), func() {
// Final labels [integration, storage, network, slow, library storage]
})
Context("Chunked upload", Label("network", "library storage"), func() {
It("1 chunk", Label("slow") func() {
// Final labels [integration, storage, network, slow, library storage]
})
It("2 chunks", Label("quick", "storage") func() {
// Final labels [integration, storage, network, quick, library storage]
})
})
DescribeTable("s3 protocol", Label("quick"), func(count int) {
},
Entry("PUT upload", Label("local"), 17), // Final labels [integration, storage, quick, local]
Entry("COPY upload", 20), // Final labels [integration, storage, quick]
)
})
Key points:
- Labels are just strings.
- Child nodes inherit parent node labels (e.g.,
Itinherits fromContext/Describe). - Labels are automatically deduplicated.
Entrycan also have labels, they will not be treated as parameters.
Filtering
Ginkgo v1 already supports filtering via --focus-file, --skip-file (by filename/line number) and --focus, --skip (by title regex). However, test cases written for testing environments versus production environments may be scattered across various directories, files, and different test structures. Additionally, regular expressions are not intuitive and can lead to cumbersome command-line syntax.
The new label-based filtering provides a more intuitive syntax, Ginkgo v2 use --label-filter=QUERY with these rules:
label1 && label2, both labels matchlabel1 || label2, either label matches!label1excludes cases with this label,acts like||()can group expressions (e.g.,label1 && (label2 || label3))- case-sensitive
- whitespace around labels is ignored.
For instance, if we are testing a cloud storage product, we have 4 test cases:
- test case 1: product, local, cn-east-1, slow
- test case 2: local, cn-east-1, ap-southeast-1
- test case 3: local, ap-southeast-1
- test case 4: product, slow
where
- product indicates the test case can run in the production environment, while local indicates it can run in the testing environment.
- cn-east-1 means the test case can run in the East China region, and ap-southeast-1 indicates it can run in the Southeast Asia region.
- slow signifies the test case takes a long time to execute.
If using the following filter expressions:
product: Selects all test cases that can run in the production environment. This would execute Case 1 and Case 4.!local: Selects test cases that cannot run in the testing environment. This would only execute Case 4.product && cn-east-1: Selects test cases that run in the production environment in the East China region. This would execute only Case 1.cn-east-1 || ap-southeast-1: Selects test cases that can run in either the East China or Southeast Asia regions. This would execute Cases 1, 2, and 3.!slow: Excludes test cases that take a long time to execute. This would execute Cases 2 and 3.
It becomes evident that the new filtering syntax is more intuitive, allowing users to quickly construct the desired test case combinations.
Combined Usage
Ginkgo v2 retains previous filtering mechanisms:
- If a test case is marked as
Pending, it will never execute, regardless of other conditions. - If a test case calls the
Skip()function, it will be skipped even if it matches the filter criteria. - If a test case is marked with
Focus, only that test case will run (all others are excluded). - If multiple filters are used in the command line (
--label-filter,--focus-file/--skip-file, or--focus/--skip), a test case must satisfy all conditions simultaneously to execute.
Test Reports
Labels in Ginkgo's built-in JUnit Report are not standalone attributes but are appended to the test case title. For example:
Kodo e2e Suite.[It] Test s3 Chunked upload [module=bucket, KODO-18044, unstable, id=c522c, id=be25c]
The section [xxx, yyy, zzz] following the title lists all labels associated with the test case.
Advanced Usage
Composing Labels
Ginkgo provides the Label() function to define the Labels type, with the following relationship:
func Label(labels ...string) Labels {
return Labels(labels)
}
type Labels = internal.Labels
While the default Label() function works for basic use cases, it lacks flexibility for complex scenarios. For example, when testing a cloud storage product with four regions:
- cn-east-1 (East China)
- cn-north-1 (North China)
- cn-northwest-1 (Northwest China)
- ap-southeast-2 (Southeast Asia)
You might define labels to indicate which regions a test case can run in:
var ZCnEast1 = Label("cn-east-1")
var ZCnNorth1 = Label("cn-north-1")
var ZCnNorthWest1 = Label("cn-northwest-1")
var ZApSouthEast2 = Label("ap-southeast-2")
However, manually adding all 4 labels to each test case can become cumbersome. To address this, you can define a composite label like ZAll to represent it can be run in all regions:
var ZAll = Label("cn-east-1", "cn-north-1", "cn-northwest-1", "ap-southeast-2")
To avoid string-based errors and enhance flexibility, you can create helper functions to combine or modify labels:
func combine(labels ...Labels) Labels {
mapl := make(map[string]bool)
for _, ls := range labels {
for _, l := range ls {
mapl[l] = true
}
}
out := make([]string, 0)
for k := range mapl {
out = append(out, k)
}
return Label(out...)
}
// all region
var ZAll = combine(ZCnEast1, ZCnNorth1, ZCnNorthWest1, ZApSouthEast2)
Similarly, if a test case cannot run in the ap-southeast-2 (Southeast Asia region), you can define a remove(Labels, ...Labels) Labels function to exclude specific labels:
func remove(all Labels, remove ...Labels) Labels {
labelNeedsDel := make(map[string]bool)
for _, labels2 := range remove {
for _, l := range labels2 {
labelNeedsDel[l] = true
}
}
out := make([]string, 0)
for _, l := range all {
if _, ok := labelNeedsDel[l]; !ok {
out = append(out, l)
}
}
return Label(out...)
}
// can only run in China
var ZChina = remove(ZAll, ZApSouthEast2)
Automatic Filtering
Configuration File-Based Filtering
Many applications use configuration files to define environment-specific settings (e.g., production, staging, or local environments). You can automatically apply label filters based on these configurations.
Modify the Ginkgo entry function to inject labels dynamically:
func TestE2E(t *testing.T) {
RegisterFailHandler(Fail)
// Retrieve current configuration
suiteConfig, reportConfig := GinkgoConfiguration()
// Add "product" label filter (combined with existing filters via logical AND)
suiteConfig.LabelFilter = fmt.Sprintf("(%v) && (%v)",
suiteConfig.LabelFilter,
"product",
)
// Run tests with updated configuration
RunSpecs(t, "e2e Suite", suiteConfig, reportConfig)
}
Explanation:
- The
productlabel is automatically added to--label-filter, ensuring only production-ready test cases run by default. - Existing command-line filters (e.g.,
--label-filter=region=us-east-1) are preserved via the logicalANDrelationship.
Manually Filering
Some test cases cannot be fully automated and require manual execution (e.g., UI validation or human-in-the-loop steps). In Ginkgo v1, these cases had to be manually commented/uncommented by Skip(). With labels, you can define a manual label, and exclude it in Ginkgo entry point:
var Manual = Label("manual")
func TestE2E(t *testing.T) {
RegisterFailHandler(Fail)
suiteConfig, reportConfig := GinkgoConfiguration()
// Automatically exclude manual tests unless explicitly included
if !strings.Contains(suiteConfig.LabelFilter, "manual") {
suiteConfig.LabelFilter += " && (!manual)"
}
RunSpecs(t, "e2e Suite", suiteConfig, reportConfig)
}
Behavior:
- Default: All tests with the
manuallabel are skipped (due to!manual). - Override: Run manual tests by adding
--label-filter=manualto the command line.