Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Ask HN: What's your favorite software testing framework and why?
53 points by lucgagan on May 21, 2023 | hide | past | favorite | 70 comments
Seeing so many different testing frameworks in every programming language. Would love to understand how this happened, when they all seemingly do the same thing. Would love to know which is your favorite and what makes it different than whatever else popular framework in your language?


Pytest is pretty nice, especially when you get it configured with a bunch of plugins for the libraries/framework you're using, and write your own stack of injectors to test your application in different ways. The very lightweight dependency injection is a joy to use once you get used to it. Add in nice output formatting from the built in asserts, and plugins like pytest-randomly that encourage good test writing, and it's really a great setup for almost all Python testing needs. I'd even suggest that it's a great setup for non-Python testing if you want automation around running binaries that you're treating as a black box.


I love pytest, and use it with Hypothesis[0] for property based testing. I also enjoy using pytest-bdd[1]

[0] - https://hypothesis.readthedocs.io/en/latest/

[1] - https://github.com/pytest-dev/pytest-bdd


If you like pytest and BDD I'd encourage you to check out my pytest library: https://github.com/hitchdev/hitchstory

With it you can write integration tests with typesafe YAML which double as user friendly documentation.

It also has inheritance (which gherkin doesnt) - e.g. a "buy golf clubs" story can inherit from "put golf clubs in shopping basket" story which can inherit from a "log in" story, etc.


Thank you. Your library seems nice.

Unfortunately, I have to say it... I hate YAML with a passion, k8s, github actions/gitlab ci, ansible, etc... When I'm doing ops jobs, I feel like I'm coding in YAML.

Btw, this hate for YAML birthed https://linkdd.github.io/tricorder/ :P


I understand the hate. I created StrictYAML, which this tool uses, because of it (see the Norway problem - that was me). Type safety is paramount and regular YAML does not have any type safety.

I also understand the hate from the perspective of tools which misuse YAML - putting loops, variables, conditionals, etc. in like ansible or CI tools do, like your tool complains about. I hate that too and haven't done it. I was somewhat inspired by ansible in the beginning but Ive come to understand that it was the wrong approach for configuration management and that a turing complete language is a better approach (i.e. like your rust tool).

I dont want to throw the baby out with the bathwater though. The syntax is a very clean way of displaying hierarchical data, which high level stories need. Nothing else matches - Gherkin is awful at that.


> I created StrictYAML, which this tool uses, because of it

I'll look more closely into it then :)

> Gherkin is awful at that.

When I use BDD, it's because I'm handed down scenarii written by non-tech people. And it's always in the Gherkin format, then I chose the right Gherkin engine for the right tech stack. That might be cucumber, or another.


>When I use BDD, it's because I'm handed down scenarii written by non-tech people. And it's always in the Gherkin format

Thats interesting. You're the first person I've heard of who actually did that rather than just talking about it.

I was under the impression that this worked ok for simple scenarios but with complex scenarios I think the syntax buckles - either you end up with very long, repetitive scenarios or vague scenarios. Or even both.


> you end up with very long, repetitive scenarios or vague scenarios. Or even both.

Almost correct :D

The BDD scenarii also act as "user stories" in a Scrum/Agile workflow. It was a nice way to re-use what the product owner requested.


PyTest is great and I love combining it with Hypothesis-Auto

https://timothycrosley.github.io/hypothesis-auto/


I really like the fixture system where fixtures can "expand" to multiple values. This is a great way to test combinations of configurations.


Exactly. I love writing tests that generate themselves. Things like expanding every value in an enum into a new test case so that new enum values are tested automatically.

This stuff isn't a silver bullet, but it can go a long way to commoditising certain kinds of code change such that the dev resource necessary for them mostly disappears. At my previous place of work we had quite a few non-engineers semi-regularly contributing actual Python code. It was mostly things like adding new enum values, or tweaking properties on classes, but we had robust testing that verified all of those sorts of changes in generic ways and automatically generated tests for new pieces, and it was basically hands-off from engineering.


It's been about a year or so now or less for me, but I really enjoyed working with Go's built-in tests. I had not experienced anything else like that before. It was truly incredible to see it as a first-class language CLI feature, but also that it worked alongside your file tree in a designated way.

Now, anything less than such functionality is second or third-rate to me. I can give older languages a pass, but anything not having this moving forward is disappointing now.


My context: Backend Java/Kotlin dev. Don't really need any fancy test framework, JUnit 5 is absolutely fine for executing the tests. But I do use a bunch of additional tools and libraries.

