Old-fashioned Lisp and Smalltalk systems (and some old Scheme systems) can save the live memory image to a file. You can load that file at launch time to reproduce the dynamic state of the system at the time the image was saved. You can share saved dynamic state between colleagues that way. Pretty handy, especially when you want someone to be able to debug a hard-to-reproduce bug that just showed p on your screen.

We used to keep known good images to work from. Generally a pristine release image, and a reference image with current project code preloaded, and then one or more working images with programmer-specific or task-specific code loaded. I used to save a working image at the end of the day and launch it again the next morning. MCL would remember my state right down to the positions and contents of my windows, so it was a quick and easy way to drop right back into the state of mind I was in at the end of the previous day.

For source code, we used revision control, exactly as you'd expect. In the Ralph days it was Apple's Projector. Nowadays most folks use git.

It's not super convenient to use revision control for dumped images because they're moderately large binaries. Source-control systems are mostly not great with those. So I keep reference images around, treating them like any other valuable binary asset--the difference being that rebuilding them from scratch is pretty fast and easy with today's fast machines.

My standard operating procedure is to start with a single scratch file that constructs a sketch of my naive idea of the data I'm going to be working with, load it, and start poking it to find out where I'm wrong.

As I begin to know what I'm doing, exploratory expressions turn into definitions of functions and data structures. I refactor them out into proper source files and add references to them into a system loader (like everybody else, I use ASDF for that).

Occasionally I might screw up the dynamic state by doing something dumb. Then I kill the session and restart it. The Lisps I use are generally back up and ready for me to continue in less than a second.

When I've made enough progress to want to build a release that I can distribute to testers, I write a builder file. The exact details of that depend on which Lisp I'm using; Lispworks, SBCL, and CCL each have their own idiosyncratic ways of going about it, but a simple build is generally something like a half dozen lines, and a complex one probably isn't much more than a page or so.

The builder file evolves along with the rest of the project.

The same general process applies to unit and integration tests. In fact, I'm pretty much always testing--testing my thoughts and testing what I just wrote is probably the majority of my activity most days. All I have to do to create formal unit and integration tests is make a testing scaffold and then harvest expressions from my scratch files and test comments to flesh it out.

When I release, I tag a commit in the source repo. I might also make a reference image to keep around locally, in case I want to test something against it or hand it to a collaborator.

I work in files a lot, which is typical of Lispers. Smalltalkers are a little more oriented to living in the image. For the most part, files are a serialization format that you use to exchange code and other resources with other sites. Locally, everything lives in your image. Modern Smalltalk systems have their own built-in interfaces to revision control, either to git or, in Squeak or Pharo, to a Smalltalk-specific revision-control system called Monticello.