Pynguin – Generate Python unit tests automatically
github.com
github.com
(I'm one of the authors of icontract-hypothesis.)
I find contracts personally super useful as I can express a lot of relationships in the code trivially and have them automatically verified. For example, when this input is None, then that output needs to be positive. Or if you dele the item it should not exist in this and that registry, and some related other items should also not exist any more.
My email is in the commits of the repository, feel free to contact me and we can have a chat if you are interested in a larger picture and more details.
I tend to limit myself to simple contracts and do table tests for the complex cases to reap the benefits of the both approaches.
Do you have in mind some technique to turn contracts into tests or tests into contracts automatically, in order to close the gaps in quality control?
For other functions, you write some obvious contracts (so that those are also tested in integration tests or end-to-end tests), but since writing all the contracts would be too tedious or unmaintainable, you test these functions additionally using, say, table-driven tests where you specifically test with data points for which you could not write the preconditions or you check the post-conditions manually given the input data. For example, sometimes is easier to generate the input, manually inspect the results and write the expected results in the test table.
> [...] turn contracts into tests [...]
icontract-hypothesis allows you to ghostwrite Hypothesis strategies which you can further refine yourself.
For new code, not so much. The generated tests wouldn't prevent bugs by themselves, but they may uncover bugs if you see assertions that don't make sense. But you have to spend time reviewing the tests.
The tests also don't describe what they are testing in human terms so you have to refactor them before commiting.
They also would end up testing more than necessary. Sometimes the code behavior might be intentionally ambiguous, not expected to matter in the real world and behavior that will likely change in the future that you should not depend on today.
That is a bad thing and should be addressed. It’s code like this that will inevitably cause you pain later and very likely become significant code debt.
From what I gather from the paper, they frame the problem of test generation as a search problem. An evolutionary algorithm randomly mutates a randomly generated test suite. The evolutionary algorithm optimizes for greater branch coverage.
Excerpt from the abstract:
"Our experiments confirm that evolutionary algorithms can outperform random test generation also in the context of Python, and can even alleviate the problem of absent type information to some degree. However, our results demonstrate that dynamic typing nevertheless poses a fundamental issue for test generation, suggesting future work on integrating type inference"
The point of tests for me is to express intent for what the software should do. The code then expresses the details of how we do it.
I could see a use for evolutionary algorithms to probe code for hidden bugs. But even there it seems limited. I definitely see a use for things like Hypothesis, which makes test expression more powerful: https://hypothesis.readthedocs.io/en/latest/
It makes sense to me that this comes from an academic perspective, and not from people who actually make software for a living.
This can be very useful if you are dealing with legacy code bases or just don’t want to write tests yourself.
I agree that if your are going to write the tests, using hypothesis is a very wise decision.
I would much rather inherit a code base with zero tests than ones automatically generated by people who "just don't want to write tests".
Any test is better than none, I’m my opinion.
Consider a case when you have a code that takes about 100 input parameters and makes a decision on whether something should or should not happen. The logic that determines the decision is about 1,000 LOC that doesn't divide easily into smaller chunks. The code consists of a lot of different conditions and calculations based on the input parameters and some constants.
Nobody knows for sure or can explain how every single line works or why is it there, but it seems that everything works correctly based on the decision.
Writing tests manually might be feasible, but is very impractical considering that you don't know whether the code is correct or not in the first place. The code is being modified on regular basis: new inputs are added and logic is changed. Any such change would break the manual test. In my experience, nobody will spend an hour studying why the test broke and do the "right" fix. It will most likely be the easiest fix that makes the test pass.
What you describe is the opposite of what I expect from (modern) fintech.
* no tests
* god method with no proper documentation/requirements
* no one actually understanding the function deciding how much money to send! Isn't this one of the core functionalities of fintech products?
This code was around before I joined. It started small, but more and more requirements were added until it became like that. Not much effort went into structuring it. Now it's my responsibility, and I'm trying to make it better for me and anyone who will have to deal with it in the future.
While one would unit test with tiny expressions, often bugs only show up in large integration tests, as systems use many optimisations to cache and speed up behaviour, which can interact poorly
Whilst an automated test suite can at least check if the code may have broken - you'll never know if the break is significant or just an inconsequential side effect of a new piece of data. And if both the code and tests aren't understood, as you say the "right" fix won't be done and something will be added quickly to fix the test.
- you don't know whether the code is correct or not
- code is being modified on regular basis... and logic is changed
If someone knows enough about the code to regularly change it, then they should be able to provide some documentation? How else are they able to modify a "correct" function and know that it's still correct?
Can I guess that the code modifiers are quants, or something?
If no one is able/willing to refactor the code into understandable and testable bits, there's not much left to guarantee the correctness of it.
If, for example, someone intends to refactor the code into something more maintainable and wants tests to prevent any accidental changes in functionality, then this is useful.
If the goal is to catch bugs or to facilitate future development or to meet some test coverage standard then I argue you might be better off not having tests rather than having auto-generated tests that have not been carefully reviewed.
Used that to create a test suite that we could then maintain normally.
Much better than random.
(Although I suppose if someone wanted to generate tests without ever executing them the warning would be relevant.)
> tests should be written from requirements
TDD might be too big of a debate to fit in an HN comment thread :)
But there are a lots of projects out there that don't, and write tests afterward.
I do.
It's usually much, much faster and easier for me to write the tests after I figure out how to make what I want to work. I sometimes need to change the API a little to be more testable, but it's still faster.
In fact, if I write a test first, I have to write it with an abstract idea on how my code will work and need to understand the problem perfectly, which I rarely do. My understanding of the problem and solution grows as I write the code. It helps me ask the right questions, it reveals issues I didn't think about, I shows part of the API I didn't know by heart and above all, it cleans my initial idea of the workflow for this part of the code.
So if I write the test first, it will take a lot of time to think it through, and I will realize later I got it wrong and have to rewrite it anyway. You could argue I need to better define my problem or get better specs, but my experience is that they are always wrong as long as you haven't written any code: "no plan survive the contact with the enemy".
Not to mention you may not have written the untested code in the first place.
So I'd love a tool that can output test boiler plate for me.
Personally I use TDD to have an easy entry point to run through my code, so the first instance of a test for an api might only check status code
It makes the process very iterative, as you can continuously run through your code just by saving
You can of course write the test to simulate all that, but's way harder than just using your user UI which lets you discover it and put a breakpoint in the endpoint to inspect. I may not remember where to get the header data in this framework, or maybe the client send me a weird queryset param and I must figure out why, or I my validation is failing in some case and I must check it out. I can't simulate something I don't know about or don't understand yet.
Plus using the UI will make you realize you forgot to limit input length or what happen if the user doesn't have any phone number or if it's tuesday.
The minimal use case you will write your code against will likely be only good in theory in your mind unless you do something very basic. You will probably throw it away in the end in exhange for IRL code learned by feed back, and you'll have written all that for nothing.
This is were TDD proponents say ypu shoud write the minimal case and see all that stuff incrementally, but again, it's way easier and faster to just fiddle with the UI and write the code I need then test each case that imagine each case incrementally, and find the proper incatation to make the whole thing works.
I'm exagerating for the sake of the argument, but that's the result I get every time I work on a project where the team requires TDD. Incidently, those are the projects with the worst UI as well, because dev don't use it enough.
i don't think that anyone would disagree that you can get caught up in writing too many tests too early, consequently loosing sight out of your objective and ending up with a bad implementation.
My point was that just writing super easy and happy-path tests at the start is a valid strategy for TDD as well, and does not cause any of your imaginary issues.
Can you? Surely it depends; most systems end up with more LOC in the tests than the tested code, and the tests multiply the cost of refactoring.
I've had success with the "happy path first" kind of TDD, where you write a use case and implement to that and only then come back and fill in other tests.
Changing existing apis comes with a very different set of challenges
Those two checks alone can take you far, specially when compared to the alternative "I tried it once and it worked".
A majority of test suites require knowledge of code structure (and thus to write the tests).
To write the code before tests you either need to dictate some strict form of input/output, and test based on those only; OR, use a test suite that is very flexible.
BDD seems to cheat by writing "tests" that aren't fully executable until you fill in the functional specifics later.
So an automated tool may be the only path forward as far as testing goes. Whether this one adds value or not is hard to say as their documentation is extremely sparse (probably why they made the tool in the first place).
I'm no fan of TDD -- I think there's a lot of cargo culting about it -- but I wasn't aware anything had been "proven" about it. I've seen papers claiming it helps, papers claiming it sort of helps, and some people claiming it mostly doesn't help (or that what actually helps are some practices that go along with it).
I thought there was no industry consensus that it doesn't help.
Sometimes you have to honor bugs as part of the (undocumented/evolved/inferred) interface. It could help to discover these, walk through them and see where they can be fixed or where they need to be honored.
It could also help prevent the "rewrite from scratch because I don't understand it" problem. Heck, I've even done that to myself picking up a codebase I haven't touched in 3+ years, sometimes validly, sometimes notsomuch.
I'll have to see how it works next time this happens to me, because this is pure conjecture.
And I'm also wondering, whether there is a return based automatic test generation, that start from the return value, and resolve all variables used and gather all possible return values with its constraint, and feed those to z3 to generate inputs to cover. It seems like it will help with branch explosion by eliminating unused branch, and only focus on branch that is being used.
Edit: it looks like CrossHair[0] is a similar tool that uses Z3 to find counter examples for predicates.
In my experience with unit testing, the process of writing the unit tests is where the most value is derived from. It forces developers to more careful examine use cases and potential inputs for the unit under test (the latter being especially important for Python). Therefore I would never consider something like this for testing new code.
The rest of the value of unit tests mostly comes from validating the scope of a change's impact. If a unit tests which once passed fails after I make a change, it could mean my change affected some other part of the code in a way I did not expect. But in my experience it usually just means I overlooked something in the test suite - not that I broke something in the software itself. Auto-generated unit tests might be useful for validating the scope of a change's impact on old code, but I'd expect most failures to boil down to issues with the tests not getting updated correctly.
Where I imagine auto-generated unit tests being particularly useful is for ensuring functionality is not affected when working on bug fixes, security patches, optimizations, cruft busting, or simple refactoring.
That sounds ominous.
Why do they execute the generated tests though? I mean I'd expect this to go only as far as generating the tests. Also that means that at least someone needs to review the tests.
Based on the input types, it creates a hyperdimensional surface which is then searched for possible output values.
So, it runs your code, and runs it a lot. If your code deletes /, it will delete it a lot.
Is that really a thing Java developers use? I am one, I remember doing a PoC with Jtest (now Parasoft) in 2008, and it was utterly useless, never come across any such since, I'd be genuinely interested to learn more (I'll go duckduckgo now).
https://pynguin.readthedocs.io/en/latest/user/quickstart.htm...
The example is a function which reports the kind of triangle. The generated tests include one that tests that a triangle with sides 12, 12 and... er... None is "isosceles".
Nice wink to Penguin Adventure, brings back very old memories.
- Python forces the `py` affix in words: scrapy, scapy, rpyc
- Python does wordplay on standard library modules' names: pickle => dill
- Emacs and Julia append the file extension to the end of the name: restclient.el, HTTP.jl, JSON.jl
- Emacs does wordplay on the package the extension is based on: git => magit
These are the ones I was most familiar with. If the replies to your comment provide more insights, I may be able to extend that part of the blog post :)
https://www.obeythetestinggoat.com/pages/book.html#toc
If you feel that TDD is too much there is a book about pytest, tho never really focused on it.
Hope it helps,
PS: Ty Harry :)
I disagree with this premise. Testing your code is a great way to (among other benefits) think about the external interface to your software components, and so can help identify whether you have a quality abstraction or not.
(I don't advocate for TDD, even though I get that it might be a workflow that helps some people. But you don't need TDD to get the benefits of writing your own tests.)
Of course, testing is something you have to get "good at", too...