https://www.assembled.com/blog/how-we-saved-hundreds-of-engineering-hours-by-writing-tests-with-llms * * ClickCease [620106ba9c] Products product OVERVIEW * Platform * Assembled Workforce Management * Assembled Vendor Management * Assembled Assist (AI) * Integrations solutions * Enterprise WFM * Scheduling at scale * Short-term forecasting * Real-time management * Performance management * Metrics management * Back office management * Quality assurance * AI workflow builder * Agent copilot * Automated case resolution tour assembled assist [6570a48b81] Explore Assembled Assist, an AI-powered issue resolution engine and agent co-pilot, in a self-guided tour. See Assembled in action Chat with sales Resources product help * Support [6] * Integrations * Product updates * API documentation * Status page * Security Learn * Blog * Case studies * Resource library * ROI Calculator * WFM Glossary download the capacity planner [66eb789bdd] Use this template to plan capacity, predict staffing needs, and meet SLAs efficiently with clear, defined metrics. PricingCustomers Company meet assembled * About * Why Assembled? * Careers * WFM Changemakers register now [66f307a4ab] Join us Oct. 30 and learn how to build a winning support operations strategy with Hotel Engine, Assembled, and Service Cloud. Sign in Get Started product OVERVIEW * Platform * Assembled Workforce Management * Assembled Vendor Management * Assembled Assist (AI) * Integrations solutions * Enterprise WFM * Scheduling at scale * Short-term forecasting * Real-time management * Performance management * Metrics management * Back office management * Quality assurance * AI workflow builder * Agent copilot * Automated case resolution tour assembled assist [6570a48b81] Explore Assembled Assist, an AI-powered issue resolution engine and agent co-pilot, in a self-guided tour. See Assembled in action Chat with sales Features [620508efb2] product OVERVIEW * Platform * Assembled Workforce Management * Assembled Vendor Management * Assembled Assist (AI) * Integrations solutions * Enterprise WFM * Scheduling at scale * Short-term forecasting * Real-time management * Performance management * Metrics management * Back office management * Quality assurance * AI workflow builder * Agent copilot * Automated case resolution tour assembled assist [6570a48b81] Explore Assembled Assist, an AI-powered issue resolution engine and agent co-pilot, in a self-guided tour. See Assembled in action Chat with sales resources [620508efb2] product help * Support [6] * Integrations * Product updates * API documentation * Status page * Security Learn * Blog * Case studies * Resource library * ROI Calculator * WFM Glossary download the capacity planner [66eb789bdd] Use this template to plan capacity, predict staffing needs, and meet SLAs efficiently with clear, defined metrics. pricing [620508efb2] Customers [620508efb2] company [620508efb2] meet assembled * About * Why Assembled? * Careers * WFM Changemakers register now [66f307a4ab] Join us Oct. 30 and learn how to build a winning support operations strategy with Hotel Engine, Assembled, and Service Cloud. See assembled in action sign in request demo request demo All Topics Engineering How we saved hundreds of engineering hours by writing tests with LLMs How we saved hundreds of engineering hours by writing tests with LLMs John Wang Co-Founder and CTO [671986e877] JUMP TO SECTION Heading 2 At Assembled, engineering velocity is our competitive edge. We pride ourselves on delivering new features at a fast pace. But how do we maintain quality without slowing down? The answer lies in robust testing. As Martin Fowler aptly puts it: [Testing] can drastically reduce the number of bugs that get into production... But the biggest benefit isn't about merely avoiding production bugs, it's about the confidence that you get to make changes to the system. Martin Fowler Despite this, writing comprehensive tests is often overlooked due to time constraints or the complexity involved. Large Language Models (LLMs) have shifted this dynamic by making it significantly easier and faster to generate robust tests. Tasks that previously required hours can now be completed in just 5-10 minutes. We've observed tangible benefits within our team: * An engineer who previously wrote few tests began consistently writing them after utilizing LLMs for test generation. * Another engineer, known for writing thorough tests, saved weeks of time by using LLMs to streamline the process. * Collectively, our engineers have saved hundreds of hours, reallocating that time to developing new features and refining existing ones. In this blog post, we'll explore how we've used LLMs to enhance our testing practices. Leveraging LLMs for testing To get started, you'll need access to a high-quality LLM for code generation like OpenAI's o1-preview or Anthropic's Claude 3.5 Sonnet. Then, you should craft a precise prompt that guides the model to produce the desired output. Here's a sample prompt we've found effective for generating Go unit tests: Help me write a comprehensive set of unit tests in Golang for the following function: // Insert your function code here Here are the definitions of the associated structs used in the function: // Optionally insert any relevant struct definitions here Please ensure that: - The tests use the fixture pattern by defining different test cases in a slice. - The tests follow Go's testing best practices, including proper naming conventions and code organization. - Use the `testing` and `require` packages as shown in the example below. - Cover various scenarios, including normal cases, edge cases, and error handling. // Include an example of a good unit test from your codebase In this prompt, you need to provide: * Function to test: Copy and paste the exact code you're looking to write tests for. * Struct definitions: Include any relevant definitions that the function uses (especially for any objects that appear in the input or output of the function). * Example of a test suite: An example of existing tests that reflect your codebase's style and conventions. Once you've dropped this into an LLM and generated a result, you might need to review and refine the generated tests. You should check for compilation issues, add any potential edge cases the LLM missed, and adjust the style to match your codebase conventions. We've found that a few iterations of back and forth are sometimes necessary to arrive at an acceptable test suite. Once you're close enough, just copy and paste the resulting tests back into your codebase. If you have an AI-assisted code editor like Copilot or Cursor, the principles remain the same; though, because tools can provide context-aware suggestions based on your existing code, you often can get away with less detailed prompts. Example in action Suppose you're building an e-commerce platform and have a function that calculates an order summary. Here's how you might apply the above approach. // Struct definitions type OrderItem struct { ProductID string Quantity int UnitPrice float64 Weight float64 // Weight per unit in kg Category string } type OrderSummary struct { TotalPrice float64 TotalWeight float64 ItemsByCategory map[string]int // Category name to total quantity } // Function to test func CalculateOrderSummary(items []OrderItem) OrderSummary { itemsByCategory := make(map[string]int) totalPrice := 0.0 totalWeight := 0.0 for _, item := range items { totalItemPrice := float64(item.Quantity) * item.UnitPrice totalItemWeight := float64(item.Quantity) * item.Weight totalPrice += totalItemPrice totalWeight += totalItemWeight itemsByCategory[item.Category] += item.Quantity } summary := OrderSummary{ TotalPrice: totalPrice, TotalWeight: totalWeight, ItemsByCategory: itemsByCategory } return summary } Using the suggested prompt, we fed this code into ChatGPT o1-preview and, in just 48 seconds, it generated a comprehensive test suite that was ready to use straight out of the box. Here's the full prompt and results from ChatGPT. You'll notice that the resulting tests are both comprehensive and well written: * The tests cover basically all of the cases that you might think of: empty slices, nil slices, single item, multiple items, items with zero quantity, etc. These test cases are mutually exclusive and collectively exhaustive and cover most of the edge cases a good engineer would think of. * Moreover, the resultant code is in the table-driven fixture style that is idiomatic in Go -- the exact format that we specified in the initial prompt. The resultant tests even use the testify/ require library, which is prescribed in the original example. import ( "testing" "github.com/stretchr/testify/require" ) func TestCalculateOrderSummary(t *testing.T) { fixtures := []struct { Name string Items []OrderItem Expected OrderSummary }{ ... { Name: "Multiple items in different categories", Items: []OrderItem{ { ProductID: "P1", Quantity: 2, UnitPrice: 5.0, Weight: 0.2, Category: "Books", }, { ProductID: "P2", Quantity: 1, UnitPrice: 100.0, Weight: 1.0, Category: "Electronics", }, }, Expected: OrderSummary{ TotalPrice: (2 * 5.0) + (1 * 100.0), TotalWeight: (2 * 0.2) + (1 * 1.0), ItemsByCategory: map[string]int{ "Books": 2, "Electronics": 1, }, }, }, ... } for _, fixture := range fixtures { t.Run(fixture.Name, func(t *testing.T) { result := CalculateOrderSummary(fixture.Items) require.Equal(t, fixture.Expected.TotalPrice, result.TotalPrice, "TotalPrice mismatch") require.Equal(t, fixture.Expected.TotalWeight, result.TotalWeight, "TotalWeight mismatch") require.Equal(t, fixture.Expected.ItemsByCategory, result.ItemsByCategory, "ItemsByCategory mismatch") }) } } Extending to more complex scenarios The same approach can be applied to more complex testing scenarios. By adjusting the prompt and providing a different set of baseline test cases, you can generate tests for: * Different programming languages. It's relatively straightforward to adjust the prompt for other languages and tailor the results to specific testing frameworks. + Example: Unit tests for a typescript function that converts roman numerals to integers, using Claude 3.5 Sonnet * Frontend component testing. You can also extend this to test React components with user interactions and state changes -- just make sure your examples capture the libraries you'd use. + Example: Testing a React dropdown component with Jest and React Testing Library, including user interactions and DOM assertions, using o1-preview. * Integration testing with mocked services. By changing the test case examples, you can test functions that interact with external APIs by mocking HTTP clients. + Example: Testing a function that fetches average weather data by mocking a weather API call, using o1-preview. Considerations At Assembled, we've been using LLMs to write tests for a few months now and have seen big boosts in engineering productivity. That said, there are a few considerations to keep in mind as you start using LLMs for test writing: * Iterative refinement: You may need several iterations to cover missed edge cases or adjust to your codebase standards. Sometimes, the LLMs might generate code that doesn't compile, so asking the LLM to make adjustments is critical. * Double check your test logic: While LLMs are pretty good out of the box, they can sometimes get tests wrong. For example, one of our engineers had an experience where the model gave incorrect output because of improper formatting. We insist that all Assembled engineers read and run any LLM-generated tests before merging into production. * Customize your prompt to your specific context: Our engineers have found that tailoring their prompts can significantly enhance the quality of the generated tests. For example, you might consider specifying your test frameworks (e.g. "Use Jest and React Testing Library for testing this React component.") or highlighting important edge cases (e.g. "Ensure you include tests for handling null inputs and maximum integer values."). * Examples matter: LLMs do their best work when they have a good example of tests to learn from. The engineering team at Assembled has built a large repository of comprehensive and idiomatic tests over time, which makes it easier to use these techniques. Remember that your examples are often your most important way to drive the LLM to do what you want. * Use the smartest models: Models like o1-preview or Claude 3.5 Sonnet generally provide better results. Since latency isn't a major concern, we tend to use the best available models. * Code structure reflects testability: If you're having trouble getting the LLM to construct suitable tests, consider refactoring your code. It's likely that whatever combination of inputs and outputs you have may be poorly structured or overly complex. You can even ask the LLM to break things up and refactor your code with the same prompting principles discussed above. * Don't overdo testing: You generally want to test the functions that have clear input / output and which contain the most important pieces of logic. You don't need to test that a checkbox is working correctly (unless you're the maintainer of a component library). Likewise, glue code is tough to test, and writing tests for some pretty straightforward glue code may not be worth it -- though you should check on a case-by-case basis (e.g., if that glue code is a very hot codepath). Conclusion Using LLMs to generate comprehensive test suites in minutes has been a game changer at Assembled. It reduces the activation energy to write tests and makes it less likely that engineers skip tests due to time constraints. This has resulted in a cleaner, safer codebase that has increased development velocity. We're hiring We've got a lot of features to build and tests to write. If you're interested in helping us transform customer support, check out our open roles. Thanks to Jake Chao, Mae Cromwell, and Whitney Rose for helping with drafts of this post. ADDITIONAL RESOURCES Template Capacity Planner Template [6668698a4a] template WFM RFP Template [6668698330] template Support Volume Forecast Template [66f2b51bf0] Related content [66561355c7] Engineering Better RAG results with Reciprocal Rank Fusion and Hybrid Search [64a7012da9] Engineering Product lessons from Dan Robinson, former CTO of Heap [621ac81158][621ac8011e] Products PlatformAssembled WFMAssembled Vendor ManagementAssembled Assist (AI) IntegrationsSecurity Company PricingAboutCareersPrivacy policyTerms of serviceServices agreement Solutions EnterpriseScheduling at scaleShort-term forecastingReal-time managementPerformance managementMetrics managementBack office managementQuality assuranceAI workflow builderAgent copilotAutomated case resolutionAssembled for SalesforceAssembled for Zendesk Resources BlogResource libraryCustomer storiesCapacity plannerErlang-C calculatorROI calculatorWFM 101Status pageSupportAPI docsProduct updates INDUSTRY eCommerceFintechHealthcareSaaS compare to AlvariaCalabrioCommunityWFMGenesysNICEPlayvoxSurfboardVerintZendesk WFMOther AI support tools subscribe to our newsletter [ ] [Subscribe] Thank you! You're subscribed! Oops! Something went wrong while submitting the form. [633c778694][633821f3a9] Sitemap