9 ms·
"""Pyret makes testing a natural part of the programming process. Functions can end in a where: clause that holds unit tests for the function. These assertions
by terhechte 13y ago
"""Pyret makes testing a natural part of the programming process. Functions can end in a where: clause that holds unit tests for the function. These assertions are checked dynamically."""
Fantastic idea! I'll keep that in mind, should be fairly easy to extend Lisps or other AST-Macro enabled languages (Elixir, Julia, Python) with such a functionality. I really like that. It makes it easy to work on a function and code tests down without switching contexts (files, workspaces, etc)
- deleted 13y ago[deleted]
- jacques_chester 13y agoD has something like this[1]. It might have come from a still-earlier language. [1] http://dlang.org/unittest.html http://dlang.org/unittest.html
- sparkie 13y agoMy biggest gripe with these is the inability to name the tests. What exactly is a unittest block testing for? You need to rely on comments or read the test code where none exists. Doesn't seem like Pyret fixed that issue.
- skrishnamurthi 13y agoEverything is fixable in a new language (-:. Say more!
- sitkack 13y agoCould the tests be more declarative, defining properties over a space?
- skrishnamurthi 13y agoAbsolutely! Pyret's test section has a "satisfies" keyword for property tests. E.g.: check: insert(a-value, a-balanced-tree) satisfies is-balanced end or even check: tree-examples = [ ... ] values = [ ... ] for each2(t in tree-examples, v in values): insert(t, v) satisfies is-rbtree end end
- akkartik 13y agoOne of my favorite things about tests is that they let me read a new codebase more interactively. If I don't understand why a function is built like this and not that I can rewrite it the new way, run the tests. Without names I'd know I broke something, but with a good name I'll often realize why that something matters in the big picture. (I think about tests a lot: http://akkartik.name/post/tracing-tests http://akkartik.name/post/tracing-tests)
- skrishnamurthi 13y agoI understand your point. One thing I pushed for initially, dropped because we had bigger fish to fry, and will revisit later, is the idea that you should be able to name everything: not just tests but an individual variant in your data definition, lines of code, etc. And this should be intrinsic to the language. That said, I couldn't see what your post had to do with this concept; if I've missed something, please explain. (As an aside, I believe this -- http://cs.brown.edu/~sk/Publications/Papers/Published/mcskr-dataflow-lang-script-debug-journal/ http://cs.brown.edu/~sk/Publications/Papers/Published/mcskr-... -- generalizes the idea in your tracing post.)
- akkartik 13y agoThanks. My post wasn't about names, I was just looking for feedback. Which I got; thanks.
- alexatkeplar 13y agoI would like it if the `where` tests supported DataTables for a slightly more declarative style. Here's an example from Scala/Specs2: https://github.com/snowplow/snowplow/blob/master/3-enrich/hadoop-etl/src/test/scala/utils/conversionUtilsTests.scala#L122 https://github.com/snowplow/snowplow/blob/master/3-enrich/ha...
- skrishnamurthi 13y agoThe fact that they don't now doesn't mean they can't in future! One of the advantages of having a separate block for tests is we can do things in that block that won't necessarily pervade the whole language where it might not make sense (which we already do: eg, the keywords "is" and "satisfies"). We've all done some sort of "here's a list of tuples, now map the prefix of this as arguments and the suffix as the expected value over this function", so it is something on our mind. But I've struggled a bit to find a good way of writing it. We've written a few things where it looked truly "naked" to have these tuples without the surrounding function call. So it's as much a matter of syntax design as anything else. I must say that the code on that page does not look like something I want to copy; it looks like a good compromise, but with emphasis on both words. We don't need to compromise.
- alexatkeplar 13y agoIf you can up with a cleaner syntax for test data tables that would be awesome, thanks Shriram.
- amenod 13y agoThat's an advantage in my book... it is testing the unit (function in this case) and it is testing all the corner cases... do you have a name for each of them? Description through comments is much more suitable IMHO.
- lukeschlather 13y agoI really hate some things about the Rspec/Cucumber suite, but providing a way to output what the tests are testing as they run is a major advantage. I like being able to run my test suite and see "it does what it's supposed to do in this case" output. At a glance, the Pyret default appears to be that tests run silently. How do I know the right tests were run?
- mercurial 13y agoCorner cases? Tests test the behaviour of the function. Functions, depending on their complexity, have a range of behaviour. And yes, you should (usually) be able to come up with a name for it. As a bonus, you'll get immediately an idea of what broke upon test failure.
- ash 13y agoMaking tests part of language syntax is an interesting idea. Of course it's not equivalent, but Python doctest module at least allows you to keep tests close to the code. Here's Pyret example, ported to doctest: def sum(l): """ >>> sum([]) 0 >>> sum([1, 2, 3]) 6 """ return reduce(lambda x, y: x+y, l, 0) if __name__ == "__main__": import doctest; doctest.testmod()
- laureny 13y ago> Making tests part of language syntax is an interesting idea. It's been tried many times before and it has always failed. A few reasons: - It clutters the code - If you need simple tests, asserts suffice - If you need more sophisticated tests, write functional tests, separately The approach offered by Pyret (along with similar ones, such as design by contract) are compromises that are the worst of both worlds.
- skrishnamurthi 13y agoIt's actually widely used in Racket already. Yesterday a lead programmer at a major financial institution told me that he liked that style so much, he'd incorporated it into his company's OCaml system, and they use it in their production systems (lots of high-volume trading). Hardly "failure"s. Nevertheless, perhaps you could provide some pointers to the "many" projects that have tried it before? Assertions are not tests. This is a fundamental misunderstanding of the difference. Pyret's `check` blocks exist to write complex tests, separate tests from definitions, or write tests that cross multiple functional units. So they offer the power of regular testing frameworks. Thus, `where` is the bonus, not a "compromise".
- pjmlp 13y agoContracts?
- skrishnamurthi 13y agoEvery time you write a refinement on a type, you're actually writing a contract. You can gracefully move between types and contracts this way, and also between thinking of them as checked assertions vs tested ones. The goal is to make this migration more and more seamless.
- pjmlp 13y agoYes, I although I have used a multitude of dynamic languages on my career I tend to lean on the static type languages with type inference side.
- GhotiFish 13y agoThat really caught my eye! Does anyone have any experience with workflows like this? Are tests next to code a pragmatic technique?
- qiemem 13y agoPython has them in the form of doctests. They are generally frowned upon as they tend to significantly clutter up code and documentation when they actually provide comprehensive testing. That said, Pyret's format may escape that fate as they are given their own section which most modern editors should be capable of folding. Doctests: http://docs.python.org/2/library/doctest.html http://docs.python.org/2/library/doctest.html
- swift 13y agoHaskell also has a library for doctests, though I don't see them used that frequently: https://github.com/sol/doctest-haskell#readme https://github.com/sol/doctest-haskell#readme
- keppy 13y agoI use this in clojure where I am interacting with the code from the repl. run-tests will execute tests for function defns wrapped with the with-test macro. This is fast and no external file or namespace needs to load just type run-tests; http://richhickey.github.io/clojure/clojure.test-api.html http://richhickey.github.io/clojure/clojure.test-api.html
- 6ren 13y agoIt clutters the code. Tests are a form of documentation, but too much in the code, like too many comments, obscures. Higher level unit tests (acceptance tests) can be quite long, especially if there's a lot of setup - unlike their example code. Literate programming tried embedded documentation, but didn't catch on (even with Knuth's backing). Embedding tests makes them easier to keep in sync, but tests are already kept in sync by (hopefully) failing when not. However, their idea of automatically running tests is interesting, so there's no infrastructure to set-up, run etc (though you'd want to be able to disable them). Nice for teaching.
- ash 13y ago> Literate programming… didn't catch on I would argue literate programming is experiencing a renaissance, thanks to docco: https://github.com/jashkenas/docco https://github.com/jashkenas/docco
- pjmlp 13y agoNot really, I never heard of docco and am yet to see a RFP for a node.js project from my employer's enterprise customers.
- vidarh 13y agoChances are much higher that you've seen the output of Docco or one of the multitude of imitations for other languages.
- e12e 13y agoI think literate coffeescript might be a better example of popularizing literate programming: http://ashkenas.com/literate-coffeescript/ http://ashkenas.com/literate-coffeescript/ There also seems to be some traction for literate Haskell: http://www.haskell.org/haskellwiki/Literate_programming http://www.haskell.org/haskellwiki/Literate_programming I experimented a bit with literate programming on an introductory course in programming using java. It's an interesting experience -- it's very easy to produce a very readable language that is still pretty poor java code, as it becomes so easy to split out fragments and "procedures", rather than follow the more common java class-oriented object orientated way of doing things.
- wuschel 13y agoInteresting. Could you propose such an extension by posting a code sample e.g. in scheme?
- ek 13y agoSure. These are basically simple contracts. Contracts are implemented in Racket: http://docs.racket-lang.org/reference/contracts.html http://docs.racket-lang.org/reference/contracts.html
- ufo 13y agoRacket has a very powerful contract system that you might want to out. It also has support for higher-order contracts on functions (contracts that have delayed checks instead of being just assertions) and they also have a typed version of Racket (Typed Racket) built using the contract system.
- skrishnamurthi 13y agoSome of the Pyret designers are part of the Racket community, and know these ideas well. Where Pyret differs is that Racket has two different languages (for the purposes of this discussion), Racket and Typed Racket, and a program has to live in one or the other. Whereas Pyret takes a different "gradual" philosophy to type annotations, so a program doesn't have to move between the two languages.
- ufo 13y agoI was just trying to point out that Pyret is not inventing the contract-checking and that Racket, the language Pyret owes a lot to, already has the contract system terhechte was looking for. Maybe I should have been more clear. That said, I'm going to take the opportunity to ask a question: does Pyret's gradual typing cover parametric polymorphism and mutable data structures? Those usually are specially though to do in these fully gradual systems so I'm curious what you guys have done about that.
- skrishnamurthi 13y agoSorry, lost track of what you were responding to. Re. your other question, yes, this is a wide-open problem, and we're trying to not get bogged down in research issues like this. We actually have one answer about what to do, presented in a paper we wrote some time ago (http://cs.brown.edu/~sk/Publications/Papers/Published/gmfk-rel-par-poly-cont/ http://cs.brown.edu/~sk/Publications/Papers/Published/gmfk-r...), but we don't really want to do that. But because we're working on a (curious) type inference story, we may be able to carve up this problem in a very different way. Sorry for the non-answer. You're right, and we're working on it.
- eliben 13y agoI'm not sure I like having tests in the same file with the code. In real code bases it's very common to just read the code, and cluttering that with huge swaths of unit-tests is counter-productive. That said, Pyret appears to be aimed at education so this may make more sense.
- skrishnamurthi 13y agoThen you can put your tests in a `check` block and move them elsewhere. You don't have to use `where` except when it makes sense.
- reeses 13y agoThis is bouncing against the perpetual "code as text" problem. Put the tests and docs in with the code and you end up going into "scanning" mode. The actionable information density per character is lower. Put the tests and docs outside the code and you (or that obnoxious guy who just changed > to >=) don't update the tests or the docs. In the worst case (cough functional web tests) people tend to disable the tests because it's too much work to get them to conform to the changes and everyone is on fire and babies are dying and this needs to be done in production now.
- vidarh 13y agoSounds like Eiffel: http://en.wikipedia.org/wiki/Eiffel_(programming_language)#Design_by_Contract http://en.wikipedia.org/wiki/Eiffel_(programming_language)#D...
- skrishnamurthi 13y agoNot even remotely in just about any respect.
- vidarh 13y agoReally? The concept of a block that specifies a contract does not sound similar to the design by contract capabilities of Eiffel. Care to enlighten us why not? Perhaps you think I was referring to something other than the quote in the comment I replied to.
- ufo 13y agoEiffel contracts are relatively primitive compared to the more modern Racket and Pyret contracts. Eiffel contracts are basically limited to assertions and don't support contracts on higher order function arguments, refinements, etc. There is also the whole dynamic typing thing. Eiffel is still fully statically typed.
- vidarh 13y agoNone of which changes what I wrote earlier. In terms of my opinion of Racket/Pyret, though, there's been a number of Ruby projects to do similar stuff to this over the years, since it's trivial to add (just add a class method that redefines a method and wraps it in code to execute the contracts), and a lot of Ruby newbies try to find ways of adding back the restrictions they've lost when moving from statically typed languages. Invariably they've ended up dying or not getting much interest, largely because a large part of the appeal of dynamic languages for a lot of people is that the code reads simple. In the Ruby world at least, aesthetics is a big deal. And putting the tests inline destroys that aesthetic and makes it harder to read the code. Especially because tests tends to be trite and repetitive and contain a lot of stuff that is obvious to a human when you have the code right in front of you. Especially when they go beyond documenting contracts that are intended to bind subclasses to an interface. That code readability is important. I have a prototype for my own Eiffel inspired language with DbC support, and my own experience with it was that as much as I love the idea, even Eiffel level contracts are bordering on being too verbose, for these reasons. Pull them out into separate files, and they're a lot more acceptable, but then what is the difference from any other unit testing framework?
- rmc 13y agoAST-Macro enabled languages (... Python) What?! You can't write macros in Python? (right? have I missed something?)
- anonymous 13y agoIndeed you can https://github.com/lihaoyi/macropy https://github.com/lihaoyi/macropy
- mck- 13y agoI was thinking exactly the same thing after I read that :)
- keppy 13y agoin clojure: (with-test (defn my-function [x y] (+ x y)) (is (= 4 (my-function 2 2))) (is (= 7 (my-function 3 4))))