testcontainers so I can write proper integration tests instead of mocking dependencies away.

AssertJ for fancy assertions with great failure messages.

Mockito for the rare mocking/spy usage.

Awaitility for the rare async/timing based test.

When there is a Frontend (templated or SPA, doesn't matter), I like writing the tests with Selenide assertions and using its Selenium integration to run against a large array of browsers + variants (Chrome, FF, Mobile etc) launched via testcontainers.


We built something a bit novel that is focused on composable integration testing in Clojure. We've used this for many years and found it quite useful for keeping a large testing codebase sane.

Overview: https://eng.amperity.com/posts/2019/04/greenlight GitHub: https://github.com/amperity/greenlight


Not really a framework, but I really like how golang made testing part of the language/stdlib/tooling. Is it perfect? No. Is it pretty good? Yeah.

In terms of frameworks, I am a big fan of testify. [0] Unfortunately it doesn't seem like the testify maintainers want to incorporate generics. [1] I'm going to be releasing a library soon to address that.

I'm also going to be releasing a golang+python+typescript library for doing super cheap/fast database-backed tests. In my last job I found it incredibly useful, it essentially made it ~0 cost to write tests that exercised database-related codepaths and logic, which for most business apps is everything important.

[0] https://github.com/stretchr/testify

[1] https://github.com/stretchr/testify/issues/1147


I'm curious what benefits you would gain in the testify API by adding generics?


It's not that large of a benefit, but when performing tests comparing two objects, I sometimes pass the wrong object type in. Most of the times this is happening, I'm refactoring existing code and updating tests as I go. It would be nice to see all the errors at compile time (and therefore in my editor's problems/quickfix view) rather than having to run the full test suite to find all the tests I forgot to update.

I think it's possible to keep the implementation of testify almost (or exactly) the same, but update the type signatures of the methods to use generics, and shift-left a lot of errors to compile time. Just a nice-to-have.


Finished, take a look if you're curious --

https://github.com/peterldowns/testy


Vitest - https://vitest.dev/ - for JavaScript

I worked with Jasmine and Jest as well, but Vitest was created recently and to me it seems to have made good improvements over both.


Came here to suggest the same.. great fast running all the things you want a front end text to be

Also can run coverage reports that are usable in ci (I think it exports to clover format but whatever it is there is a Jenkins plugin that works with it)


In VSCode you can also run all the tests or any select tests with this plugin directly from VSCode!

https://marketplace.visualstudio.com/items?itemName=ZixuanCh...


I tend to use anything that offers property-testing, since tests are much shorter to write and uncover lots more hidden assumptions.

My go-to choices per language are:

- Python: Hypothesis https://hypothesis.readthedocs.io/en/latest (also compatible with PyTest)

- Scala: ScalaCheck https://scalacheck.org (also compatible with ScalaTest)

- Javascript/Typescript: JSVerify https://jsverify.github.io

- Haskell: LazySmallCheck2012 https://github.com/UoYCS-plasma/LazySmallCheck2012/blob/mast...

- When I wrote PHP (over a decade ago) there was no decent property-based test framework, so I cobbled one together https://github.com/Warbo/php-easycheck

All of the above use the same basic setup: tests can make universally-quantified statements (e.g. "for all (x: Int), foo(x) == foo(foo(x))"), then the framework checks that statement for a bunch of different inputs.

Most property-checking frameworks generate data randomly (with more or less sophistication). The Haskell ecosystem is more interesting:

- QuickCheck was one of the first property-testing frameworks, using random genrators.

- SmallCheck came later, which enumerates data instead (e.g. testing a Float might use 0, 1, -1, 2, -2, 0.5, -0.5, etc.). That's cute, but QuickCheck tends to exercise more code paths with each input.

- LazySmallCheck builds up test data on-demand, using Haskell's pervasive laziness. Tests are run with an error as input: if they pass, we're done; if they fail, we're done; if they trigger the error, they're run again with slightly more-defined inputs. For example, if the input is supposed to be a list, we try again with the two forms of list: empty and "cons" (the arguments to cons are both errors, to begin with). This exercises even more code paths for each input.

- LazySmallCheck2012 is a more versatile "update" to LazySmallCheck; in particular, it's able to generate functions.


It's a bit context dependent. Generally prefer tools that allow running multiple tests in parallel because I hate waiting. Unfortunately I'm a few years out of date on JavaScript testing frameworks so I'll abstain from naming specific tools. If it's easy to switch between test frameworks and there's no big differentiating features I would try em all out and use whichever runs fastest.

I think Test Anything Protocol [0] [1] is neat. Anything that takes inspiration from TAP is probably gonna be mostly structurally similar anyway.

It's easier to point out things I dislike in a testing framework. In general, anything that gets too fancy or magical, or assertion frameworks with lots of object chaining to read like natural language.

[0] https://en.wikipedia.org/wiki/Test_Anything_Protocol

[1] https://testanything.org/


You might want to give ava a spin:

https://github.com/avajs/ava/

It has a TAP reporter, but more importantly, as opposed to the more popular solutions, like Jest, the way it achieves parallelism is explained in the docs and won't change anytime soon, thus preventing wonky, hard to debug errors which occur when this part is abstracted away.


The problem with ava (which is great! But it does have problems) is that its concurrency story is deeply tightly coupled to internals you can’t get at, and it misreports timings pretty significantly as a result. It might report single digit millisecond test runs but take about a second to run a full suite anyway, because its concurrency solution has a ton of (unreported) overhead.


I'll take that any day over Jest's "we won't tell you how we do parallelism because no" and errors which don't reproduce with --runInBand that plagued just about every project I've been to using this framework.

Perhaps it's an Angular thing - I don't understand why people keep pushing this combination.


I like the idea of TAP, but one annoyance I found is that most tooling expects the total number of tests to be output up-front, so we can't just "run everything", e.g. looping through all shell scripts in a test/ folder, or just writing a dumb, top-to-bottom script that outputs pass/fail messages.

Even though it's small, the extra structure and effort needed to count tests up-front is basically the same as switching to a "proper" test framework (most of which don't output TAP :( )


I'm a fan of RSpec and any BDD (Behavioral Driven Development) type of framework. You are correct that they all do the same thing, but for me everything boils down to which one provides the quickest understanding of the code by the reader.

There's another testing library in Ruby that is called minitest. The syntax is different, for example you would write "assert_equal num1, numb2". My problem with this is that you don't know which is the expected vs the actual values. Is it the first or second argument? On the otherhand, my framework of choice, RSpec would write this as "expect(num1).to eq(numb2)". The syntactic sugar alone makes this easier to understand.


After years of development, RSpec is what made me stop testing the code manually. Rspec is one of those things that made me a better developer.


Definitely Jest, I've tried few in the past and one of the great things of Jest is how easy it is to extend and customize if you are a library author with meaningful messages.

So I wrote a small test runner for React (`react-test`) and added few custom Jest matchers, so you can do:

    const button = $(<Button>Hello World</Button>);
    expect(button).toHaveClass('btn');
    expect(button).toHaveText('Hello World');


My favorite was the SUnitToo package for VisualWorks Smalltalk. It was integrated right into the IDE. The code browser was a natural fit for really intuitive feedback for running tests and getting feedback.

The Smalltalk language and SUnit library had a really great signal ratio without a lot of boilerplate that goes with a lot of other xTest frameworks.

Shameless plug — I was the author of said tool.


I generally like rspec because:

* The setup pattern with "just in time" variables is amazing.

* It offers some _extremely_ terse tests

* It offers a huge library of plugins.


The API and output on failure from Perl's Test2-Suite (https://metacpan.org/dist/Test2-Suite) is amazing. I miss it in every other language I work in, especially Go and Rust, both of which have fairly anemic tooling for this stuff compared to Perl.

I've tried making something similar in Go (https://pkg.go.dev/github.com/houseabsolute/detest/pkg/detes...) but the language makes having as nice an API impossible, and I really don't love what I've come up with there. The output from detest is pretty nice though (until it wraps lines in which case it's a mess).


Haskell’s Tasty framework.

It lets me combine HSpec for unit tests and Hedgehog for property tests in the same test files with test auto-discover. For failed properties, it prints the seed.

And it has really pretty, readable output.

I’m using Rust’s tests currently, and the output is just a lot less obvious. E.g. green text when 0 tests are run.


> Seeing so many different testing frameworks in every programming language. Would love to understand how this happened,

Asking everyone for their favorites is not likely to meet that goal.

> when they all seemingly do the same thing

You haven't actually looked into this at all before asking, have you?


I write mainly HTTP APIs and I almost exclusively test them with a lesser known tool: Venom[1]

(For the background, I used to work at OVHcloud and Venom was developed by the core/platform tool. I'm usually not a big fan of in-house tooling when I can avoid it, but I found Venom's paradigm so good that I still use it to this day and can't imagine not using it to test my APIs now)

It's an integration testing tool in which test suites are written declaratively in YAML. It's completely language agnostic, and you can be 100% sure you're actually testing behaviors and contracts. This was once very useful to us as we migrated an old Python API to Go, with the same interface contract. We just kept the same test suites, with pretty much no changes.

A very basic HTTP API test would look like this:

    - type: http
      method: GET
      url: http://localhost:8080/ping
      assertions:
        - result.statuscode ShouldEqual 200
        - result.bodyjson.status ShouldEqual ok
But where it shines in my opinion is that you can not only make HTTP calls, but also database calls. So when you implement and test a DELETE endpoint, you can also make a query to check you didn't delete ALL the database:

    - type: sql
      driver: postgres
      dsn: xxx
      commands:
        - SELECT * FROM table
      assertions:
        - result.queries.queries0.rows ShouldHaveLength 8
You can also load fixtures in database directly, work with Kafka or AMQP queues both as a producer (e.g. write an event to a Kafka queue, wait a few seconds and see that it was consumed by the service you test, and that some side effects can be observed) or as a consumer (e.g. make sure after an HTTP call, an event was correctly pushed to a queue), or even read a mailbox in IMAP to check that your service correctly send an email.

It's a bit rough on the edges sometimes, but I'd never go back on writing integration tests directly in my programming language. Declarative is the way to go.

[1]: https://github.com/ovh/venom


Sounds like we had the same idea: https://github.com/hitchdev/hitchstory

Like you I also dont want to go back. Declarative tests are the future.


I like Rust unit tests, simply because you don't need to set anything up to use them. Also they can go in the module you're testing, so writing tests for things that are not public is simple.


I do like how Rust’s test system ships with Cargo and how tests are local to a private submodule.

I wish the test report output was more comprehensible, and that there was a property test framework that felt more like in Haskell, e.g printing the PRNG seed when a test fails and letting you run with that seed again; it seems like proptest is the best there is, and it’s doing its own thing, kind of like how property tests work in OCaml.


Yeah, it would be nice if you could easily hook into the test runner/reporting stuff.


+1, being able to write tests that access private fields and functions means you never need to expose behavior or data purely for testability


I’ve found the datadriven[1] testing approach in go to be quite effective. The idea is that you leverage a standardized file structure to construct a little DSL for testing your code. This allows you to write expressive tests that print the state of the code and then look at it. Rewrite is also very powerful.

This is all inspired by the sqllite logic test framework.

[1]: https://github.com/cockroachdb/datadriven


How do I actually try this out / use it? Are there docs or examples anywhere?



In my opinion it is Spock for Java/Groovy [1]. The amount of functionality and readability you can squeeze from Groovy's DSLesque is absurd. Is basically a full fledged new test language with Java sprinkled as the test contents code

[1]: https://spockframework.org/


For Scala, Specs 2. Just overall well designed, useful, yet flexible API.

I'm looking for something for Node.js. I've used jest, but it's very heavy and does all sorts of magic and is a whole build system. Whereas I just want a test runner with utilities and reporting.


You should check out uvu


A good recommendation on the opposite end of the magic spectrum. It’s also very fast, because it’s so minimal. If you find yourself needing more, ava is a good compromise but it definitely trends into magic territory more rapidly than I’d prefer.


I'm liking catch2 for cpp tests these days.

It is easy to have an idea for a testing framework that you can quickly write yourself. That is why there are so many. It is tedious to add all the features people want so many are not quite as good as their hype.


I like Catch2. It's a nice, clean syntax and also supports benchmarks in addition to tests. It also seems to work nicely with Visual Studio's built-in test tools and UI, even in CMake projects.


pytest. The way it handles both fixtures and parameterization (and the fact that it doesn't force classes on me) genuinely results in me enjoying writing tests.

I'd love to find a JavaScript testing framework that's as pleasant to work with.

I wrote a bit about those two features here: https://simonwillison.net/2018/Jul/28/documentation-unit-tes... - about half way down.


> ...Would love to understand how this happened...

Well, why are we using cut-down Chrome with a bunch of js scripts and pretend it's a native app or a web server?


I’ve worked on countless codebases with thousands of unit tests, integration tests, synthetic tests, automated acceptance tests, automated UI tests, etc.

The only real “test” is whether it works in production. Everything else is a poor substitute and gives you false confidence. It’s absurd the amount of time and effort we spend on writing tests and we still have as many, if not more bugs in our software than we did before writing tests became a religion.


> The only real “test” is whether it works in production. Everything else is a poor substitute and gives you false confidence.

Tests are like bloom filters[1], either they fail and you have a bug (either in code or in the test itself), or they pass and you possibly still have bugs.

As such they only give you false confidence if you're overly naive.

If our tests pass then we know what we know. Doesn't mean we pretend there can't be unknown unknowns, so we still need diligence before rolling out a new version into production.

[1]: https://en.wikipedia.org/wiki/Bloom_filter


If tests are naming you less rather than more productive, you should rethink how you use tests.

(I've seen MANY examples of unproductive tests, they're definitely a very real problem - but it doesn't have to be that way.)


Racket's (module+ test ...) by far.


ospec is king in JS land. Ultra small, does what I need, gets out of the way and doesn't come with any corny grammar.

https://github.com/MithrilJS/ospec


For end to end and frontend tests, Playwright.


What's your biggest pain point when using Playwright?

We've been trying to adopt Playwright but keep having issues with flaky tests.


Yeah flaky tests are a pain, not sure if cypress is any better but other things are so much worse. To deal with flakiness I wait for page loads or network responses or locators before executing actions that might depend on these things.

I think my biggest pain point is just the slow page loads and servers at my company. Also depending on third party services and integrating with them makes it impossible to have a reproducible environment, I can't self-host our salesforce integration in testing environment.


my favorite is the one that is most ergonomic for the tools i'm building with


Outside of differences between assertion-based unit tests and property-based tests (both of which are worth doing), I don't think framework makes much difference. But your approach to testing definitely does.

I think every language having its own testing framework is good, even for things like functional tests which can often be externalised. Tests are an essential part of every project and should be well integrated with the rest of the codebase and the team creating it. Often, the tests are the only good place to go and see what an app actually _does_ and so they form an essential part of the documentation.

In my experience it's very rare that you can effectively create and maintain something like Cucumber tests owned by anyone but the team implementing the code so there's little benefit to translating from a text DSL like that. But the language used is definitely useful, so what I like to see is code in the implementation language that matches the Given/When/Then structure of those tests, but instead of reusable text steps you just have reusable functions which take parameters. This means you can easily refactor, and use the full functionality of your IDE to suggest and go to definitions etc. No matter what, you should treat your test code the same way you do everything else - abstractions matter, so functional tests at the top level should rarely just be about clicking on things and asserting other things, they should be in the language of the domain.

Functional tests are worth much more than unit tests. No only do they test the only things of actual business value, they are also more robust in the face of implementation refactorings and so require less rework (unless you're being overly specific with CSS selectors etc). Unit tests are often highly coupled to specific implementations and can be a poor investment, especially early in a project. I believe a good balance is functional and integration tests that explore the various paths through your app and prove everything's hooked up, coupled with property based unit tests for gnarly or repetitive logic that isn't worth endlessly iterating via the UI. All other unit tests are optional and at the discretion of the implementer.

You should be able to mock out every major articulation point in your code, but it's generally preferable if you can mock _real_ dependencies. That is, instead of mocking out a 'repository' abstraction that looks stuff up and returns canned data, have a real test database against which you look up real data (created by steps in your functional tests). This reduces risk and cognitive overhead (you're not having to encode too many assumptions in your test suite) and doesn't have to be as slow as people like to make out - Embedded Postgres is quite fast, for example:

https://github.com/zonkyio/embedded-postgres

Same with network services - it's not slow to chat to localhost and you'll find more issues testing proper round-trips. I have not found "assert that you called X" style testing with mocks useful - you care about outcomes, not implementation details.

Beyond all that, as long as you can make assertions that generate clear error messages, you're fine.


This was insightful. You sound like you know a ton about QA. Would you be open to join a rolling interview? I am curating a list of QA experts and asking them weekly a single question, and then compiling everyone's answers into a blog post.


Xunit.


i prefer not writing tests and just saying sorry if i ship a bug


We had a guy do that. Cost the company $300k (penalty) and they had to eat the cost of the rework (another $50k or so in labor, maybe more). He's not very popular right now as a result.


Your company doesn’t have a QA team?


It does. But the person responsible for evaluating it was on their way out (had gotten a new job, hadn't put in their notice yet though) and just said, "All good!" to a bunch of things (previously trustworthy and good work, but their work that last month or two was shit).

So to some extent the dev got unlucky, his terrible code (that he didn't test) may have been caught through the normal QA process which the QA willfully lied about doing. But that doesn't absolve him for not testing (even basic testing) his own work.


Do you imagine QA catches everything?


Darn that has to be exhausting




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: