Necessity of Testing in Development and Difficulties to test in Obsidian

Introducing What I’m going through

Code in Dev State

import { ..., HeadingCache } from 'obsidian';

class HeadingNode {
    cache: HeadingCache;
    parent: HeadingNode | null;
    children: HeadingNode[];
    isTODOHeading: boolean;

    constructor(cache: HeadingCache, children: HeadingNode[] = [], parent: HeadingNode | null = null) {
        this.cache = cache;
        this.parent = parent;
        this.children = children;
        this.isTODOHeading = false;

        this.children.forEach(c => c.parent = this);
    }

...

    getFamilyTree(): HeadingNode[] {
        const DFSStack: HeadingNode[] = [this];
        const familyTree: HeadingNode[] = [];

        while(DFSStack.length > 0) {
            let node = DFSStack.pop()!;
            DFSStack.push(...node?.children.reverse());
            familyTree.push(node);
        }

        return familyTree;
    }
}

If you are not familiar with Markdown

With some conditions, my code traverse through headings and makes trees starting from H1 headings.

Necessity of Testing

The problem here is I can’t be sure that getFamilyTree() works properly or not. I know that sounds weird for a developer. But I have excuse for that.

As you can see, HeadingNode class has dependency on HeadingCache of Obsidian Plugin API. That means, I can’t run code snippet withtout running the whole plugin through Obsidian application. It’s not even worth mentioning it’s tiresome to test specific feature with running whole plugin.

Luckily, I have two options to deal with this problem.

Using dynamic debugging tool with dev tools

ctrl + shift + i (Windows) / opt + cmd + i open dev tools in Obsidian. This is the same dev tools you can meet when you are using google chrome or microsoft edge.

break point on the result of  with help of dev tools’ dynamic debugging.

break point on the result of getFamilyTree() with help of dev tools’ dynamic debugging.

It says content to show is organized in right order but each element’s headings are reversed.

Dynamic Debugging

Dynamic debugging is very important technique. Practically all used languages have tools for dynamic debugging. And for some cases, there are attachable debugging tools such as FRIDA, which can attach to running program and pause, watch, read program running information without shutting down the program.

You might wonder why I keep mentioning necessity of testing in development while I can check functionality of code with dynamic debugging. That’s because dynamic debugging is kinda last option for testing and debugging process.

Testing Process

In modern developing process, developer write code for a feature, and the same developer usually write a test code for the feature. For example,

// <project>/src/test/java/christmas/models/ChampagneGiftInDec.java
package christmas.models;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class ChampagneGiftIn2023DecTest {
    @ParameterizedTest
    @MethodSource("provideDateAndPrice")
    void testDateAndPrice(Date date, int price, Gift expected) {
        ChampagneGift test = new ChampagneGift(date, price);
        Gift actual = test.getGift();
        assertEquals(expected, actual);
    }

    static Stream<Arguments> provideDateAndPrice() {
        return Stream.of(
                Arguments.of(Date.getDefaultInstance(3), 120000, Gift.CHAMPAGNE),
                Arguments.of(Date.getDefaultInstance(2), 119999, Gift.NONE),
                Arguments.of(Date.getDefaultInstance(5), 1, Gift.NONE),
                Arguments.of(Date.getDefaultInstance(-1), 200000, Gift.NONE),
                Arguments.of(Date.getDefaultInstance(50), 200000, Gift.NONE)
        );
    }
}