Last active
October 13, 2021 15:58
-
-
Save zpqrtbnk/e2d978be282f5a64cc842c1cd645ed5d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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