3

We have been getting surprise random failures in our legacy Apex unit tests when certain types of modifications are made to process automation logic (usually new DML in triggers, flows, etc). I'm looking for ways to regularly monitor which Apex tests are closest to hitting SOQL and DML governor limits, so we can avoid these surprises.

Debug logs include information about usage vs. limits, but reading thousands of debug logs manually is not practical or repeatable. Do I have any other options?

2 Answers 2

6

As I was reading @sfdcfox's answer and considering why I felt it missed the mark a bit, I realized there's an approach that would meet my needs better.

The idea would be to create a test utility method that can be invoked from every test. A Boolean value in the utility (in my example, doMonitoring) can be toggled to force every test to fail artificially, and in the failure message, we report out on the usage stats.

Sample utility class:

@isTest
public class TestLimitsUtil {

    // Toggle this when monitoring usage vs. governor limits.
    // Could be custom metadata if you would rather have it configurable.
    private static final Boolean doMonitoring = false;
    private static String fullMessage = '';

    public static void monitorInnerTestLimits() {
        if (doMonitoring) {
            fullMessage = 'INNER TEST USAGE\n' + getLimitsMessage();
        }
    }

    public static void monitorOuterTestLimits() {
        if (doMonitoring) {
            fullMessage += '\n\nOUTER TEST USAGE\n' + getLimitsMessage();
            Assert.fail(fullMessage);
        }
    }

    private static String getLimitsMessage() {
        String message = 'CPU time: ' + Limits.getCpuTime();
        message += '\nDML statements: ' + Limits.getDmlStatements();
        message += '\nDML rows: ' + Limits.getDmlRows();
        message += '\nSOQL queries: ' + Limits.getQueries();
        message += '\nSOQL query rows: ' + Limits.getQueryRows();
        return message;
    }
}

Sample usage

@isTest
static void myUnitTest() {

    // SOME PRE-TEST LOGIC, QUERIES, ETC

    Test.startTest();

    // SOME INNER TEST LOGIC

    TestLimitsUtil.monitorInnerTestLimits();
    Test.stopTest();

    // SOME POST-TEST QUERIES, ASSERTS, ETC

    TestLimitsUtil.monitorOuterTestLimits();
}

I see the following advantages of this approach over @sfdcfox's:

  • You get usage statistics for every test, not just the ones that are in the danger zone
  • You get usage stats for the separate set of governor limits inside Test.startTest();/Test.stopTest(); as well as those outside
  • You can keep it toggled off most of the time, so it doesn't block production deployments
2
  • 1
    Nice idea. I'd take it one step further and just create a Custom Metadata so you don't need to a full code deployment each time you want to toggle this feature; you can just deploy the Custom Metadata record and run your tests. Bonus: you can deploy and run the tests at the same time. You'll get your output and the deploy will revert because of the failures.
    – sfdcfox
    Commented Jul 27, 2022 at 19:55
  • Agreed! I will add that the message string can probably be tweaked so that the data it provides can be ported into a spreadsheet without excess effort. This may require getting the test class and method names programmatically (see salesforce.stackexchange.com/questions/153835) and/or something like Excel's Power Query to split rows on the \n character. Commented Jul 27, 2022 at 20:17
2

How I manage this is to add assertions to each test, such as:

System.assert(Limits.getCpuTime() <= 9000, 'Exceeded expected CPU time of 9 seconds.');
System.assert(Limits.getDmlStatements() <= 125, 'Exceeded expected 125 DML statements.');
// ...

You can put this into a static method, update each of your unit tests once, and then you won't need to worry about this ever again. You'll get test failures when you exceed your metrics automatically.

6
  • aren't your operators backwards? < when it should be >=?
    – cropredy
    Commented Jul 27, 2022 at 18:48
  • 1
    @cropredy asserts stop tests when the value is false. So, I'm checking if it's less than 9000ms or I'll get an assertion error, but I guess <= would be better than <.
    – sfdcfox
    Commented Jul 27, 2022 at 18:52
  • 1
    I'm an idiot ....😳
    – cropredy
    Commented Jul 27, 2022 at 18:53
  • @cropredy lol, no worries. We all do this from time to time.
    – sfdcfox
    Commented Jul 27, 2022 at 18:54
  • Upvoting this since it got me halfway there in my thinking. I added another answer here that I think has some very important advantages, even if it is perhaps a little more sophisticated than what some readers might want. Commented Jul 27, 2022 at 19:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .