{"id":2003,"date":"2024-04-16T21:52:00","date_gmt":"2024-04-16T21:52:00","guid":{"rendered":"https:\/\/www.rainforestqa.com\/blog\/?p=2003"},"modified":"2025-03-03T17:09:37","modified_gmt":"2025-03-03T17:09:37","slug":"qa-testing-best-practices","status":"publish","type":"post","link":"https:\/\/www.rainforestqa.com\/blog\/qa-testing-best-practices","title":{"rendered":"The Rainforest Method: 5 essential QA testing best practices"},"content":{"rendered":"\n<p>Over a decade of helping startups improve quality, we\u2019ve arrived at a set of QA testing best practices that work for teams shipping fast and frequently. They fit neatly into five organizing principles:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Product teams own software quality \u2014 not a siloed QA team<\/li>\n\n\n\n<li>Effective testing requires CI\/CD<\/li>\n\n\n\n<li>For test coverage, less is more<\/li>\n\n\n\n<li>A test is only as useful as its environment<\/li>\n\n\n\n<li>Always test like a human (even with automation)<\/li>\n<\/ol>\n\n\n\n<p>Together, these principles represent The Rainforest Method. <strong>We\u2019ve built <a href=\"https:\/\/www.rainforestqa.com\/\">our AI-powered test automation platform<\/a> to make it easy for you to put this software testing method into practice and develop a quality product.<\/strong><\/p>\n\n\n\n<p>A few notes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>This post covers functional testing \u2014 the kind that makes sure your app\u2019s important flows behave as expected for your users. We won\u2019t be covering non-functional types of testing like security testing, performance testing, or usability testing.<\/li>\n\n\n\n<li>Throughout the post, assume we\u2019re referring to black box testing (where the tester doesn\u2019t have access to the underlying code), not white box testing.<\/li>\n\n\n\n<li>Also assume that \u201ctesting\u201d refers specifically to automated testing, unless otherwise noted.<\/li>\n<\/ul>\n\n\n\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.rainforestqa.com\/blog\/qa-testing-best-practices\/#1_Product_teams_own_software_quality_%E2%80%94_not_a_siloed_QA_team\" >1. Product teams own software quality \u2014 not a siloed QA team<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.rainforestqa.com\/blog\/qa-testing-best-practices\/#2_Effective_testing_requires_CICD\" >2. Effective testing requires CI\/CD<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.rainforestqa.com\/blog\/qa-testing-best-practices\/#3_For_test_coverage_less_is_more\" >3. For test coverage, less is more<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.rainforestqa.com\/blog\/qa-testing-best-practices\/#4_A_test_is_only_as_useful_as_its_environment\" >4. A test is only as useful as its environment<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.rainforestqa.com\/blog\/qa-testing-best-practices\/#5_Test_like_a_human_even_with_automation\" >5. Test like a human (even with automation)<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"1_Product_teams_own_software_quality_%E2%80%94_not_a_siloed_QA_team\"><\/span>1. <strong>Product teams own software quality \u2014 not a siloed QA team<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>The people who build the product \u2014 particularly product managers and developers \u2014 are incentivized to ship and have the context to make informed tradeoffs between speed and quality.<\/p>\n\n\n\n<p>Siloed QA team members, on the other hand, tend to slow down the release process because they\u2019re incentivized to catch as many bugs as possible, whether the bugs are meaningful for quality control or not.<\/p>\n\n\n\n<p>Therefore, software product builders should hold ultimate responsibility for quality.<\/p>\n\n\n\n<p><strong>This isn\u2019t to say you shouldn\u2019t use quality assurance roles as part of your testing team \u2014 just that you should apply those roles to the right testing activities. <\/strong>Working in close partnership with product builders (starting as soon as software requirements are being scoped out) they can level-up any <a href=\"https:\/\/www.rainforestqa.com\/blog\/qa-strategy-guide\" target=\"_blank\" rel=\"noreferrer noopener\">startup QA<\/a> process and contribute to high-quality software.<\/p>\n\n\n\n<p>QA testers and QA engineers, depending on their specialties, can consult on your software testing process, help define test cases, conduct exploratory testing to supplement your regression testing, review automated test failures (not all test failures are bugs \u2014 many simply reflect tests that need to be updated), and maintain automated tests.<\/p>\n\n\n\n<p>As long as your product builders are responsible for defining what functionality gets tested, for executing tests at the appropriate times in your DevOps pipeline, and for prioritizing and fixing bugs, you\u2019re on the right track.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Build test planning into product planning<\/strong><\/h3>\n\n\n\n<p>A lack of forethought and planning inevitably leads to gaps in test coverage.<\/p>\n\n\n\n<p>When defining a new feature or sprint, determine the tests you\u2019ll need to add or update to account for product updates. Don\u2019t consider a feature specification complete until you\u2019ve clearly defined the test cases you need to add to your suite.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Adopt accessible testing tools<\/strong><\/h3>\n\n\n\n<p>Being responsible for quality assurance implies defining test coverage (i.e., identifying the important functionality, features, and flows that should be tested) and then confirming the coverage has been implemented correctly, regardless of who implements it.<\/p>\n\n\n\n<p>To make this possible for everyone on the product team, you need software testing tools that anyone can use \u2014 tools that can be adopted quickly and easily without any special skills or training.<\/p>\n\n\n\n<p>When you have the right testing tools, anyone else in the organization who depends on product quality \u2014 including founders, support staff, marketers, and more \u2014 can access, interpret, and update the test suite, regardless of their technical skills. The QA process becomes more of a team sport.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Developers execute automated tests<\/strong><\/h3>\n\n\n\n<p>Product managers and developers can collaborate on defining test coverage and acceptance criteria for your tests, but your software engineers and developers should be the primary ones kicking off automated tests. They run deployments, so they\u2019re the ones who should trigger testing at the appropriate quality gates.<\/p>\n\n\n\n<p>They also have the most context around the changes being shipped and are best situated to unblock the release process. So, when automated tests fail, developers have the context necessary to get an outdated test to a passing state (in the case of a false positive test result) or to fix the underlying functionality in your app, just as they do when unit tests or integration tests fail.&nbsp;<\/p>\n\n\n\n<p>Of course, many test automation solutions are notoriously painful to use. Ideally, your test automation tooling is highly usable by any developer or engineer, without their needing to learn a new language or framework. Otherwise, test maintenance can become a painful drag for your developers and for your release process. (Even with usable tooling, teams often underestimate the amount of time they&#8217;ll have to spend on maintenance.)<\/p>\n\n\n\n<p>In fact, some software product teams use QA engineers \u2014 working closely with developers \u2014 to review test failures and update tests so their developers can stay focused on shipping code and fixing legitimate bugs.<\/p>\n\n\n<div id=\"highlight-block_f6d78aeb7c606eab255748ca805caf0f\" class=\"highlight\">\r\n    <p>Using <a href=\"https:\/\/www.rainforestqa.com\/blog\/generative-ai-test-automation\" target=\"_blank\" rel=\"noopener\">generative AI in its test automation<\/a>, Rainforest can automatically update \u2014 or &#8220;self-heal&#8221; \u2014 automated tests to reflect intended changes to your app.<\/p>\n<p>Which means your team spends less time on test maintenance and more time shipping code.<\/p>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"2_Effective_testing_requires_CICD\"><\/span><strong>2. Effective testing requires CI\/CD<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Software development teams tend to prioritize hitting deadlines over testing practices that promote product quality. Amid the urgency to ship code, testing discipline tends to suffer.<\/p>\n\n\n\n<p>The automated, systematized workflows of a continuous integration \/ continuous deployment (CI\/CD) pipeline provide both velocity and checkpoints for test creation, execution, and maintenance.<\/p>\n\n\n\n<p>Plus, CI\/CD is designed for SDLCs featuring frequent delivery of small releases, allowing teams to quickly uncover and fix issues with the least amount of work.<\/p>\n\n\n\n<p>Therefore, the best testing process involves creating, running, and maintaining tests within a CI\/CD pipeline.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Add test coverage for new features before merging<\/strong><\/h3>\n\n\n\n<p>A lack of test coverage for important functionality is how critical bugs escape into production.<\/p>\n\n\n\n<p>Any tests specified during planning should be created before merging the corresponding feature.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Use code reviews to verify test coverage<\/strong><\/h3>\n\n\n\n<p>A strong CI\/CD pipeline includes checkpoints for code reviews.<\/p>\n\n\n\n<p>The policies that govern code reviews and the decisions to merge new code should include considerations for test coverage. Specifically, before merging can proceed, require that any new tests \u2014 as defined during the planning process \u2014 have been implemented and that code on the feature branch passes the updated test suite.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Run end-to-end tests automatically in the release process<\/strong><\/h3>\n\n\n\n<p>In the ideal continuous testing strategy, you run your test suite within CI at quality gates triggered by a pull or merge request.&nbsp;<\/p>\n\n\n\n<p>At a minimum, to prevent issues from hitting production, set your CI\/CD pipeline to run automated end-to-end testing as a part of every release. Any test failures should block the corresponding release.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Enforce QA policies with a Quality Ops owner<\/strong><\/h3>\n\n\n\n<p>The same stakeholder who enforces policies and drives best practices in your software development process and deployment workflows should do the same for your software QA workflows.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"3_For_test_coverage_less_is_more\"><\/span><strong>3. For test coverage, less is more<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Automated test suites that take too long to run or are too difficult to maintain can become bottlenecks in the release process. When testing is a bottleneck, product teams are more likely to skip it so they can keep shipping code \u2014 which puts quality at risk.<\/p>\n\n\n\n<p>Therefore, be judicious about adding coverage and continually prune low-value tests from your suite to keep it fast and maintainable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Apply the Snowplow Strategy<\/strong><\/h3>\n\n\n\n<p>After a blizzard, snow plows clear the most-trafficked streets first because they affect the most people. Later, those plows might clear some side streets while ignoring other streets altogether.<\/p>\n\n\n\n<p>Applying the&nbsp;<a href=\"https:\/\/www.rainforestqa.com\/blog\/automation-test-coverage\">Snowplow Strategy<\/a>&nbsp;to your software testing means focusing on the user flows that are most important to your end users and to your business. Ignore the flows you wouldn\u2019t immediately fix if they broke. (It\u2019s among the top QA best practices.)<\/p>\n\n\n\n<p>This can also apply to compatibility testing: don\u2019t bother testing on browsers or platforms that your users don\u2019t actually use.<\/p>\n\n\n\n<p>Good testing is like a good product roadmap: the \u2018nice-to-haves\u2019 go in the backlog.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Only create what you can maintain<\/strong><\/h3>\n\n\n\n<p>It\u2019s tempting to test as many things as possible in the name of quality assurance. But not all user flows in your application are equally important, and test coverage is expensive in terms of maintenance costs.<\/p>\n\n\n\n<p>So if you\u2019re already struggling to keep up with test maintenance, reconsider adding a new test to your suite until you\u2019ve retired an old one.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Keep tests short<\/strong><\/h3>\n\n\n\n<p>For each test case, cover a user flow that\u2019s as finite as possible. Short tests finish quickly, are easier to debug, and are easier to maintain.<\/p>\n\n\n\n<p>For modern, agile teams, speed of test execution can be a useful metric to track. If you find your test suite is taking longer to run than usual, it might be time to look for ways to make your tests shorter and more efficient.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Fix or remove broken tests right away&nbsp;<\/strong><\/h3>\n\n\n\n<p>When left unaddressed, broken tests can prompt a vicious cycle: they don\u2019t just erode your quality standards, but also the team\u2019s trust in the test suite. Which leads them to invest less in the suite, which leads to more broken tests. Ultimately, the test suite\u2019s reliability suffers, and so does quality.<\/p>\n\n\n\n<p>When a broken test is detected, promptly evaluate it using the guidelines in this section to determine whether it should be fixed or removed. Code review policies should require that the test suite be free of broken tests before code can be merged.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"4_A_test_is_only_as_useful_as_its_environment\"><\/span><strong>4. A test is only as useful as its environment<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>The test environment is the most overlooked aspect of good testing. Thoughtful configuration of test environments and seeding state are critical to creating a system designed for high quality.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Set up test environments for consistency<\/strong><\/h3>\n\n\n\n<p>Inconsistency among test environments adds unnecessary complexity to debugging any issues revealed by testing.<\/p>\n\n\n\n<p>Therefore, use automation both to create and to deploy to test environments as part of your CI\/CD pipeline. Using automation to standardize test environments makes issues easier to reproduce, debug, and write tests for.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Make it clear what version of the code is being tested<\/strong><\/h3>\n\n\n\n<p>Have staging, QA, and production environments that reflect the stages in your development and release process. It should be clear what version of code is running on each environment, so when someone files a bug report in Jira (or whatever ticket management tool you use), you know who should fix it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Deploy each feature branch to its own environment<\/strong><\/h3>\n\n\n\n<p>Providing every feature branch with its own environment provides clarity around ownership of any issues and allows different teams to work independently without creating collisions that slow things down.&nbsp;<\/p>\n\n\n\n<p>Giving each feature branch an environment that developers can use for end-to-end testing also supports&nbsp;<em>shift left<\/em>&nbsp;practices, which help the development team catch new issues before code becomes too complex to easily debug.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Reset and deploy test data with your test environment<\/strong><\/h3>\n\n\n\n<p>For a measurable reduction in the complexity and maintenance costs of your tests, set up test data in your test environment that can shorten your tests.<\/p>\n\n\n\n<p>For example, if you want to confirm the \u201cpast orders\u201d page on an e-commerce site is working as intended: Instead of adding steps to the test to create backdated orders, automatically add sample orders to your test environment upon deployment.<\/p>\n\n\n\n<p>To avoid broken tests, reset the state of test data before each execution of your test suite.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Avoid shared state<\/strong><\/h3>\n\n\n\n<p>Running tests concurrently shortens the time it takes to get results. But when tests share test accounts and states, running them concurrently can create conflicts and subsequent false-positive test failures and incorrect validation that waste the team\u2019s time and energy. (Few things are more disruptive to the software development lifecycle than engineers having to investigate false positives.)<\/p>\n\n\n\n<p>Wherever possible, seed unique test data and account profiles to each test so all of your tests can run concurrently without colliding.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"5_Test_like_a_human_even_with_automation\"><\/span><strong>5. Test like a human (even with automation)<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Testing should represent the customer experience as much as possible. This means using the right kind of automation and recognizing when automation isn\u2019t a good fit.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Use the right kind of automation<\/strong><\/h3>\n\n\n\n<p>Many approaches to test automation evaluate what the computer sees in the DOM (behind-the-scenes browser code), not what your end users will see in the user interface (UI). To validate the actual user experience, use an automation approach that interacts with the UI.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Know when to use test automation and when not to<\/strong><\/h3>\n\n\n\n<p>Automation isn\u2019t a fit for every use case and testing phase. It\u2019s a great fit for rote, repetitive tests that don\u2019t require subjective evaluation and for features not subject to significant change.<\/p>\n\n\n\n<p>Manual testing is a better fit for new features still in flux, particularly complex use cases, situations that benefit from human judgment, and high-risk releases where manual testing can augment automation.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>After 10 years, we have a set of QA testing best practices that work for teams shipping fast and frequently.<\/p>\n","protected":false},"author":28,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","inline_featured_image":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-2003","post","type-post","status-publish","format-standard","hentry","category-qa-strategy"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/posts\/2003","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/users\/28"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/comments?post=2003"}],"version-history":[{"count":12,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/posts\/2003\/revisions"}],"predecessor-version":[{"id":2342,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/posts\/2003\/revisions\/2342"}],"wp:attachment":[{"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/media?parent=2003"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/categories?post=2003"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/tags?post=2003"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}