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

represent various objects when logging better #98

Merged
merged 19 commits into from
Oct 4, 2023

Conversation

bandophahita
Copy link
Contributor

@bandophahita bandophahita commented Aug 23, 2023

This is a fix for the way different objects are represented when logging.

  • strings will use repr.
  • matcher objects that have a describe_to function will describe themselves.
  • all other objects will surrounded the __str__ output with angle brackets <1>, <True>, <MyCustomObj>

Copy link
Member

@perrygoy perrygoy left a comment

Choose a reason for hiding this comment

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

I have a lot of questions below, i'm not sure i fully understand the purpose of these changes.

screenpy/resolutions/contains_the_item.py Outdated Show resolved Hide resolved
screenpy/speech_tools.py Show resolved Hide resolved
screenpy/speech_tools.py Outdated Show resolved Hide resolved
tests/test_resolutions.py Outdated Show resolved Hide resolved
tests/test_resolutions.py Outdated Show resolved Hide resolved
tests/test_resolutions.py Outdated Show resolved Hide resolved
@bandophahita bandophahita changed the title show the repr of a string object when logging represent various objects when logging better Aug 24, 2023
Copy link
Member

@perrygoy perrygoy left a comment

Choose a reason for hiding this comment

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

Sorry, i had a few more things that i think we should address on this one before merging.

I now fully understand the reasoning and the benefit of adding this speech tool, and thank you for doing all this work for it! I think it could be the first of several narration-massaging tools that we could offer our users in the speech tools module.

