//DEVGURU

Category archives ‘software engineering’

The Ultimate Website Launch Checklist

Thursday, February 19th, 2009

websitelaunchGuys form Box UK released “a checklist that all websites should be checked against before launch“. It’s full of “oh, I know that!”, but I can imagine finding it very helpful when estimating project due dates and, of course, when launching the website/app – because you probably won’t remember some of them. Pretty cool.

Test-driven development w RoR – wprowadzenie

Monday, August 18th, 2008

Zacznę może od oczywistości doskonale znanej wszystkim, którzy mieli okazję pracować nad projektem większym, niż “blog w 15 minut”: wraz z rozwojem aplikacji rośnie liczba błędów, które wkradają się niepostrzeżenie “gdzieś, kiedyś”. Drobna zmiana jednej funkcjonalności potrafi wywalić połowę aplikacji – a to niezalogowany użytkownik dostaje w twarz wyjątek (bo nam, zalogowanym na developmencie przecież działa!), a to mail się nie wyśle, bo nie lubi użytkownika nil. Częstą praktyką jest zostawianie wyławiania takich “smaczków” na moment “tydzień-przed-wdrożeniem”. Często też “tydzień-przed-wdrożeniem” okazuje się być “potrzebujemy-jeszcze-pięciu-kolejnych-dni”, “trzeba-to-przepisać-bo-nie-działa” czy tym podobnymi… Na szczęście można temu zaradzić. Panie i panowie (i programiści), kłania się przed wami Test-driven development.

Zamysł jest taki: zanim napiszemy faktyczny kod – piszemy krótkie “przypadki testowe” (z ang. test cases). Przykłady?

  • zanim napiszemy funkcję do autentykacji użytkownika – piszemy test case, który sprawdza, czy logowanie przykładowego użytkownika powiodło się
  • zanim napiszemy faktyczne powiązania pomiędzy modelami – opisujemy je w testach
  • zanim napiszemy walidację konkretnego modelu – rozpisujemy komplet przypadków testowych, który atakuje model poprawnymi (lub niepoprawnymi) danymi i sprawdzamy, czy się zapisuje (lub też nie)

Po napisaniu test case zabieramy się za pisanie właściwej funkcjonalności, odpalamy testy i cieszymy się, jeśli wszystkie przeszły – a jeśli nie, to poprawiamy kod. Następnie piszemy kolejny test. Piszemy funkcjonalność. Odpalamy testy…
“Po co mam pisać dwa razy to samo?”, “czy to się nie kłóci z agile software development?”. Jeśli nie zadaliście sobie tych pytań, to szkoda. Ja zadałem. Zyskujemy kilka rzeczy:

  • Kompletny zestaw testów. Pozwala mocno zaoszczędzić czas (nawet, jeśli z początku wydaje się być inaczej) – poza wspomnianym już problemem debugowania przed wdrożeniem, testy dają nam również pewność, że aplikacja działa w różnych środowiskach (development, staging, production…), które nie zawsze przecież są na tych samych maszynach. Do tego jesteśmy świadomi błędów gdy zmiana jednej funkcjonalności, od której zależne są inne, spowoduje wysypanie się któregoś miejsca aplikacji, nawet bardzo ukrytego (jeśli jest pokryte testami, oczywiście).
  • Klarowne spojrzenie na cały projekt. Możemy bardziej skupić się na tym, jak aplikacja powinna działać w środku – pisząc test nie obchodzi nas, jak będzie wyglądała funkcja logująca użytkownika – obchodzi nas za to, w jaki sposób może zalogować się użytkownik. (Prosta myśl, ale trudna do przekazania).

Ok, uwierzcie mi: testy są przydatne. Do tego pisanie testów może być przyjemne i zautomatyzowane. Pisanie testów może uratować życia. Od czego zacząć?

Na start polecam shoulda plugin. Załóżmy, że chcemy mieć dzieci… Co powinno mieć dziecko? Imię, wagę, wzrost. Generujemy model za pomocą script/generate. Dostajemy model w /app/models i test w /test/units. Zacznijmy od testu.


require File.dirname(__FILE__) + '/../test_helper'

class ChildTest < ActiveSupport::TestCase
  should_have_db_columns :name, :weight, :height, :gender, :father_id
end

Następnie tworzymy migrację. Uwzględniamy w niej wymagane pola. Odpalamy rake db:migrate. Odpalamy test: rake test:units. Cieszymy się.

Dodajmy przykładowe dziecko do testów. Do pliku /test/fixtures/children.yml wrzućmy Timmy’ego:


timmy:
  id: 1
  name: Timmy
  height: 103
  weight: 15
  father_id: 1

Sporo waży. Dziecko powinno też mieć ojca:


require File.dirname(__FILE__) + '/../test_helper'

class ChildTest < ActiveSupport::TestCase
  should_have_db_columns :name, :weight, :height, :gender, :father_id

  should_belong_to :father

end

W modelu dodajemy odpowiednią relację:


belongs_to :father

