So-Called Advent Calendar Day 11 - Testing as Documentation
In the spirit of the holiday season, this is a series of short blog posts covering random things I have learned while doing Salesforce development, one for each day of Advent.
Yesterday we talked about documenting your application’s functionality outside of your code. But someone pointed out to me that your automated tests are also a form of documentation. Your automated test suite, whether it is Apex tests, LWC Jest Tests, or something like Seleniun, should not just test that the code runs but also validate the expected behavior. And a robust test suite should clearly describe those test scenarios.
I really like the format Jest uses when testing LWC. The describe blocks give you the freedom to describe each scenario clearly and the expected behavior within each scenario:
describe('Registering as a new user', () => {
it('should have the correct label and name', () => {
expect(lwcInput.label).toBe('Name');
expect(lwcInput.name).toBe('Name');
});
});
And when the tests run, you get a clear message as to what exactly is malfunctioning.
In Apex, you don’t quite have as many options. This is totally up to your team, but I try to mimic these describe blocks by using the method names of the tests:
@isTest
private static void shouldRegisterANewUser() {
Boolean result = MyPage.register('New Username');
System.assertEquals(true, result, 'The new user should be registered' );
}
@isTest
private static void shouldNotifyUserIfUsernameIsTaken() {
Boolean result = MyPage.register('Existing Username');
System.assertEquals(false, result, 'The user should not be registered' );
}
It’s a little crude, but it does make the test results more readable. By looking at the failed method name, you get a glimpse of the scenario that’s not working. It’s definitely a lot better than using generic method names and no assertions, an anti-pattern I have seen in way too many orgs:
@isTest
private static void test1() {
Boolean result = MyPage.register('Existing Username');
}
@isTest
private static void test2() {
Boolean result = MyPage.register('New Username');
}
With generic method names, you can’t really tell what’s being tested unless you go walkthrough the entire test method. The lack of assertions means you’re not really ever sure what the expected behavior should be. And without that information, just because the test checkmark is green doesn’t mean you have met the test’s intended criteria.
So-Called Advent Calendar Day 10 - Should vs. Does
In the spirit of the holiday season, this is a series of short blog posts covering random things I have learned while doing Salesforce development, one for each day of Advent.
I have worked in numerous Salesforce orgs and whenever I walk into a new org, I try to make an effort to audit the code to get a feel for the product. I figure, the application can only do what the code tells it to do, so the code is ultimate source of truth of the application’s functionality, right?
Technically that’s true, but just because an application functions a certain way doesn’t mean it should function that way. If it did, then there would never be any bugs! Let’s take an example of a piece of code that calculates some tax:
public Double calculateTax(Double income) {
return income * 0.25;
}
With just that alone, you would think “OK, the tax rate is 25% and we’re using a flat tax.” While that is technically true, that might not be the intention of how product wants the tax to be calculated. Now let’s look a the same method but with a comment:
/*
* @description Calculates tax using current US tax brackets
* @returns double
*/
public Double calculateTax(Double income) {
return income * 0.25;
}
Clearly that description is out of sync with what the method is actually doing and without it we would be none the wiser. It doesn’t necessarily have to be a comment, but the intended behavior needs to documented somwhere.
I used to believe that there should NEVER be comments because the code should document itself. But every product I have worked on isn’t just made by developers writing code. It’s a collaboration between product managers, stakeholders, customer feedback, and developers working together.
I actually don’t know what the ideal best practice is for documentation. Is it comments in the code? Extensive documentation managed outside of the code base? Through the project tracker? User guides? That’s up to your team, but what I know for sure is that code that is left to document itself is always incomplete. Documentation is the metadata for the functionality of the application and it needs to be easily accessible by everyone collaborating, not just hidden inside the code base.
So the next time you inherit a legacy code base, ask for any documentation on the intended behavior exists. If that documentation does not exist, your code audit shouldn’t define your application’s behavior - it should just serve as a starting place on clarifying what your application should do, as opposed to what it actually does.
So-Called Advent Calendar Day 9 - Block that scope!
In the spirit of the holiday season, this is a series of short blog posts covering random things I have learned while doing Salesforce development, one for each day of Advent.
When it comes to writing code, scope is used to describe how visible a piece of code is to the rest of your code. For example, if a variable is in scope, then it can be referenced. An easy way to define a scope is within curly braces. In Apex, I generally see curly braces used in a few contexts:
To define methods and classes
public class MyClass {
private static Object scopedToClass;
public class void myMethod() {
Object scopedToMethod;
}
}
To scope if statements
if (myCondition) {
Object scopedToIf;
} else {
Object scopedToElse
}
To scope loops
for(Object x : records) {
Object scopedToLoop;
}
But recently I noticed that you can you just use curly braces to create scope anywhere. For example, if you have a large method and want to limit the scope of parts it, you just use curly braces to create that scope without creating another method.
public void myMethod() {
Object inMethodScope;
{
Object inBlockScope;
}
//This would fail compilation because
//inBlockScope is out of scope of the rest of the method!
System.debug(inBlockScope);
}
I can’t think of many times when you would use this where a separate method or a separate class would be more appropriate. Maybe if you were really trying to manage some heap issues and breaking things out into a separate method isn’t really feasible? It seems a little farfetched, but it’s always fun to find another tool in the toolbox, just in case.