Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add unit testing framework and start writing unit tests (acceptance tests) #708

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

leonbubova
Copy link
Contributor

@leonbubova leonbubova commented Aug 1, 2022

Hey everyone,

I had a look at the codebase again and there are some opportunities to refactor thinks that were build back in the days, but before we do that we should have tests in place to make sure we don't introduce bugs while refactoring.

That's why I added jest for automated unit and integration testing to the project. I didn't write tests for everything yet, but started with the utils.js since they were easy to take apart.

Two things I made sure to do:

  • I tried to not touch the code that I was testing (other than 1 exception that was necessary -> see changes)
  • I am basically writing acceptance tests only for now (Which means I am strictly documenting the current behaviour of the code (There is one example where I am testing behaviour that I would say is buggy currently -> see changes)

The reason behind that is, that it is easier to strictly cover the codebase with tests first and have them as a contract for existing code and later go ahead and change the codebase with the tests as security net.

I used jest as the testing framework because it's in my opinion the most widely used and we can expand it to integration tests as well later on as well (which we should do to minimize manual testing needed when releasing changes in the future)

Let me know what you think of the changes, I'll try and keep my changes conflict free until it get's merged (or declined)

That's the current code coverage report:

image

…stead of just executing it

Otherwise this code is executed everytime state.js is imported somewhere in a file.
This leads to not being able to test properly, since state.js is imported in a lot
of the src files and fails when running the tests since we are missing global environment
settings such as document, window etc in the jest environment.
Comment on lines -49 to -76
let mutationObserver = new Object();

if (isShorts() && mutationObserver.exists !== true) {
cLog("initializing mutation observer");
mutationObserver.options = {
childList: false,
attributes: true,
subtree: false,
};
mutationObserver.exists = true;
mutationObserver.observer = new MutationObserver(function (
mutationList,
observer
) {
mutationList.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.target.nodeName === "TP-YT-PAPER-BUTTON" &&
mutation.target.id === "button"
) {
// cLog('Short thumb button status changed');
if (mutation.target.getAttribute("aria-pressed") === "true") {
mutation.target.style.color =
mutation.target.parentElement.parentElement.id === "like-button"
? getColorFromTheme(true)
: getColorFromTheme(false);
} else {
mutation.target.style.color = "unset";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the one place where I had to change existing code, to make testing possible.

The problem was, that this code was always executed whenever state.js was imported somewhere. But because in there are variables that were not existing in the test environment, there were errors all around the place.

I moved to code to it's own function initMutationObserver() and exported it, to be used in the one place where it should be called. That's what we should aim for when using modules in the future. Loose code otherwise will lead to problems.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. Mutation Observer was a new feature introduced in the last code merge. It was very much needed as to fix several bugs and adapt to the new and weird Youtube behaviors. Will fix soon. Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, I think I fixed it without removing behaviour by putting it into it's own function, but feel free to have a look and verify that :)

Comment on lines +158 to +160
roundDown,
numberFormat,
getNumberFormatter,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I exported those as well to make them testable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they need to be exported by you to complete the test, does that mean it's better to do it in the source code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly do you mean by

does that mean it's better to do it in the source code?

But one thing to add: Typically if something is not exported, it means it's an implementation detail and should not be tested separately, but rather by testing the public/exported functions that use them. But in this case I decided to make them testable since the exported function is a rather big unit of code. But i someone disagrees we can always undo that and remove the tests for roundDown and getNumberFormatter again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it better to make all modules testable? In this case, the round down logic, and number formatting logic. They may be re-used by other places in the future, so better define it in a more standardized way. Plus, the mutation observer, which is definitely gonna be re-used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I see. Good question. There is not a general consensus on that question to be honest. Some say "There is a reason that a function is private/not exported", others say "it's better to have all code under test". Having everything under test can lead to having to rewrite the unit tests more often when you refactor, but for the state the codebase is in right now, I would lean to let's have everything testable. Because generally I would say the functions are doing quite a lot of things and once we have everything covered in tests I would love to break specific parts up in more atomic functions. Those could then be private/not exported/untested.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. Thanks.

expect(getNumberFormatter()).toMatchSnapshot();
});

it("should return a correct formatter when using the URL locale", () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra info: I am planning to write the acceptance tests in this format as mentioned in the summary already, because it makes it easier to cover the codebase with tests. If I assume there is a bug in the code, I write the test so that they are green with the current code, but also add a section what the correct test code should be to trigger the buggy behaviour.

These can be addressed later on then, when code coverage is complete. By doing this approach we can minimize introducing new bugs to the codebase by refactoring.

Comment on lines +3 to +7
collectCoverage: false,
collectCoverageFrom: [
"Extensions/combined/src/**/*.js",
"Extensions/combined/*.js",
],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabled code coverage collection for now for performance reasons, but it can be enabled by changing collectCoverage to true

@Anarios
Copy link
Owner

Anarios commented Aug 1, 2022

Amazing work, this was in my plans for a looong time.

@leonbubova
Copy link
Contributor Author

@Anarios (or anyone else who knows): I was gone for some time before opening this PR so I am not sure what the current approval/review workflow is. I see there are 6 reviews required. Do I have to actively request those, or what's the best process?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants