"The kind of change I’m talking about here is refactoring your product code so that your tests can get at the parts they want to. There’s nothing wrong with using your testing as a way to challenge the modularity of your code. Usually, improving the structure to benefit tests also ends up helping you in other ways."
"Exceptions in the fixtures are counted as Errors, whereas exceptions in the test function are Failures. This is a subtle but important distinction. A Failure means the test ran, and detected a problem in the product code. An Error means we couldn’t run the test properly.This distinction is one reason to put set-up code in fixtures, but there are others as we’ll see."
"Testing code should not be difficult. If the code is hard to test, then the code is hard to use. This is also indicative of tightly coupled code."
"Steps in the setup process can be broken out into standalone fixtures that should throw errors if something didn’t go well. When those errors are thrown, they will show up as an error in the test report, rather than a failure in the test itself. When a test has an error, it should represent that the desired state for that test could not be reached. If a test fails, it should represent that the state was reached, but something about the state is wrong"
I came across these articles about pytest recently and thought they might be of interest to other developers here.
...one quote I liked:
"The kind of change I’m talking about here is refactoring your product code so that your tests can get at the parts they want to. There’s nothing wrong with using your testing as a way to challenge the modularity of your code. Usually, improving the structure to benefit tests also ends up helping you in other ways."
The below two were linked from the above presentation:This article also had some snips related to the quotes I already pasted above (emphasis mine):"Testing code should not be difficult. If the code is hard to test, then the code is hard to use. This is also indicative of tightly coupled code."
Otoh, Leo's Qt dock code depends on complex state set on program startup and shutdown, as well as arbitrary sequences of user actions. I agree that the code is hard to test and hard to use. That does not mean that testing that code will ever be easy. If anyone knows how to test the Qt code, I would appreciate knowing how.
That does not mean that testing that code will ever be easy. If anyone knows how to test the Qt code, I would appreciate knowing how.
Edward
That does not mean that testing that code will ever be easy. If anyone knows how to test the Qt code, I would appreciate knowing how.
[snip]
I really believe that doing decoupling piece by piece, module by module, can make Leo's code much easier to test and more importantly easier to (re)use.Vitalije
The way to achieve this is straightforward but it requires some effort. The coupled modules should be decoupled as much as possible. From the current state it is not obvious how decoupling can be achieved, but the decoupling process must start somewhere. It can't be started unless you as Leo's creator and main developer do not wish to consider decoupling as a worthy goal.
Let me give an example of the unnecessary couplings I have in mind.About a year ago, when I was experimenting with the history_tracer plugin, I wished to create a small window with the scale and a text widget. Changing the scale widget should show in the text widget the version of the selected node. I wished to have syntax highlighting in the text widget. Now, Leo has several classes related to highlighting the source code, but I could not use any of them. They are all coupled to the commander, so it is not possible to use them for highlighting anything other but the body widget.
The idea behind object oriented programing is code reuse. But with the current implementation of syntax highlighters in Leo the code reuse is not possible. I had to implement another highlighter if I wanted to make a qt display widget.
I really believe that doing decoupling piece by piece, module by module, can make Leo's code much easier to test and more importantly easier to (re)use.
> the history of re-implementations contains a lot of sad failures. It's just too big a job, the new implementations have their own bugs, and by the time they get done, the original version has developed beyond that the new one has implemented.I have no intention of doing a grand reimplementation. However, many of Vitalije's suggestions have lead to real improvements.
I'll look at all specific suggestions.
@@ -1444,57 +1450,61 @@ class LeoApp:@cmd('quit-leo')def onQuit(self, event=None):"""Exit Leo, prompting to save unsaved outlines first."""- if 'shutdown' in g.app.debug:+ app = self+ if 'shutdown' in app.debug:g.trace()- g.app.quitting = True- if g.app.loaded_session and g.app.sessionManager:- g.app.sessionManager.save_snapshot()- while g.app.windowList:- w = g.app.windowList[0]- if not g.app.closeLeoWindow(w):+ app.quitting = True+ if app.loaded_session and app.sessionManager:+ app.sessionManager.save_snapshot()+ while app.windowList:+ w = app.windowList[0]+ if not app.closeLeoWindow(w):break- if g.app.windowList:- g.app.quitting = False # If we get here the quit has been disabled.+ if app.windowList:+ app.quitting = False # If we get here the quit has been disabled.
As noted in the issue, it's not clear whether changing `g.app` to `self` in the LeoApp class would make any real difference. It might well mislead people.
When I awoke this morning I understood why you might suggest this. It would, supposedly, make it possible to instantiate multiple instances of the LeoApp class. I have just created #1537 for this.
As noted in the issue, it's not clear whether changing `g.app` to `self` in the LeoApp class would make any real difference. It might well mislead people.
The fact that g.app is a singleton instance of the LeoApp class is pretty much baked into Leo. For instance, g.app.db is an external file. Do we really want to complicate the code by creating a dummy instance of g.app.db in some cases?