Currently, while tests are great for verifying that something works they are horrid for figuring out what broke. That's because if you don't have a real time debugger, you have no way of getting debug information.
- $this->fail("Some message") : Only works in the test itself, which is not all that useful.
- drupal_set_message() (and variants) : Batch API eats these somewhere.
- watchdog() : Oops! Gets saved to the simpletest tables, not the normal tables.
- print(), print_r(), var_dump() : Any output at all causes Batch API to break.
This makes fixing bugs discovered by tests, or even verifying that tests are doing what they should, far harder than it should be. We need some sort of "log this to show later" mechanism that works during a simpletest run and can be placed *anywhere* in the code and still work.
(Note: This may end up being a Batch API fix, not simpletest per se. I don't know there.)
| Comment | File | Size | Author |
|---|---|---|---|
| #11 | mw.patch | 1022 bytes | moshe weitzman |
Comments
Comment #1
wim leersI thought I was the only one feeling this way … I've pulled out many hairs over this.
Subscribing.
Comment #2
moshe weitzman commentedFirst, a debugger is well worth the (sizeable) setup effort. I posted some xdebug setup tips related to simpetest (see bottom of page).
For others, I think our only hope is to add a 'messsages' column to the simpletest table which stores a serialized array of messages. Developers could then call drupal_set_message('foo', 'debug'). We'd populate that column when we do the usual INSERT into the simpletest table. We could choose to present each message type separately on the final test results page.
Comment #3
drewish commentedit's actually the test runner that's calling drupal_get_message() the change was made as part of #310358: Add a test for file_save_upload and clean up file.test
Update: I'd initially linked to the wrong issue. Just corrected it
Comment #4
webchickSubscribe.
Sounds like we ought to at least revert that change to drupal_web_test_case.php, but I'm sure there's more that we could do.
@drewish: that code was in the file tests because they incorrectly pass when it's not there, or..?
Comment #5
drewish commentedwebchick, i'd had the drupal_get_messages() calls in to clear out all the expected error message that are generated when testing the file functions. someone else--i don't recall who at this point--had suggested moving that up to the test runner.
Comment #6
boombatower commented#296574: Provide debug facility for general and testing
I end up using:
works great for seeing what browser is up to:
and of course you can use FILE_APPEND.
I vote for getting something like debug methods, otherwise I'm really not sure what else can be done.
In addition I'm not sure what the intended role of the tests should be. I always though of them as a way to help find bugs, not debug them. That is much more complicated. Perhaps we should decide that and make changes accordingly. Either way having the dump to a file/screen would provide a way to get tests working when your written them, etc.
Comment #7
drewish commentedboombatower, i'd really like to have some kind helper functions for file based logging. one thing i often find myself doing is writing $this->_content to a file to find out why $this->assertText() calls are failing. often times having a file is much nicer than data in the session.
Comment #8
Crell commentedDumping to a file instead of to screen for a simple "what is the value of this variable at this point" is needlessly complex. Lately I've been using error_log(); I then moved the entire unit test to a menu callback, called dpm() a few times, and have a far better experience than simpletest can even dream of.
One of the principles of test driven development is to write the test first, then write the code the test is calling and poke at it until the test passes. Find a bug? Write the test for what it SHOULD do, then poke at the code until it passes. If you're doing that, then your primary development is happening within a testing context. That means your debugging will be, too. (What else is primary development but the removal of bugs?) If simpletest is not able to handle debugging, then it fails at supporting test driven development.
As that is exactly the approach I'm taking for nearly all of my database work, simpletest becomes a hindrance, not a help.
Comment #9
wim leers@Crell: I agree, but you're missing something. The situation you're describing, only applies to unit testing (i.e. a piece of well defined code that should return X if passed Y).
But what about functional testing? For example, it's surreal to apply that process when you're designing a form in Drupal: you design the form first, then add testing. After that, a loop begins: refine the form, make the test more granular, repeat.
Anyway, the point still stands: easy debugging is a necessity.
Comment #10
moshe weitzman commentedI'v been pondering this and I'll agree know that a filesystem based debug function is all we can hope for. The filesystem is all that persists in our wacky simpletest hyper isolated environment (@Crell - you really should look into scripts/run-tests.sh. you wil have new appreciation for the complexity I think. Or you will sugegst a better way) ... The debug function should live in bootstrap.php since that gets loaded early on. It can't live in simpletest module since thats not enabled in the instance that gets installed (currently this is the 'default' install profile).
Something like:
I'll give this a try now.
Comment #11
moshe weitzman commentedOK, this is proving useful. It is a bit nicer than error_log() because it prints complex variables and outputs an optional label.
patch rolled from /includes because my tree is bit tricked out ATM.
Comment #12
moshe weitzman commentedComment #13
boombatower commented"our wacky simpletest hyper isolated environment"
/me is proud of the isolation since it prevents you having to periodically re-install your testing environment after running tests, not to mention those tests that fail after one test changes something they count on. Drupal's use statics makes this rather hard. It makes writing tests much simpler.
I like the output function. I'll take a look at the patch when I get home.
Comment #14
damien tournoud commentedI suggest the following course of action:
1) we add a "data" column in the test result table, that will be populated:
- with the full page fetched by drupalGet, drupalPost and similar calls
- with the full backtrace on error and exceptions
2) we implement a new drupal_debug function that:
- simply calls dsm() when ran in a normal site
- record a "debug" assertion type (we currently have only 'pass', 'fail' and 'exception') when ran in testing mode. In that case the "data" column could be populated with the full context (local variables, backtrace, etc.)
Comment #15
moshe weitzman commented1. Thats a good start ... A single test can do many drupalGETs. Do you suggest to automatically record all of them? Also, how to present this info? P
2. The assertDebug is a great enhancement, but does nothing for when you are on the 'tested' side. The backtrace and such would be keyed by to the assertion ID? Again, how to present this on the web and in run-tests.sh?
Comment #16
damien tournoud commentedThe impact on performance and storage requirements for this remains to be evaluated. We could also simply record that info when drupalPost processing fails (submit button not found, unmatched fields, etc.).
As far as display is concerned, I'm open to suggestion. Maybe simply adding a "show data" link to the result page, that display this in a new window (or something, I know popups are not really loved these days).
The information from the tested side could be fetched by the same mechanism put in place by #243532: Catch notices, warnings, errors and fatal errors from the tested side. This is just waiting for Angie to make up her mind on #304924: Extend drupal_error_handler to manage exceptions, that is a dependency.
For display on the web, the same mechanism as (1) could apply. For run-tests.sh, everything remains to be done (you know that the text reporter is awful :p, could you contribute back the one you wrote for Drush?).
Comment #17
Crell commentedRE #14: I don't need a full dump of every request. The unit tests I'm writing don't even make HTTP requests at all. I need some form of print_r/var_dump/dpm() that I can stick into arbitrary code and get easy access to the output. Real time debuggers don't like Batch API either, so this is just about the only alternative I have to guess-and-check when trying to track down a bug.
@WimLeers: True, I am describing unit tests, not functional tests. That's because I've never actually written a functional test in Drupal; I find unit tests far more useful for the sort of work I do. :-) Both are important, however, I agree.
@All: within the test itself,
$this->assertTrue(TRUE, $some_debug_message);kinda works well enough, although it would be nice if it was separated out more, visually. The problem is that doesn't work outside of the test itself, because $this won't dereference properly. Some equivalent of that which works outside of the unit test would be ideal. I don't know the guts of simpletest well enough to say how best to do it, however.Comment #18
boombatower commented"We could also simply record that info when drupalPost processing fails (submit button not found, unmatched fields, etc.)."
The assertions already tell you all of that.
Storing all this data in addition to the already large amount of data stored by assertions seems a bit excessive, at least to do automatically.
Looks like the debug method is up for debate, but I still prefer and will most-likely use a file_put_contents as viewing the actual page as SimpleTest sees it is one of the most useful things I can do. Now this is obviously a difference between Functional (majority of current core tests), and unit testing (by far minority). That is not to say that we shouldn't tailor some of the tools towards unit testing, just to say that SimpleTest is in the state it is based on functional testing.
Unit testing was discussed and washed over in general so much of the good ideas were never implemented.
I suggest some sort of methods that help with unit testing, but don't add extra load to already large load created by functional tests and create the file dumb method that those of us who write functional tests find useful.
Comment #19
Crell commentedMostly because Drupal is architecturally very ill-suited to unit testing. It's just not built for it. That's something that we do need to change, and I'm working on in bits and pieces, but making it easier to to TDD will mean more people make use of proper unit tests, which in turn gives us better APIs. So making it easier to write and debug unit tests for future code is arguably more important than figuring out how to make unit tests for our existing code (which is, I agree, a losing battle).
It does sound like we need two separate debugging mechanisms here, one micro-level (for unit testing) and one macro level (for functional test "core dumps").
Is there a way perhaps that simpletest could intercept or pull out watchdog() calls of level E_DEBUG? Or could we, since we can rely on PHP 5, route error_log() or similar to a special session-based queue (complete with line numbers automatically) that gets retrieved later?
Comment #20
webchickhow about $this->assertDebug('message'); that gets highlighted in blue or something and instantly gets floated to the top in the test results page?
Comment #21
Crell commentedwebchick: That would work fine within the unit test itself. It would not work in the actual code being tested (say, the DB logging code I'm working on now) because $this won't refer to the simpletest object. We need some way to get the effect you describe, but be able to put the debug line anywhere.
(The last bit of the DB logging that is needed is a simpletest fail that I cannot replicate except within the unit test; the same code run outside of simpletest works. Hence my need to be able to figure out wth is going on. :-) )
Comment #22
boombatower commentedLets make this issue focus on unit testing side of thing, since obviously that was the original purpose of this issue.
The other issue I opened can focus on functional testing.
Comment #23
damien tournoud commentedEdit: this should in fact go in the other issue.
Comment #24
damien tournoud commentedchx pointed out that with both #304924: Extend drupal_error_handler to manage exceptions and #243532: Catch notices, warnings, errors and fatal errors from the tested side, adding debugging statements will be uber-easy: simply call
trigger_error()(or a very simple wrapper around that we could make), and the error handler will take care of everything for you.So in fact this should be postponed until #243532: Catch notices, warnings, errors and fatal errors from the tested side (or a variant) goes in. After that, we could consider adding a
drupal_debug(), some kind of wrapper aroundtrigger_error().Sorry boombatower, you were right: we should indeed separate those two issues.
Comment #25
moshe weitzman commentedFYI, I committed drupal_debug() and its alias dd() to devel.module. Hope we can get this into D7 though.
Comment #26
moshe weitzman commentedI did some testing an trigger_error works great from either side but it can only pass strings. Would be nice to log more complex variables like array/object.
Comment #27
moshe weitzman commentedComment #28
boombatower commented/me still likes a file_put_contents() wrapper.
Comment #29
david straussSubscribing.
Comment #31
cwgordon7 commentedTheoretical question: why not start an output buffer before a SimpleTest starts running, get the contents as it ends, and display them as either a SimpleTest exception or on the batch API page?
Comment #32
CorniI commented@#20 and following: Why don't you use a static method, then you can use $this-> in the unit tests, and the static method by className::etc outside.
Comment #33
Crell commentedYou know... I think I prefer #32. Kinda bastardly and the class would have to session-save the data itself to get around batch API, but it's probably the simplest approach.
Comment #34
naxoc commentedsubscribe
Comment #35
soxofaan commentedI've also pulled some hairs out over this one.
I'm currently experimenting with
file_put_contents(), but it feels a bit clunky and ad hoc (a wrapper like the suggesteddrupal_debug()makes it a bit better).(subscribing too)
Comment #36
soxofaan commentedA note about the usage of devel's
drupal_debug()(pun intended):drupal_debug()writes to a filefile_directory_temp(). '/drupal_debug.txt'.Now, on my setup
file_directory_tempwas configured to something else than the default.When I use
drupal_debug()outside a simpletest context, it writes to that directory as expected.However, in a simpletest context (with fresh database tables and configuration)
file_directory_tempis just the default (unless changed explicitly in a test function of course), sodrupal_debug()writes to the defaultfile_directory_tempinstead.I guess this dual behavior may initiate some (additional) hair pulling, so I thought it deserved to be mentioned it here.
Comment #37
moshe weitzman commentedTrue that. The tested site is virgin Drupal so lots of settings like temp dir are set to defaults. I'm not sure if simpletest lets you configure which install profile it uses - that would be a handy fix. One could use a customized install profile here. Would be great for adding in a base set of data and so on.
Comment #38
cwgordon7 commentedYou can always just pop the database prefix back to the original, query the {variables} table, and then push it back.
Comment #39
moshe weitzman commented@cwgordon7 - i don't think the tested side knows thye db prefix of the original. you are probably assuming it is no prefix.
you could use a variable override in your settings.php
$conf['file_directory_tmp'] = '/example';
IMO, this should debug function should be in core
Comment #40
cwgordon7 commentedThe tested side has to know the db prefix of the original, otherwise it wouldn't be able to write assertions to the original database...? Not sure if I understand what you're saying. If you're talking about pages that are called as a drupalGet()/drupalPost(), it's simple enough to pass the original database prefix to those pages.
Comment #41
robloachGrrr.
Comment #42
boombatower commentedYea inside drupalGet()/drupalPost() it would need to be passed....I'll take some time this week and see if I can create some sort of solution.
Comment #43
Crell commentedIf the testing framework were updated to use separate DB connections, then the new API easily allows for the testing system to write back to the original database by connection ID, regardless of its prefix. There's a patch for that somewhere. If it hasn't gone in yet, it needs to.
Comment #44
boombatower commented@Crell: If you point me to the patch, any pointers on how to use it (if necessary), and get that committed I would be happy to work on implementing it within SimpleTest.
Comment #45
boombatower commentedLets focus all conversation at #296574: Provide debug facility for general and testing.