NOTE: Apart from
(and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.
Sunday, 01 September 2024
//Less than a minute
Τώρα έχω το... Πακέτο Umami.Net Εκεί έξω θέλω φυσικά να διασφαλίσω ότι όλα θα δουλέψουν όπως αναμενόταν. Για να το κάνουμε αυτό ο καλύτερος τρόπος είναι να δοκιμάσουμε κάπως ολοκληρωτικά όλες τις μεθόδους και τις τάξεις. Εδώ είναι που έρχεται η δοκιμή μονάδας.
Σημείωση: Αυτό δεν είναι μια "τέλεια προσέγγιση" θέση τύπου, είναι ακριβώς το πώς το έχω κάνει αυτή τη στιγμή. Στην πραγματικότητα δεν χρειάζεται πραγματικά να Mock το IHttpMessageHandler
Εδώ ένα μπορείτε να επιτεθείτε σε ένα DelegatingMessageHandler σε ένα κανονικό HttpClient για να το κάνετε αυτό. Απλά ήθελα να σου δείξω πώς μπορείς να το κάνεις με μια Μοκ.
Η δοκιμή μονάδας αναφέρεται στη διαδικασία δοκιμής μεμονωμένων μονάδων κώδικα για να εξασφαλιστεί ότι λειτουργούν όπως αναμένεται. Αυτό γίνεται με τη συγγραφή δοκιμών που αποκαλούν τις μεθόδους και τις τάξεις με ελεγχόμενο τρόπο και στη συνέχεια ο έλεγχος της εξόδου είναι όπως αναμένεται.
Για ένα πακέτο όπως Umami.Net αυτό είναι τόσο δύσκολο, καθώς και οι δύο αποκαλούν ένα απομακρυσμένο πελάτη πάνω HttpClient
και έχει ένα IHostedService
χρησιμοποιεί για να καταστήσει την αποστολή νέων δεδομένων γεγονότων όσο το δυνατόν πιο απρόσκοπτη.
Το κύριο μέρος της δοκιμής HttpClient
Η βασική βιβλιοθήκη αποφεύγει την πραγματική κλήση "HttpClient." Αυτό γίνεται με τη δημιουργία ενός HttpClient
που χρησιμοποιεί ένα HttpMessageHandler
που επιστρέφει μια γνωστή απάντηση. Αυτό γίνεται με τη δημιουργία ενός HttpClient
με HttpMessageHandler
που επιστρέφει μια γνωστή απάντηση? Σε αυτή την περίπτωση απλά αντηχώ πίσω την απάντηση εισόδου και ελέγξτε ότι δεν έχει παραμορφωθεί από το UmamiClient
.
public static HttpMessageHandler Create()
{
var mockHandler = new Mock<HttpMessageHandler>();
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(x => x.RequestUri.ToString().Contains("api/send")),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync((HttpRequestMessage request, CancellationToken cancellationToken) =>
{
// Read the request content
var requestBody = request.Content != null
? request.Content.ReadAsStringAsync(cancellationToken).Result
: null;
// Create a response that echoes the request body
var responseContent = requestBody != null
? requestBody
: "No request body";
// Return the response
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(responseContent, Encoding.UTF8, "application/json")
};
});
return mockHandler.Object;
}
Όπως θα δείτε αυτό δημιουργεί ένα Mock<HttpMessageHandler>
Στη συνέχεια, περνάω στο UmamiClient
.
Σε αυτόν τον κώδικα θα το συνδέσω με το δικό μας IServiceCollection
Μέθοδος ρύθμισης. Αυτό προσθέτει όλες τις υπηρεσίες που απαιτούνται από την UmamiClient
συμπεριλαμβανομένου του νέου μας HttpMessageHandler
και στη συνέχεια επιστρέφει το IServiceCollection
για χρήση στις δοκιμές.
public static IServiceCollection SetupServiceCollection(string webSiteId = Consts.WebSiteId,
string umamiPath = Consts.UmamiPath, HttpMessageHandler? handler = null)
{
var services = new ServiceCollection();
var umamiClientSettings = new UmamiClientSettings
{
WebsiteId = webSiteId,
UmamiPath = umamiPath
};
services.AddSingleton(umamiClientSettings);
services.AddScoped<PayloadService>();
services.AddLogging(x => x.AddConsole());
// Mocking HttpMessageHandler with Moq
var mockHandler = handler ?? EchoMockHandler.Create();
services.AddHttpClient<UmamiClient>((serviceProvider, client) =>
{
var umamiSettings = serviceProvider.GetRequiredService<UmamiClientSettings>();
client.BaseAddress = new Uri(umamiSettings.UmamiPath);
}).ConfigurePrimaryHttpMessageHandler(() => mockHandler);
return services;
}
Για να χρησιμοποιήσετε αυτό και να κάνετε την ένεση στο UmamiClient
Στη συνέχεια, χρησιμοποιώ αυτές τις υπηρεσίες στο UmamiClient
Στήσιμο.
public static UmamiClient GetUmamiClient(IServiceCollection? serviceCollection = null,
HttpContextAccessor? contextAccessor = null)
{
serviceCollection ??= SetupServiceCollection();
SetupUmamiClient(serviceCollection, contextAccessor);
if (serviceCollection == null) throw new NullReferenceException(nameof(serviceCollection));
var serviceProvider = serviceCollection.BuildServiceProvider();
return serviceProvider.GetRequiredService<UmamiClient>();
}
Θα δείτε ότι έχω ένα μάτσο εναλλακτικών προαιρετικών παραμέτρων που μου επιτρέπουν να εισάγω διαφορετικές επιλογές για διαφορετικούς τύπους δοκιμών.
Έτσι τώρα έχω όλα αυτά στη θέση τους Μπορώ τώρα να αρχίσω να γράφω δοκιμές για το UmamiClient
μέθοδοι.
Αυτό που σημαίνει όλη αυτή η ρύθμιση είναι ότι οι δοκιμές μας μπορούν να είναι αρκετά απλές
public class UmamiClient_SendTests
{
[Fact]
public async Task Send_Wrong_Type()
{
var umamiClient = SetupExtensions.GetUmamiClient();
await Assert.ThrowsAsync<ArgumentException>(async () => await umamiClient.Send(type: "boop"));
}
[Fact]
public async Task Send_Empty_Success()
{
var umamiClient = SetupExtensions.GetUmamiClient();
var response = await umamiClient.Send();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
Εδώ μπορείτε να δείτε την απλούστερη περίπτωση δοκιμής, απλά εξασφαλίζει ότι UmamiClient
Μπορεί να στείλει ένα μήνυμα και να λάβει μια απάντηση? type
Είναι λάθος. Αυτό είναι ένα συχνά παραβλεφθεί μέρος των δοκιμών, εξασφαλίζοντας ότι ο κώδικας αποτυγχάνει όπως αναμένεται.
Για να δοκιμάσουμε τη μέθοδο μας, μπορούμε να κάνουμε κάτι παρόμοιο. Στον παρακάτω κώδικα χρησιμοποιώ το δικό μου EchoHttpHandler
Απλά να αναλογιστείς την απάντηση που μου έστειλες και να διασφαλίσεις ότι θα στείλει πίσω αυτό που περιμένω.
[Fact]
public async Task TrackPageView_WithNoUrl()
{
var defaultUrl = "/testpath";
var contextAccessor = SetupExtensions.SetupHttpContextAccessor(path: "/testpath");
var umamiClient = SetupExtensions.GetUmamiClient(contextAccessor: contextAccessor);
var response = await umamiClient.TrackPageView();
var content = await response.Content.ReadFromJsonAsync<EchoedRequest>();
Assert.NotNull(response);
Assert.NotNull(content);
Assert.Equal(content.Payload.Url, defaultUrl);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
Αυτό χρησιμοποιεί το HttpContextAccessor
να ρυθμίσετε το μονοπάτι /testpath
και στη συνέχεια ελέγχει ότι η UmamiClient
Στέλνει αυτό σωστά.
public static HttpContextAccessor SetupHttpContextAccessor(string host = Consts.Host,
string path = Consts.Path, string ip = Consts.Ip, string userAgent = Consts.UserAgent,
string referer = Consts.Referer)
{
HttpContext httpContext = new DefaultHttpContext();
httpContext.Request.Host = new HostString(host);
httpContext.Request.Path = new PathString(path);
httpContext.Connection.RemoteIpAddress = IPAddress.Parse(ip);
httpContext.Request.Headers.UserAgent = userAgent;
httpContext.Request.Headers.Referer = referer;
var context = new HttpContextAccessor { HttpContext = httpContext };
return context;
}
Αυτό είναι σημαντικό για μας Umami κώδικα πελάτη, καθώς πολλά από τα δεδομένα που αποστέλλονται από κάθε αίτηση είναι στην πραγματικότητα δυναμικά παράγονται από το HttpContext
αντικείμενο. Οπότε δεν μπορούμε να στείλουμε τίποτα. await umamiClient.TrackPageView();
Καλέστε και θα εξακολουθούν να στέλνουν τα σωστά δεδομένα με την εξαγωγή του URL από το HttpContext
.
Όπως θα δούμε αργότερα είναι επίσης σημαντικό το δέος στέλνει αντικείμενα όπως το UserAgent
και IPAddress
όπως αυτά χρησιμοποιούνται από τον εξυπηρετητή Umami για να παρακολουθείτε τα δεδομένα και τις "παρακολουθήστε" απόψεις των χρηστών χωρίς να χρησιμοποιείτε cookies.
Για να έχουμε αυτό το προβλέψιμο, ορίζουμε ένα μάτσο Consts στο Consts
Μαθήματα. Έτσι μπορούμε να δοκιμάσουμε ενάντια σε προβλέψιμες απαντήσεις και αιτήματα.
public class Consts
{
public const string UmamiPath = "https://example.com";
public const string WebSiteId = "B41A9964-FD33-4108-B6EC-9A6B68150763";
public const string Host = "example.com";
public const string Path = "/example";
public const string Ip = "127.0.0.1";
public const string UserAgent = "Test User Agent";
public const string Referer = "Test Referer";
public const string DefaultUrl = "/testpath";
public const string DefaultTitle = "Example Page";
public const string DefaultName = "RSS";
public const string DefaultType = "event";
public const string Email = "[email protected]";
public const string UserId = "11224456";
public const string UserName = "Test User";
public const string SessionId = "B41A9964-FD33-4108-B6EC-9A6B68150763";
}
Αυτό είναι μόνο η αρχή της στρατηγικής δοκιμών μας για Umami.Net, πρέπει ακόμα να δοκιμάσουμε το IHostedService
και δοκιμή με βάση τα πραγματικά δεδομένα Umami παράγει (το οποίο δεν είναι τεκμηριωμένο οπουδήποτε, αλλά περιέχει ένα JWT μάρκα με κάποια χρήσιμα δεδομένα.)
{
"alg": "HS256",
"typ": "JWT"
}{
"id": "b9836672-feee-55c5-985a-a5a23d4a23ad",
"websiteId": "32c2aa31-b1ac-44c0-b8f3-ff1f50403bee",
"hostname": "example.com",
"browser": "chrome",
"os": "Windows 10",
"device": "desktop",
"screen": "1920x1080",
"language": "en-US",
"country": "GB",
"subdivision1": null,
"subdivision2": null,
"city": null,
"createdAt": "2024-09-01T09:26:14.418Z",
"visitId": "e7a6542f-671a-5573-ab32-45244474da47",
"iat": 1725182817
}2|Y*: �(N%-ޘ^1>@V
Οπότε θα θέλουμε να δοκιμάσουμε γι' αυτό, να προσομοιώσουμε το σύμβολο και πιθανόν να επιστρέψουμε τα δεδομένα για κάθε επίσκεψη (όπως θα θυμάστε αυτό είναι φτιαγμένο από ένα uuid(websiteId,ipaddress, useragent)
).
Αυτό είναι μόνο η αρχή της δοκιμής του πακέτου Umami.Net, υπάρχουν πολλά περισσότερα να κάνουμε, αλλά αυτό είναι μια καλή αρχή. Θα προσθέσω κι άλλα τεστ καθώς θα πηγαίνω και σίγουρα θα τα βελτιώνω.