4
kiki
275d

Two real reasons people write unit tests:
- mommy’s boy can’t even fart without mommy’s approval, but instead of mommy, there are unit tests now
- Stockholm syndrome

Comments
  • 8
    Not sure what you mean? Unit tests are good to have around when you're refactoring or changing code. In case of TDD even before you have written the code, like a set of specifications you know have to hold up once the code is done.
  • 10
    @kiki I was writing a little expression parser recently, my own project, not work related. And before the main code was done I wrote unit tests for all sorts of edge cases and formats it needed to pass.

    It helped a lot, mostly because I thought of edge cases early and knew how yo approach the final architecture better.

    It's not valueless, maybe you used it wrong at your job, I worked at a place that used unit tests to essentially tested mocks and nothing else... But when done right It's like automation for manual testing through a debugger...
  • 1
    @Hazarth https://devrant.com/rants/4607192/... comments. I'm not gonna explain it all again
  • 12
    @kiki ah ok. Neat. Your logic is incredibly flawed.

    Your entire point is "You don't need tests if you, and everyone else involved, is perfect"

    Which is true, I agree and im glad you work in a perfect environment and that you yourself are perfect. Must be nice
  • 1
    So... what testing do you do? Are you advocating scrapping unit testing but keeping integration testing / UAT etc.? Or is all such testing bad?
  • 2
    @AlmondSauce testing? Just build it in prod 🤷‍♂️

    Obviously that's the only way forward now.
  • 1
    @C0D4 Yeah...

    I'm curious as to the logic more than anything else. There are occasions when unit testing is basically impossible - working on a large, monolithic codebase for instance that has everything crammed into a few classes and methods. You can't really test anything as there's no real units to test. In that case large scale, thorough integration tests are kinda the best you can do without heavy, often impractical refactoring - but I don't think that's the point that's being made here somehow.

    In all other cases, the only people I've met who think unit tests are a waste of time are people who've never written them properly, or at all.
  • 0
    So, for a lot of stuff I think unit tests are impossible and a waste of time. EG in the case of UI tests, they're probably regression tests under the guise of "unit" tests. But as they don't determine the correctness, they're not.

    Unit tests to measure approximations of trapdoor functions are, however, good.
  • 0
    @Hazarth have you ever worked with a Turing-complete declarative language? Even if it was prolog.
  • 3
    @kiki your other rant is interesting reading. There's the old addage that code is one of:
    * obviously not wrong
    * not obviously wrong
    But bugs aren't always about execution. The above solves the execution problems.

    When you're trying to solve a problem, for example to approximate missing information (a trapdoor problem), the above doesn't help with correctness, and no matter how complete your DSL is, it's easy to describe the problem in one direction, hard to describe it in the other. That's the definition of a trapdoor function. There are simple cases where a solution should always be correct, and yes a DSL could describe those, but this behaviour should be emergent of the combination of a set of rules. These tests don't validate how well the solution generalises, but because its easy to generate input from known results and hard to describe each specific input, there can be a reasonable level of confidence that it might.

    I might be missing something. I'll go think/read.
  • 0
    @atheist the first good answer, hallelujah!

    I can almost smell taming legacy code from what you've written.

    https://devrant.com/rants/4752097/...
  • 2
    So, first of all, languages I've worked with:

    C
    C++
    Objective C
    C#
    Java
    Python
    R
    TypeScript/JavaScript
    Fortran
    Assembly
    Lisp
    Prolog
    OpenGL
    Ocaml

    Few others too. Language is irrelevant, they're just constructs, ways to describe the same thing. Switching to functional isn't some magic bullet. If you can write mistakes in one language, you can do it in all of them. There are advantages, eg Jane Street using ocaml because the structure of the code matches what the economists write.

    You've got a bit of an ego problem, telling people to pick up a new language. My ability to write code is one of my least valuable skills. And I'm *good* at that.

    Re: legacy code, there's no such thing. It's either maintained or frozen. I had a previous company freeze a 5k line code base because it was a prototype people used because they lacked tools in the replacement code base. I built the tools, taught people how to use them then we stopped maintaining the old code. We had to keep it as it was proof of origin, we'd used the VCS log in a court case.

    With regard to tests. Even in a sufficiently descriptive language, you declare a set of rules and test cases define how they should interact in certain circumstances. You're arguing at a certain level the tests are the code, but they're not.

    The rules are complex and interact and create emergent behaviour. It is simple to say what this behaviour should be in specific circumstances, hard to describe the behaviour in general.

    For example, I worked on a motion tracking projet. One of the tests was to move a picture 1 pixel, and verify that this is "one of" the measured motions. You wouldn't write code to check for one pixel motion, it has to evaluate every possible motion and select the most likely. The latter is a set of complex rules, the behaviour that emerges is that you can usually measure this 1 pixel motion. Even in a declarative dsl, the tests should be evaluating implicit attributes, not explicit.
  • 0
    There's an argument that in the right dsl the test & the code are equivalent, and OK maybe. But then your product is the dsl, not the code. That needs tests.

    For tests to be the code in this case, the tests would have to be exhaustive. For a 32x32 image with 256 luma values, that's 262k possible states. That's relatively small compared to what we actually used. It's not possible to write exhaustive tests, nor meaningful.

    Compare this to the likes of an infrastructure test, where code "creates an ec2 instance", then a test validates "that an ec2 instance was created". Fuck that for a laugh. That's just testing getters and setters.
  • 0
    Never mind, my maths was hokey. I had done 32x32x256. It's actually 256^(32x32) which has like a couple of thousand zeroes. That's the number of unique states.
  • 0
    The "if an image has moved one pixel, the system should measure one pixel" is a tautology, and can't be implemented as it relies on information that isn't present in practice.
  • 0
    @atheist “if you use dsl, your product is the dsl, not the code” argument is flawed. Everything maybe except for assembly is a dsl of some sorts. If I use a headless CMS solution with one of its domain-specific entities being the user, according to your argument, I’m better off testing the CMS rather than the code I write that uses its entities. Sounds wrong to me.

    Our language lists are uncanny similar. The only difference for me is Swift instead of Objective C and Clojure instead of OCaml.

    Argument about “just constructs” is your basic “X is just Y” argument that is inherently flawed. I also never mentioned functional programming because it’s still imperative in its core. The way you’re presenting different languages like a matter of personality preferences is misleading. Languages aren’t created equal, and it’s much easier to make a mistake using language that just doesn’t fit the task.
  • 0
    If you can do the same thing in different languages, they're conceptually equivalent at a certain abstraction.

    https://youtu.be/cgVVZMfLjEI
  • 0
    @kiki My point is about abstraction. If you abstract to the point where you can say:

    "For an input image, measure the motion in it", that's an abstraction. To test that, you'd need to write something like "was the motion measured?". But that doesn't tell you the result is correct, only that your use of the abstraction is correct. If you've also written the abstraction, you're also likely to have added bugs to that. Regardless of the paradigm, that's possible.
  • 0
    Also, if you think assembly isn't an abstraction or dsl, let me introduce you to my little friend: VHDL.
  • 0
    @atheist oh I heard about it but forgot what the name was. Thanks
  • 0
    @atheist it’s not an abstraction if you can’t go lower
  • 0
    There's an inception joke here.
  • 0
    @kiki (VHDL is lower than assembly)
  • 1
    One last point on the matter. You've argued that one language that doesn't match how we think is better because you're less likely to write bugs in it.

    That might be the case.

    But not for the reasons that you think.

    There's a thing called cognitive ease. When we find something easy, we're prone to thinking less. There are hard to read fonts that have been shown to help memory recall.

    If you're interested, the book "thinking fast and slow" is a good start.
  • 0
    @atheist thanks, but I don't quite understand what you're trying to say. Declarative good? Declarative bad?

    Declarative is different. One thing I know is that it's easier to do this

    coffee { temperature: 70C }

    than this:

    if (coffee.temperature >= 70) return

    coffeeHeater.start()

    coffeeTermometer.on('change', function measure (e) {

    if (e.temperature < 70) return

    coffeeHeater.stop()

    coffeeTermometer.off('change', measure)

    })
  • 0
    @kiki You are correct. However those are not equivalences. The first is plausible in several languages and paradigms. You've not defined the rules and relations that define the evolution of the system, you've defined one fact. The state at a single time point, and compared that to a partial implementation of a PID feedback loop.
  • 0
    Also, the second is also equivalent to

    heater.is_on = coffee.temperature < 70

    And yes, that's somewhat declarative, but it's imperative too. The bit that differentiates is that the imperative needs an event loop and some other fluff.

    But then having skimmed a prolog tutorial, I also know that the declarative implementation needs relations defining that relate the state of the heater to the state of the current temperature vs your declared "target" temperature.
  • 0
    @atheist
    heater is undefined
    heater has no member called is_on
    coffee is undefined
    coffee has no member called temperature
    is_on is not assignable to boolean

    > coffee.temperature
    true
  • 1
    Disagree.

    In fact, I've had scenarios where a change triggered by one requirement broke something else, caught by unit tests; and once there was an 8 hour outage, thanks to me not running unit test, someone had skipped them in the build scripts, and I didn't test one particular scenario. Funnily, unit tests were actually failing, and it would have been caught had the tests been run.
  • 0
    @BugsBuggy e.g. "bad and cohesive imperative code was saved by unit tests"

    Maybe because that's the whole point?

    My point though is writing declarative code that doesn't fucking crash every 5 minutes if left alone in production
  • 1
    @kiki you didn't get my point

    Okay, so there's a requirement, and we build a microservice for it.
    There's another requirement that is quite similar to the previous one, with 90% similar codebase, hence we decided to keep it as a common microservice.

    Now, a change in the previous one, broke a functionality in the newer one. The code doesn't need to be bad for something like this to happen. A simple careless developer error, a human mistake, can happen.
  • 0
    @BugsBuggy that's why DRY as-is sucks at scale. Spoiler alert: the whole concept of aspect-oriented programming was invented to tackle this fundamental problem in imperative code. Google it.
  • 0
    @kiki your statement is so complicated man
  • 0
    @kiki okay, just curious.
    How do you want to make sure that a change in a feature will not break some other feature?
    (one application might serve multiple features even as microservices)
  • 0
    OK.

    EAX = EBX < 70

    (obvs not valid assembly, but whatever)
  • 0
    @BugsBuggy you want to turn your codebase from a single-dimensional thing to a double-dimensional one, literally. It may sound complex but was famously solved in Lisp with a bunch of macros and that costed Java a lot of mockery back then. Just read the wikipedia article bro

    https://en.wikipedia.org/wiki/...
  • 1
    Although I still think UTs should be there, specially for large features, but this whole AOP thing looks interesting.
  • 1
    Apparently, I've done some of it, like metrics and all...
    Yet to use it with something regarding business logic.
  • 1
    @BugsBuggy man, not even learning new paradigms but just reading about them is what I consider one of the most valuable time investments when it comes to professional growth
  • 0
    @kiki I just shared a blog explaining AOP to my team's channel.
  • 3
    Third reason:
    I don’t trust the idiots I work with to not break everything.
  • 0
    @Root in Code Complete, Steve Mcconnell strives to define a medium where errors are impossible. Np matter what you do, you can't produce an error — language or toolchain just doesn't allow it.

    If adopting a declarative language as-is is not possible, I think state machines would be the optimal approach instead of tests, where your linter would just compute every possible state transition and trivially catch impossible ones.
  • 2
    @kiki Defensive programming (and designs) are great and all, but you don’t need to produce an error to produce incorrect output.

    Also, how about dealing with user input?

    Or how about someone changing either the name or interface of a class/module that my code relies upon?
  • 0
    @Root as we say here in Russia, "idiot-proof systems only deter _lazy_ idiots". (Защиту от дурака придумали, а защиту от долбоеба — еще нет)

    when it comes to user input, only a runtime solution is applicable, so tests are useless here
  • 2
    @kiki What are you talking about? It’s easy to write specs to check your code against various bad cases. Input too long, wrong formatting, unexpected characters, bad nesting, whatever.
  • 2
    A fun little observation. Laws are, inherently, declarative. And yet they're full of bugs.

    (ie legislation from the government)
Add Comment