Odpalamy testy. Cieszymy się. Co jednak, gdy dziecko nie ma ojca? Jest sierotą. Sprawdzimy to. Najpierw testem:


require File.dirname(__FILE__) + '/../test_helper'

class ChildTest < ActiveSupport::TestCase
  should_have_db_columns :name, :weight, :height, :gender, :father_id
  should_require_attributes :name

  should_belong_to :father

  context "A child" do
    setup do
      @child = children(:timmy)
    end
    should "be an orphan when father_id is null" do
      @child.father_id = nil
      assert_equal true, @child.orphan?
    end
  end
end

Następnie metoda w modelu:


def orphan?
  return self.father_id.nil?
end

Odpalamy test. Przechodzi. Dziecko jest sierotą. Możemy napisać kolejny test sprawdzający, czy dziecko mające ojca nie jest sierotą. Możemy sprawdzić, czy ojciec ma więcej dzieci i w tym przypadku sprawdzić, czy jest jedynakiem. Możemy sprawdzić walidację dziecka bez wagi (wiadomo, że takie nie istnieją)…

W następnym poście opiszę narzędzia, które pozwalają na zautomatyzowanie testowania czy też sprawdzanie procentowego pokrycia kodu testami – a póki co polecam przejrzeć dokumentację rdocs pluginu shoulda. To najlepsze źródło do zrozumienia idei TDD w railsach, na jakie trafiłem.

CakePHP – dobre praktyki

Monday, February 25th, 2008

Pisząc małe aplikacje nie musimy się przejmować zbytnio jakością kodu, ale już nawet średnie projekty wymagają tego, by je w jakiś sposób zaplanować, żeby je w ogóle skończyć. Po napisaniu kilku rzeczy w Cake’u mam parę spostrzeżeń na to, jak pisać, by było dobrze (a przynajmniej by nie było źle). Część z poniższych punktów odnosi się do programowania w ogóle, część jest stricte Cake’owa.

1. Dobre zaplanowanie struktury aplikacji.

Rzecz najważniejsza. Przeanalizować założenia, zidentyfikować wszystkie obiekty, które będą występować w systemie. Dobrze rozbić aplikację na wiele rozłącznych modułów, które będą mogły być oddzielnie implementowane i testowane, a dopiero później ze sobą łączone. Korzyści są oczywiste – mniejsze powiązanie różnych części aplikacji ze sobą (łatwiejsza implementacja, testowanie), łatwość podziału prac pomiędzy wielu programistów.

2. Podejście abstrakcyjne do implementacji funkcjonalności

Poszczególne moduły aplikacji mogą zostać tak wykonane, żeby mogły być użyte w wielu kontekstach bez dokonywania w nich żadnych, bądź tylko kosmetyczno/konfiguracyjnych zmian. Dla przykładu funkcjonalność oceniania obiektów przez użytkownika można wykonać następująco:

tabela w bazie danych ratings:

  • id – primary key
  • user_id – id oceniającego użytkownika
  • model – nazwa modelu obiektu ocenianego
  • foreign_key – id obiektu ocenianego
  • rating – ocena

Taka struktura pozwala na ocenę każdego obiektu w serwisie. Do tego można dopisać sobie odpowiedni Behavior udostępniający w wybranych modelach metodę realizującą zapis oceny do bazy i mamy prosty ogólny system oceniania, do którego aktywacji wystarczy dodanie behaviora do $actsAs modelu.

3. Uniezależnienie kontrolerów od interfejsu użytkownika

Akcje w kontrolerach powinny być tak zaimplementowane, by nie było różnicy czy są wywoływane poprzez AJAX czy normalnie. To widoki (a właściwie w tym przypadku layouty) decydują jak zostaną zwrócone dane wynikowe przez akcję.

4. Przeniesienie większości logiki do modeli

To w modelach, nie w kontrolerach powinna być zaimplementowa większość logiki biznesowej systemu – zgodnie z koncepcją “Fat model, skinny controller”. Często wiele kontrolerów korzysta z tych samych modeli wykonując na nich te same funkcje. Przeniesienie tych funkcji do modelu pozwala łatwiej później wprowadzać zmiany i poprawiać ewentualne błędy, ponieważ musimy tego dokonywać tylko w jednym miejscu – w modelu. Podobnie implementacja takich mechanizmów jak caching czy logowanie zdarzeń jest dużo łatwiejsza do wykonania i utrzymania.

5. Elementy

Podobnie jest z elementami. W wielu widokach występują te same elementy, więc powinny być tworzone właśnie w takiej formie. Dla przykładu: formularze dodawania i edycji obiektu z reguły różnią się od siebie co najwyżej wartością atrybutu action, zatem idealnie nadają się, by zrobić z nich element.

To pierwsze modele i kontrolery aplikacji determinują jej strukturę i to, w jaki sposób jest dalej rozwijana. W połowie prac, bardzo trudno zmienić założenia co do struktury kodu, zatem analiza i planowanie powinny odbyć się jeszcze przed rozpoczęciem kodowania. I myślę, że nie należy na nie przesadnie szczędzić czasu, gdyż zwróci się on później na pewno.