Skip to content

Instantly share code, notes, and snippets.

@zpqrtbnk
Last active October 13, 2021 15:58
Show Gist options
  • Save zpqrtbnk/e2d978be282f5a64cc842c1cd645ed5d to your computer and use it in GitHub Desktop.
Save zpqrtbnk/e2d978be282f5a64cc842c1cd645ed5d to your computer and use it in GitHub Desktop.
// I have a concern that despite having lots of tests and "good enough" test coverage, we
// are more or test "testing everything" in the hope of catching issues, but not using a
// very deterministic approach. I would rather split our tests per layers, and validate
// that each layer is OK.
//
// This file contains a sample test for the HMap.SetAsync method (aka map.set) that does
// rely on the entire client-side pipeline of using the codec to create a message,
// serializing the message and sending its frames over a TCP socket -- but does *not*
// rely on an Hazelcast cluster: instead, the test relies on a lightweight server that
// can receive, and respond to, messages. We use it to capture and validate the message
// that was sent.
//
// This is not exactly a true "unit test", as it still goes through all the clients' layers,
// but at least it only relies on client code. It means that for testing things such as TTLs,
// we don't have to do weird timing tricks: just verify that the correct TTL is passed to the
// server in the message.
//
// It should help reduce the total tests duration, and separate concerns.
//
// This test excludes:
//
// - Testing that, when presented when a certain message, the server behaves correctly. This
// is OK: that shoud be client-language independent, i.e. we should have only 1 tests suite
// for that purpose, outside any particular client code base.
//
// - Testing that, when connected to a true cluster that can be unstable, the client behaves
// correctly. This is OK too: that is another concern, and each client code base should have
// tests for this, but we don't need to test that *each* distributed object behaves
// correctly - just that the client remains stable.
//
// - Testing that, when we think something should happen, it happens. That one is tricky: if
// I set a TTL with map.SetAsync(key, value, ttl)... and I use a ttl value of zero because
// the .NET client says that zero means "infinite ttl"... am I observing an infinite TTL?
// At that point, we are testing that we have interpreted the server specs correctly.
//
// And I don't know how to test it, other that by running the tests against the actual
// server. But then maybe it's only about a limited subset of features?
//
// Finally, I am still annoyed that the test looks complex enough. I would love it to be way
// smaller, but maybe a good deal of it can be factored out of the method and reused for all
// tests targetting distributed objects method.
[Test]
[Timeout(10_000)]
public async Task Test()
{
// define the address
var address = "127.0.0.1:11000";
// configure a server
// every message it receives which is not handled will cause an error
await using var server = new Server(NetworkAddress.Parse(address), new NullLoggerFactory())
.WithMemberId(Guid.Empty)
.HandleClientAuthentication() // (pretend to) authenticates every client
.HandleClientAddClusterViewListener(); // sends members and partitions views (once)
// handle CreateProxy requests (do nothing, but required for client.GetMapAsync to succeed)
server.Handle(ClientCreateProxyCodec.RequestMessageType, async (svr, conn, requestMessage) =>
{
var responseMessage = ClientCreateProxyServerCodec.EncodeResponse();
await server.RespondAsync(conn, requestMessage, responseMessage).CfAwait();
});
// start the server, will be listening on the TCP port
await server.StartAsync().CfAwait();
// configure a client
var options = new HazelcastOptionsBuilder()
.With(o => { o.Networking.Addresses.Add(address); })
.Build();
// start the client, connected to the server
await using var client = await HazelcastClientFactory.StartNewClientAsync(options).CfAwait();
// get a map
await using var map = await client.GetMapAsync<string, string>("my-map").CfAwait();
// declare a handler that will capture the request
MapSetServerCodec.RequestParameters request = null;
server.Handle(MapSetCodec.RequestMessageType, async (svr, conn, requestMessage) =>
{
// capture the request
request = MapSetServerCodec.DecodeRequest(requestMessage);
// do nothing - but we need to respond
var responseMessage = MapSetServerCodec.EncodeResponse();
await svr.RespondAsync(conn, requestMessage, responseMessage).CfAwait();
});
// perform the operation
await map.SetAsync("key", "value").CfAwait();
// assert that the request was captured
Assert.That(request, Is.Not.Null);
// assert that the request was correct
Assert.That(request.Name, Is.EqualTo("my-map"));
Assert.That(client.Deserialize<string>(request.Value), Is.EqualTo("value"));
Assert.That(client.Deserialize<string>(request.Key), Is.EqualTo("key"));
Assert.That(request.Ttl, Is.EqualTo(-1));
Assert.That(request.ThreadId, Is.EqualTo(AsyncContext.Current.Id));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment