7

I am the newest programmer on a very small team of 6 developers, 3 of which are offsite. By "newest" I mean both in years of experience (drastically) and familiarity with the code. I've been with the company for a couple years, though only recently shifted towards programming. I have no formal education on programming (be it degree, certificate, or otherwise), only experience is some very poorly-made hobbyist projects over the years. My co-developers and manager all have 30+ years professional programming experience, several of whom have been with the company since its start 20 years ago.

Naturally, the code I'm working with is a big ball of object-oriented mud (C# .NET Framework). It's not important what's specifically wrong with the code itself, just that it's very much a victim to legacy code rot. I've been scouring SE questions on how to deal with this, and been reading Working Effectively with Legacy Code as is often prescribed. Most of what I've found points to slowly and gradually getting the code under unit testing, starting with new code we write.

I've looked at Joel On Software's post regarding trying to improve things as the grunt, and it (and other SE posts) say to lead by example, to just start with it in my own work. Theoretically, if my code has test coverage, and it proves to be far easier to maintain, then that on its own could convince others to consider it.

The trouble is, I don't think I'm really in a position to start the overhead needed to begin implementing testing. My coworkers and boss, who all have far more experience than I do, have no intent on testing. I've asked briefly before about unit testing, and it was brushed off as "not a priority". At least one of my coworkers is outright opposed (ideologically) to unit testing. As the newest dev when the others have decades with this codebase, I think it'd be stepping out of line for me to try to press the issue further.

And from a technical side, almost everything I see on unit testing seems to make some assumptions that might be problematic for me:

  • Using a unit testing framework (presumably NUnit for .NET). This adds an external dependency to all the project files that we start putting tests on, and I'm not sure that would be taken very kindly.
  • Creating additional build/run configurations. From what I can tell, adding unit testing would require added build configurations, an additional project to load the test classes (with its own dependencies), or an #if DEBUG block in each runnable project's Program.cs to kick something off prior to debugging. There are 150+ projects all compiled together in one SLN, changing that much might not come across so well.
  • Being in a position to make structural changes needed to get a class in a test harness. Most of the programming I've been assigned has been smaller fixes buried within monster classes. I'm not sure slapping down a new interface to get a class under test when I'm assigned a bug fix is something that will go well.
  • Other team members being willing to run tests after they're added, and fixing tests that break when intended behavior changes. Nobody else on the team has any interest in unit testing. If I start writing tests, I'll be a steward to those tests and have to adjust whenever my coworkers break them.
  • Having a relatively unified VCS branch structure. We have a lot of long-term (upwards of 9 years unmerged) production branches, with a variety of fixes and features pseudo-cherry-picked inconsistently between them. A test that works in one branch is quite likely to need to be entirely different in another due to a lack of behavioral parity.

This office very much has a "just fix the here and now" attitude with software. If it's not an easy fix or an immediate and severe problem, it gets shelved indefinitely. We don't even have an issue tracker, so any lingering bugs just get forgotten. Any long-term infrastructural problems (testing, unsupported dependencies, issue tracking, misuse of branches) all get dismissed, because something else is always a higher priority.

Is it reasonably possible to start working on unit tests in an environment like this? And do it without:

  1. Irrevocably disturbing my coworkers and manager's very "cowboy" workflows
  2. Digging myself into an unmaintainable pit that only wastes time better spent elsewhere
  3. Overstepping my bounds and risking my job as the lowest ranking dev

To be clear, I am not advocating to start slapping tests all over the existing code and ignoring deliverables. I understand that sometimes things just have to get done. But gradual improvements along the way (which I do have time for) would really help, given how unwieldy the codebase is.

Maybe this belongs in Workplace SE instead, if so I apologize. I'm hoping there's some kind of technical solution without having to worry about interpersonal difficulties, so I'm trying here. Please move this question if I am mistaken.


