AI for Unit Testing

I work in an environment where people seek opportunities for using AI such as GitHub Copilot to perform daily development tasks.

While there is a general agreement that such tools can accelerate the development process, there is still some uncertainty about how they achieve this. Some possible explanations include quick refactoring options, automatic unit test generation, CI/CD YAML file generation, and assistance with integration testing. The last point refers to UI testing or end-to-end testing, while the second point narrows the focus to unit testing specifically. Although AI can help with most of these points, unit testing seems like an anti-pattern when looking into the details.

My Unit Testing process

Writing unit tests is a well-understood and daily practiced process. In fact, I have seen multiple projects accumulating tens of thousands of tests over their lifetime and developers create unit tests daily. There are numerous generic posts detailing the testing best practices or focusing on special aspects such as Performance testing within Unit tests bad idea.

Find out more

Static Abstract in Practice

I recently encountered a problem where a class used a static method, and I had to provide a second implementation for this static method from one of the callsites. It looked like this case:

public class MyClass
    public void Foo(int input)
        var value = MyStaticType.Calculate(input);
        // ...

I needed to have a different implementation for Calculate in one place I have been invoking the Foo method. There are multiple ways to address the issue, but I have chosen one that is only available on C# 11 and above.

Alternative Solutions

Find out more

Await events

Some legacy API-s provide an event-based mechanism to achieve an asynchronous behavior. In this case typically a sync method starts a longer running async operation and an event notifies the consumer when the operation succeeds or fails.

For example, a hypothetical code below sends a message to queue and also subscribes an event for completion.

_queue.Completed += (sender, args) => // message is sent

Code quickly gets complicated in case the caller wants to wait for the completion of the send operation. In more modern C# one would represent this operation with a Task. A Task could be then awaited at the call site:

Find out more

Frozen Collections

.NET 8 (Preview 1) introduces new collection types including FrozenDictionary and FrozenSet. Both types are frozen counterparts of Dictionary<TKey, TValue> and HashSet<T>. These types reside in the System.Collections.Frozen namespace in SystemCollections.Immutable.dll package.

The frozen semantics mean, that these collections resist to change once they become frozen: they are immutable. However, they are different to the existing immutable collections, as these are being even more restrictive to change. While immutable collections allow change by creating a new immutable collection frozen semantics discourage such operations. Immutable collections use 'clever' data structures in memory that make operations like Add and Remove (relatively) cheap given the underlying data may not change. Contrary, frozen collections can optimize for lookups (or for construction, but more on that later).

For example, ImmutableStack<T> uses a linked list implementation instead of using an array as backing data structure. It has two fields: a head which contains data stored by a user, and a tail which is a pointer to the next item on the stack. Pushing a new item to the stack creates a new instance of ImmutableStack<T> where the head field stores the new data pushed, and the tail references the previous instance of ImmutableStack<T>.

Note, that these are the actual field names in the implementation of ImmutableStack at the time of writing this blog post.

Find out more

Pooling IBufferWriter

In the .NET 7 area some high performance APIs offer overloads for dealing with raw data via IBufferWriter<T>. IBufferWriter is a contract for buffered writing. High performance APIs typically offer an overload of their methods with IBufferWriter<T> along with byte[], Stream or Memory<T>. However, this is not the case for every API.

IBufferWriter<T> offers three methods:

  • GetMemory() and GetSpan() get a piece of writable memory.

  • Advance() to notify the buffer writer that data has been written.

.NET 7 offers two implementations of IBufferWriter<T>: Pipe (via PipeWriter) and ArrayBufferWriter<T>. Pipes help to solve the problem of parsing high performance streaming data. ArrayBufferWriter<T> offers an array-based buffering solution by implementing IBufferWriter<T>.

Find out more