DEV Community

Cover image for Here's Why A Newbie is Raving About Code
Carlo
Carlo

Posted on

Here's Why A Newbie is Raving About Code

I am a software QA engineer working on transitioning to software engineering. And while I won't bore you too much on the journey so far, I have had some experiences that could be useful or at least shed some light on my overall experience.

And while this won’t be (yet) another long-winded, "cry me a river" post about the struggles of breaking into the field it will be honest, transparent and raw.

Now, about that code I am raving about... it is my own.

Before we start dissecting it or dropping some 💩 emojis (yes I am aware it's most likely 💩...) let's talk about why.

As a QA engineer, a key tool is a well-defined test plan. These living documents are the tools that help make the lives of many SEs' a nightmare – or, at the very least, they help all of us ship and share somewhat functional features that won't blow up the first time an end user actually puts their eager hands on it. In my daily workflows, I found myself manually building the structure and format of these test plans to then enter all of the information pertinent to them.

Like many of you, when faced with a problem, I want to solve it. Of course the idea is to increase efficiency, or at least avoid tedious manual tasks – but its more than that...

Enter the Test Plan Builder, a CLI tool, to help me focus on the actual content of the test plan and spend less time structuring the template.

I jumped on the Go bandwagon in 2024 and have thoroughly enjoyed the learning experience. So, it seemed like the natural choice for this project.

Demo of the "current" final product:

Diving Into the Code

First, I applaud you for making it this far! Now that we're all here, it's time to take a detour to ...jk! Let's get into the implementation details! How fun!

Disclaimer: I am not adhering to any methodology/philosophy on test plan structure.

I built this tool to match my own approach to planning and discovery for the user stories I validate with my test plans. While this may not be a one size-fits-all solution, it should provide a good starting point.

Defining the Structure of the Template

type TestPlanTemplate struct {
    Name string
    TicketDetails
    RequiredValidation string
    Description        string
    SavePath           string
    WriteToFile        bool
}

type TicketDetails struct {
    UserStory           string
    UserStoryLink       string
    UserStoryComponents string
    AutomationNeeded    bool
    PM                  string
    Dev                 string
    QA                  string
}
Enter fullscreen mode Exit fullscreen mode

For simplicity's sake, and to avoid cognitive overhead with a giant struct, I defined a TicketDetails struct that houses the information specific to the user story my test plan is tied to, and embedded it into a TestPlanTemplate struct the overall structure of the test plan itself.

This approach improves readability and simplifies field access. Embedding TicketDetails sets me up to access ticket details directly through the TestPlanTemplate instance.

As a final note, all field and struct names are capitalized for future exportability.

The Methods and Helpful Functions

  1. "Almost A Factory" Really A Constructor in Other Languages
func NewTestPlanBuild() *TestPlanTemplate {
    return &TestPlanTemplate{}
}
Enter fullscreen mode Exit fullscreen mode

This "Almost a factory" function is really a constructor in other languages. I could have easily just used GO's built in constructor t := TestPlanTemplate{}, but I went this route for various reasons.

First, not all the necessary information is available, and I don't want to restrict the instantiation process.

Second, there's only one version of TestPlanTemplate object ahem I mean struct that will be instantiated. Therefore, no complex logic is required to justify a full factory implementation 'right now'.

Finally, given the potential size of the TestPlanTemplate struct, I chose this approach to return a pointer instead of GO's default behavior of passing by value. This reduces the number of TestPlanTemplate struct copies.

  1. My Take On a Stringer() Method
func (tp TestPlanTemplate) MDString() string {

    title := "#  " + tp.Name + "\n\n"
    link := fmt.Sprintf("**User Story Link:**\t[%s](%s)\n\n", tp.UserStory, tp.UserStoryLink)
    table, _ := tableBuilder(tp)
    components := fmt.Sprintf("**User Story Components:**\t%s\n\n", tp.UserStoryComponents)
    auto := fmt.Sprintf("**Requires Automation:**\t%t\n\n", tp.AutomationNeeded)
    description := fmt.Sprintf("## Description:\n%s\n\n", tp.Description)
    testplan := "## Test Validation\n\n"

    return title + link + table + components + auto + "***\n" + description + "***\n" + testplan
}
Enter fullscreen mode Exit fullscreen mode

I took inspiration from the Stringer workflow in the fmt package.

While this method doesn't implement the Stringer interface, it uses a similar approach to generate a Markdown representation of the TestPlanTemplate. Which come in handy in the future.

For those unfamiliar with the Stringer interface, it's defined in the fmt package. Any type that implements a String() string method fulfills this interface.

When you pass an instance of a type that implements the Stringer interface to a fmt function (like fmt.Println or fmt.Printf with the %s or %v format specifiers), Go automatically calls its String() method to get the it's string representation. Otherwise, it uses default formatting.

For more information, see A Tour of Go - Stringers

CLI Forms and Charm to the Rescue

Initially I considered using bufio and os.Stdin (both part of the standard library) for individual prompts to gather the TestPlanTemplate data. However, this approach would have been monotonous for the user. Frankly, it's not very aesthetically pleasing. I know I mentioned I built this for myself, but I did take into account the user experience if others decided to use this tool as well.

After researching alternatives, I discovered Charm's Huh library. This library facilitates building and rendering visually appealing forms in the terminal, making it ideal for transforming my basic CLI app into a more engaging TUI.

Using Huh

To keep this concise, I'll summarize the Huh form implementation rather than including the full code snippet.

for full implementation: BuildForm

I defined a BuildForm(tp *TestPlanTemplate) *huh.Form function which takes in a pointer to a TestPlanTemplate Struct as an argument.

Within the function, I instantiate a new form, define the form inputs (using Huh's API), bind them to their respective TestPlanTemplate fields, and return a pointer to the form.

For more on huh Forms charm.sh/HUH

Putting It All Together

With a defined structure for the TestPlanTemplate, a data-collecting form, and a Markdown representation method, we can now put it all together.

In our main.go file, we begin by instantiating a TestPlanTemplate struct and a huh.Form.

Then, we execute the form's Run method. We won't delve into the implementation details here, but the Run method uses Charm's Bubble Tea library. Bubble Tea generates a model (the form's state) and manages that state. For more information, see the Bubble Tea Documentation

Once we have a completed TestPlanTemplate, we call the BuildTemplateFromString which takes a pointer to the test plan.

A helper function checks if the savePath field is empty. If it is, the function uses user.Current() to get the user's home directory, appends /desktop and the filename, and returns the resulting path. If the savePath field is not empty, the function simply appends the filename to the existing path and returns.

Now that we have the file path I use os.WriteFile(saveFilePath, []byte(tp.MDString()), 0666) to create and write the file.

I've chosen these permissions to allow read/write access for all users by default (though this might need adjustments depending on specific security requirements).

And now we have a Test Plan Markdown file saved to the disk! Let's go!

What's Next

Now, I'll be dog fooding it. I'll share with my colleagues to gather feedback and make further tweaks. Am I a scientist? No. But is there value in testing, testing, testing. You bet. What’s most important though? Feedback. Even Einstein turned to others to get input.

Some things I considered when designing this tool:

  • Implement the ability to build different types of test plans based on the user story type leveraging my constructor function and a new Interface.
  • Integrate with jira or other User Story and Test Case Management applications via API's for direct writing.
  • Enhance the form's dynamic capabilities and add a preview function to visualize the final output

Finally, if you've made it this far, thank you for sticking with me! This has been my first fully-fleshed-out CLI APP/TUI and my first technical post. I appreciate your time! Writing about this project has been an awesome learning experience.

Now, its my turn to welcome your thoughts and input. Please feel free to leave comments (and 💩 emojis!). Or if you think it's helpful, take the tool for a spin!

Let's ride.

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more