tests/test_actions.py Show resolved Hide resolved
Comment on lines +608 to +669
assert [r.msg for r in caplog.records] == [
"Tester sees if all of 4 tests pass:",
" Tester sees if fakeQuestion is equal to <True>.",
" ... hoping it's equal to <True>.",
" => <True>",
" Tester sees if fakeQuestion is equal to <False>.",
" ... hoping it's equal to <False>.",
" => <False>",
# don't be fooled! the next few lines do not have commas on purpose
" ***ERROR***\n"
"\n"
"AssertionError: \n"
"Expected: <False>\n"
" but: was <True>\n",
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this test belongs in this test class. I think we should put it in TestSee.

I do think it's worth it to have a test that each of the Actions which now use represent_prop are representing the props how we want them to be represented, but SeeAllOf doesn't directly use it.

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 think this test isn't only confirming that represent_prop does the right thing. It's a confluence of everything that goes into making SeeAllOf work as expected.

  • beat messages have to format and indent properly
  • settings have to work properly
  • See has to function properly
  • SeeAllOf has to loop through each of the passed in tests.

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 think of it like this -- we want to prove SeeAllOf calls See, we can do that with another test showing how many times it gets called. But say someone tries to sneak in the following change:
the_actor.should(Silently(See.the(question, resolution)))
The test with the number of calls wouldn't catch that whereas this test would.

Copy link
Member

Choose a reason for hiding this comment

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

I dunno, i think it's still in the wrong place and is (well, would be—we don't have the test that i'm suggesting in TestSee) testing things that are already tested elsewhere.

I personally tend towards not having tests that are a group of tests we already have unless that integration is testing something new. I don't see anything new being tested with this test, aside from the test that is missing from TestSee of course.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In principle I agree that unit tests should be as atomic as possible. I've always struggled with testing functionality that basically coalesces multiple functionalities. Each of the smaller functions have been tested but how to you prove they all came together in the right manner? I found that at some point you'll get some overlap.

"We aren't proving the sugar is sweet, we are proving we used it in the recipe."

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 added two tests to See to prove the logging works there as expected.

Copy link
Member

@perrygoy perrygoy Sep 26, 2023

Choose a reason for hiding this comment

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

For me, i want to know why a test at this level failed just by reading the title, if possible.

I think you might like functional testing, which test things as they would be used by a user, rather than all this fiddling with unit tests. So instead of testing each part down here, we'd have a test that was like:

def test_click_does_a_click(self, Tester):
    target = Target.the("test").located_by("//test")

    Tester.attempts_to(Click.on_the(Target))

    # assert the dummy element was asked for by "//test"
    # assert the dummy element had `.click()` called

It's a pretty interesting concept that turns the tests into more documentation on how the developers expect the tool to be used. If you remember a few years ago, the tests in here used to be kind of like that, until i realized i could just do Click().perform(Tester) instead and skipped a layer.

I think the tests as they are now are easier to organize, but i did kind of like how those older tests looked.

Copy link
Member

@perrygoy perrygoy Sep 26, 2023

Choose a reason for hiding this comment

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

I can't find the article now that introduced me to the concept because i specifically remember it going on at length about how neat it is that someone wanting to use your library can just look at your tests directory to see how to use it. :(

I do remember i had found it right after i finished changing all the unit tests to how they are now, though, so i didn't want to change them all back. Haha.

Copy link
Contributor Author

@bandophahita bandophahita Sep 28, 2023

Choose a reason for hiding this comment

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

These logging tests are probably closer to functional rather than unit tests. I think the trick to any test suite (especially a public library) is no one way will suffice. We want as comprehensive as coverage as we can get.

For instance, if we were to completely rewrite SeeAllOf to no longer use See but we still wanted the logging to output the same, we need a test to show that it works as expected. This treats the action like a black box. There may be overlap at a unit test level, but only because we know what's inside that action.

Copy link
Contributor Author

@bandophahita bandophahita Sep 28, 2023

Choose a reason for hiding this comment

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

I want to know why a test at this level failed just by reading the title,

Perhaps I should rename these tests. Something like test_log_first_failure ?

tests/test_actions.py Show resolved Hide resolved
tests/test_actions.py Show resolved Hide resolved
tests/test_resolutions.py Outdated Show resolved Hide resolved
tests/test_resolutions.py Outdated Show resolved Hide resolved
tests/test_resolutions.py Show resolved Hide resolved
tests/test_resolutions.py Show resolved Hide resolved
tests/test_resolutions.py Outdated Show resolved Hide resolved
Comment on lines +63 to +66
def test_int(self):
val = 1234

assert represent_prop(val) == "<1234>"
Copy link
Member

Choose a reason for hiding this comment

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

If we wanted to, we could parametrize this as test_everything_else and verify that a bunch of different classes get wrapped in the <>s.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're not wrong...

Separating them gives you a clear indication of where a bug lies. If somehow we only break how ints are displayed, when we run the tests TestRepresentProp:test_int will inform us. While in this case that's not a significant difference in how we would go about fixing it, it is often helpful to have that separation to quickly diagnose a bug.

Code wise it's a couple extra lines. /shrug

Copy link
Member

Choose a reason for hiding this comment

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

Parametrized tests still show what the values are, so we'd have that same level of granular feedback. We can also give each parameter'd test a specific name if we'd like to.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair enough. I updated them.
I have a tendency to only use parameterized tests when there are 3 or more variations.

perrygoy
perrygoy previously approved these changes Sep 23, 2023
Copy link
Member

@perrygoy perrygoy left a comment

Choose a reason for hiding this comment

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

Some final thoughts and suggestions, but they're no longer blocking.

I'm excited to get this one in!

@@ -54,9 +55,9 @@ def as_(self: SelfMakeNote, key: str) -> SelfMakeNote:

def describe(self: SelfMakeNote) -> str:
"""Describe the Action in present tense."""
return f"Make a note under {self.key}."
return f"Make a note under {represent_prop(self.key)}."
Copy link
Member

Choose a reason for hiding this comment

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

This could be key_to_log instead, just in case we change our mind on represent_prop.

Copy link
Member

Choose a reason for hiding this comment

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

(And it looks like that's the approach you have in the other describe methods, to use *_to_log.)

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've always worried that setting the variable in the constructor would somehow lead to a problem if anyone were to alter the attribute being logged after instantiation. It might be a non-issue but it's one of the reasons I try to avoid it as much as possible.

We want the value coming out of describe to be accurate at the moment it is called.

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 forgot that part of the reason we have *_to_log is for the beat message. I kinda want to go through each of the classes and make those attributes properties.

Copy link
Member

Choose a reason for hiding this comment

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

I think they will not need to be around any longer, ideally, once we figure out this whole update to the logging bit. Or maybe the description/beat itself becomes a property, i think that would be slick.

Either way, let's do the describe reimagining before we do a change like that, because it might not be necessary in that brave new world.

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 think that would be slick.

It really would. the only reason I didn't earlier is because the pattern wasn't already being used. I'll open an issue to add those at some point.

Comment on lines +608 to +669
assert [r.msg for r in caplog.records] == [
"Tester sees if all of 4 tests pass:",
" Tester sees if fakeQuestion is equal to <True>.",
" ... hoping it's equal to <True>.",
" => <True>",
" Tester sees if fakeQuestion is equal to <False>.",
" ... hoping it's equal to <False>.",
" => <False>",
# don't be fooled! the next few lines do not have commas on purpose
" ***ERROR***\n"
"\n"
"AssertionError: \n"
"Expected: <False>\n"
" but: was <True>\n",
Copy link
Member

Choose a reason for hiding this comment

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

I dunno, i think it's still in the wrong place and is (well, would be—we don't have the test that i'm suggesting in TestSee) testing things that are already tested elsewhere.

I personally tend towards not having tests that are a group of tests we already have unless that integration is testing something new. I don't see anything new being tested with this test, aside from the test that is missing from TestSee of course.

tests/test_resolutions.py Outdated Show resolved Hide resolved
perrygoy
perrygoy previously approved these changes Sep 26, 2023
Copy link
Member

@perrygoy perrygoy left a comment

Choose a reason for hiding this comment

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

Re-approving! Thanks for the extra TLC!

@bandophahita
Copy link
Contributor Author

@perrygoy I updated those tests and rebased with trunk. I'm sorry to make you review it again.

Copy link
Member

@perrygoy perrygoy left a comment

Choose a reason for hiding this comment

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

Re-re-re-re-approving! I've got more where that came from.

@bandophahita bandophahita merged commit 72481d8 into ScreenPyHQ:trunk Oct 4, 2023
9 checks passed
@bandophahita bandophahita deleted the loggin_strings branch October 9, 2023 23:54
bandophahita added a commit to bandophahita/screenpy_adapter_allure that referenced this pull request Feb 19, 2024
bandophahita added a commit to ScreenPyHQ/screenpy_adapter_allure that referenced this pull request Feb 26, 2024
* initial deps
* initial project sync (via cruft)
* ruff ANN
* updated test based on changes in ScreenPyHQ/screenpy#98
* ruff ARG
* ruff D
* ruff EM
* ruff FA
* ruff I
* ruff TCH
* ruff PGH
* ruff PIE
* ruff PT
* ruff RUF
* ruff SIM
* ruff UP
* black formatting
* fixing mypy namespace error
* adding copyright tests
* adding namespace test
* fixing name of mocked item since it's being patched at the class level.
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.

2 participants