TL;DR - New inexperienced dev on a small team consisting entirely of devs with 30+ years experience. Codebase (C#) is demonstrably difficult to maintain, with no automated testing whatsoever. Long-term improvements are always dismissed as "not a priority". Want to gradually work on unit tests, but no manager approval and no coworker support. Wondering if and how it might be possible to "boy scout rule" them in a way that doesn't disturb the cowboys or overstep my bounds.

7
  • When in rome...
    – Doc Brown
    Mar 4, 2023 at 21:49
  • 3
    You can't. Sorry. Best I can suggest is to survive long enough so that it won't be weird for you to jump out and find some (hopefully better) place.
    – Euphoric
    Mar 4, 2023 at 22:02
  • 1
    Well, all I can say is sometimes learning the bad practices, but knowing they are bad makes it easier to see the good in the good practices. Survive. Absorb. Then move on one you've gotten enough experience. Mar 4, 2023 at 22:35
  • 1
    The thing is, the general workplace itself is quite positive. They pay well, respect work-life balance, and allow me a path to gain experience in a field I'd otherwise never get a foot in the door. It's just that I disagree so strongly with their coding practices, and it hurts to see things getting worse when I know what should be done to make things better.
    – tageta72
    Mar 4, 2023 at 23:31
  • Just make your code the best you can. Maybe they will start seeing the benefit as time progresses. Mar 6, 2023 at 12:24

5 Answers 5

8

You are working on project S, which lacks CI/CD nor even the ability to see an NUnit green bar. Presumably it exports at least one public function you can call. Create project T, which calls one or more S functions. You can decide later on whether you tell anyone about T. Incorporate any external dependencies you like, verify some trivial property, and admire the green bar. Wait for next sprint planning session.

You are assigned a new item, a feature or bugfix, so you will add at least one new public function to S. You will have to run this code, more than once, and evaluate its results. Avoid printf debugging or eyeballing the GUI or whatever approach is customarily used with S. Write a Red (failing) test in T, then iterate until it is Green. At this point, S has good code committed to successfully close out the sprint.

When the subsequent sprint begins, you're assigned an item that requires you to interact with colleague Bob's function b(), but you have no idea how to call it nor how to verify the result. Rather than add paragraphs to some ReadMe explaining this, just add an automated test that covers b(). Now when you make changes, you can tell if you broke things, or anyway if you broke the covered behavior of interest. Add your new function f() that calls b(), and write a test for it. Close out the sprint with both S and T in better shape than when the sprint started. Why did we write these tests? To improve your productivity, so the feature is released quickly and correctly.

Get GitHub Actions or Jenkins or some other CI/CD approach to routinely run tests when new code is pushed. As calendar time goes by it will develop a history of either false alarms or valuable alerts. Decide later if you want to tell anyone about it.


What's wrong with S + T ? Oh, so many things, both technical and social, let's not count the ways. Perhaps the biggest is that commits across both are not atomic. So git is not your time machine. You might be able to clumsily synchronize times with a pair of checkout commands, to reproduce some regression and its fix.

After you've been doing this for six months, there will be some bug that is significant enough that it will need power tools to fix, and folks will be grateful to have good tools handed to them. Seize the opportunity, drag a bunch of code into a "test PoC" branch in S, and pitch your approach. Maybe it gets adopted a few sprints later after the current emergency has been dispatched in the accustomed way, but hey, that's still a victory.


Much of the planning for project initiatives is about de-risking them -- systematically exploring items that could jeopardize KPIs and choosing to abandon the item or adopt it, perhaps with adaptations from lessons learned. Your investment in automated testing serves your own productivity, and may pay dividends when the team can clearly see a different set of {risks, rewards} than they saw last year.

6
  • Project S consists of a suite of GUI programs (WinForms) to interface with hardware. Pretty much none of the classes in Project S can be instantiated without the presence of that hardware at the time the constructor is called. Not sure how to get these classes in a test harness in Project T without altering the structure of classes in Project S. There are a few exceptions to this, a few classes that can reasonably be instantiated without hardware and other procedural setup, but they are few and far between. Suggestions?
    – tageta72
    Mar 4, 2023 at 23:47
  • I won't even begin going down the DI / mock route, for fear of disturbing the existing S codebase. I will observe that many developers have found humble objects useful. It seems a feasible pattern within your shop when writing new features, e.g. a new input screen. I have to believe there's code in S that could interrogate the attached device for a detail like a serial number, and that if T is linked against dependency S then it, too, could interrogate that detail. When you're ready to de-cloak: make S more humble!
    – J_H
    Mar 4, 2023 at 23:59
  • 2
    Beautiful answer. Write unit tests because they help. Not because you're supposed to. Mar 5, 2023 at 15:30
  • 3
    I've built separate, incognito test projects before. A passing topic of conversation about the tests you write for yourself suddenly turn into "hey I changed X yesterday. Did you notice any problems in your tests?" The proof is in the pudding. So make pudding. Mar 5, 2023 at 21:01
  • I think short of the other devs and manager being open to the idea of testing as a gradual and valued improvement, a wrapper project for tests is the closest I'll get to a real solution.
    – tageta72
    Mar 5, 2023 at 22:09
0

It seems impossible to implement unit testing on a shared codebase, where every single one of your colleagues are dead set against it.

It's not clear whether this refusal is a "cowboy practice" however. I've worked in businesses big and small who have bespoke applications, and I don't recall any that were covered by unit tests, and I've never used them myself. I've not worked at NASA though, nor at a major global software vendor.

I think the crucial thing to note is that certain "good practices" are only justifiable in the largest businesses with the most sensitive code.

When smaller shops dispense with many things which are typical of larger operations, it is no more a "cowboy practice" than a master carpenter working alone, who does not have a quality system, a health and safety manager, or machinery costing millions, because the scale of his operation does not justify that.

There is a difference in style between industrial production and craft production certainly, but master craftsmen are not conventionally considered "cowboys" simply because they do not follow industrial methods.

8
  • 1
    Where I agree is that experienced, old-school programmers should not be considered to be "cowboy coders" just because they haven't discovered the value of unit testing for their work (yet). However, the very wrong assumption in this answer is that unit testing is a technique for "large businesses with industrial software production and/or very sensitive code". Quite the opposite: the right amount of unit testing can help to use the spare resources of a small team more efficiently, since it can automate the boring, time wasting, cumbersome work of test repetition.
    – Doc Brown
    Mar 8, 2023 at 17:35
  • @DocBrown, certainly if there would be repetitive manual testing, then it makes sense to automate. However my assumption was not that the alternative to unit tests would be repetitive manual testing, but no testing at all beyond that done manually during initial development.
    – Steve
    Mar 8, 2023 at 21:42
  • To clarify, not ALL of my colleagues are dead set against it. One is dead set against it, who is a middle manager. The head of the company (who is also lead dev) just sees it as a "nice to have" to be shelved. The other devs I have almost no contact with, and I have no idea their opinions on the matter. As for whether they are "cowboys", the lack of testing is but one factor. They routinely make ad-hoc design choices that are damaging to maintainability, and do so in many ways. I'm sure there are many senior devs who don't follow standards but do keep maintainability, but these aren't them.
    – tageta72
    Mar 9, 2023 at 2:37
  • @tageta72, well certainly they may be cowboys by some other standard, including the quality and integrity of the design. But people who conceive bad designs are just as likely to write sloppy tests. If you say he's a cowboy because his design stinks, then you're head-on with the issue. If you say he's a cowboy because he doesn't have a suite of automated unit tests, the argument may fail to land simply because unit testing is (in my experience, and without expressing opinion on the value of automated testing...) not widespread for bespoke/in-house development.
    – Steve
    Mar 9, 2023 at 15:26
  • I agree that bad designs and sloppy tests are definitely a thing. It's often enough that I've asked a colleague to delete or change a test before a merge. But I feel an aspect that's missing from this conversation is the kind of clear communication that tests facilitate. Bob ran a manual test a year ago, fine. But now I want to run that function, and I do what, consult a ReadMe? Chat with Bob? Read the source to figure out suitable environment + input arguments + interpretation of output? Maybe. Or I just run the test, setting a breakpoint. Plus, a year later, does Bob remember details?
    – J_H
    Mar 12, 2023 at 19:03
0

Joels article assumes management and colleagues are at worst ignorant or indifferent towards the better development practices you want to introduce.

But in your case it seems management and seniors are actively opposed to unit tests. This is a very different scenario. It will not end well if you try to stealthily do work management think is detrimental to the business!

Introducing unit tests on an existing code base that is not designed for testing will require some level of restructuring across the code base. There is no way you can do that without some buy-in from your colleagues. They might not want to write unit tests themselves but they need to at least be willing to have you give it a shot and not actively sabotage it.

You mention the senior who is "ideologically opposed" to unit tests. But you don't say a word about why they are opposed. I guess this means you never asked? But you can't get anywhere without listening to and addressing their concerns. There are plenty of examples of unit tests being used wrong and just becoming a general drain on resources without providing any increase in code quality. The person might have experienced such a project.

You need to listen to their concerns before you will get anywhere.

2
  • 1
    In the time since asking, I've been able to get a brief conversation on maintainability with the middle-rank senior dev. It turns out he isn't actually opposed to testing, just doesn't believe he has time for it. That said, I feel like my rapport with the head of the company is falling fast, and he actually does oppose maintenance work on the whole. Lately I find myself internally opposing just about everything he does with the code, and on rare occasions I dare to suggest a decision would cause problems. My coding tasks are being cut to the barest and most mundane, probably won't be long.
    – tageta72
    May 21, 2023 at 2:19
  • Know your bosses and culture. Nascent testing can survive in a primitive yet tolerant culture but not a vindictive boss. Ive experienced both on different teams in the same organization. With the psychopath boss you are the target and testing is just one potential avenue for abuse. If there are no overt punitive actions by now I'd say proceed.
    – radarbob
    Jun 4, 2023 at 3:07
0

An alternative to unit testing are lots of assertions.

What you want isn’t really unit tests, but the ability to spot bugs when they are introduced, and to be more certain that there are no bugs. And exact knowledge what code is supposed to do. Good unit tests are very helpful for this.

But if people are opposed to unit tests, then lots of assertions can achieve the same to a degree, and are harder to oppose because they are an old standard technique.

0

The rocket science of "boy scout rule" is to "stir the tea with a teaspoon", is to "mind your business", persevere and things will get in motion thanks to what I guess in physics is called sympathetic movement, in business is called disruptive business, in software development is called incremental development, in management is called bottom-up approach.

The question is by what methods could experienced developers perceive the benefits of changing the way they develop software. It depends. The way software is developed "passed the test of time" and it keep passing it; it's influenced by the contexts it has been developed through. Adding new techniques to existing code could be non-intrusive; unit testing new code is concerned with new code, is the side car pattern from containerisation design patterns; interlacing new and existing code implementing design patterns, bridge design pattern to mention one without knowing the details.

Bottom line is when there isn't anything better to do roll up your sleeves, peek your tools, persevere and have fun.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.